From 24f12a27f42461bfec9d2d09911102f91de54664 Mon Sep 17 00:00:00 2001 From: Luiz Gustavo Date: Sun, 24 May 2026 13:27:58 -0300 Subject: [PATCH] W4.1+W4.2: anti-AI-tics house style + bureau nav (back/home everywhere) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two complaints in one wave: (W4.1) User: "Não pode ter vícios de IA como uso excessivo de '-' que a IA coloca geralmente no lugar de vírgulas por exemplo. Isso deve fazer parte do prompt geral." - New prompts/_house-style.md banning the 9 most common AI prose tells in both EN and PT-BR: 1. Em dashes as comma replacements (—) 2. Rule-of-three lists ("concrete, rigorous, and grounded") 3. Conjunctive openers ("Moreover", "Notably", "Ademais") 4. Superficial -ing analyses ("marking a shift", "destacando") 5. Inflated symbolism + AI vocab (tapestry, navigate, delve, underscore, robust, multifaceted, marco histórico, ...) 6. Negative parallelisms ("Not just X but Y") 7. Vague attribution ("Some scholars say...") 8. Summary closers ("In summary...", "Em suma...") 9. Hedging fluff ("It's important to note...") Verbatim chunk quotes are explicitly exempt; preserve as-is. - claude.ts callClaude() lazily loads _house-style.md once per process and PREPENDS it to every detective's system prompt: composedSystem = houseStyle + "---" + detective.systemPrompt This means all 7 detectives + future ones get the rules without any per-prompt change. (W4.2) User: "Quando entra em uma página da investigação não tem como voltar! UX terrível!" - New sticky topbar with explicit "← home" + "🔎 bureau" buttons + clickable breadcrumb trail. Always visible at the top of every bureau page so the user can escape in one click. - Wired into /bureau, /h/[hypothesisId], /c/[slug], /jobs/[id]. Each page passes its sensible parent crumb (/bureau#hypotheses, /bureau#reports, /bureau#jobs). - Replaces the previous plain-text "disclosure.top / hypothesis / H-0004" line which had no visual affordance. Co-Authored-By: Claude Opus 4.7 (1M context) --- investigator-runtime/prompts/_house-style.md | 117 +++++++++++++++++++ investigator-runtime/src/lib/claude.ts | 31 ++++- web/app/bureau/page.tsx | 9 +- web/app/c/[slug]/page.tsx | 15 ++- web/app/h/[hypothesisId]/page.tsx | 15 ++- web/app/jobs/[id]/page.tsx | 15 ++- web/components/bureau-nav.tsx | 62 ++++++++++ 7 files changed, 232 insertions(+), 32 deletions(-) create mode 100644 investigator-runtime/prompts/_house-style.md create mode 100644 web/components/bureau-nav.tsx diff --git a/investigator-runtime/prompts/_house-style.md b/investigator-runtime/prompts/_house-style.md new file mode 100644 index 0000000..4ee5395 --- /dev/null +++ b/investigator-runtime/prompts/_house-style.md @@ -0,0 +1,117 @@ +# House style — bureau voice (mandatory) + +This preamble is injected before every detective's system prompt. It applies +to **all narrative prose you emit, in both EN and PT-BR**. Verbatim chunk +quotes from the source corpus are exempt — preserve those as-is. + +Your output will be read by an editor who will reject anything that smells +of AI-generated prose. The rules below are the editor's red pen. + +## 1. NO em dashes as comma replacements + +Forbidden: "...sobre o Novo México — território que abrigava os programas..." +Forbidden: "He was the director — a man of severe temperament — who..." + +Use a comma. Or end the sentence and start a new one. If you genuinely need +parentheses, use parentheses. Em dashes are allowed **only** for true range +notation (e.g. `1948–1949`) and for verbatim quotes from the corpus that +already contain them. + +In practice: if you can replace the dash with a comma without changing the +meaning, you should have used the comma. + +## 2. NO rule-of-three lists + +Forbidden: "concrete, quantitative, and grounded" +Forbidden: "rigorous, methodical, and exhaustive" +Forbidden: "uma análise cuidadosa, metódica e exaustiva" + +Two adjectives is enough. Four is fine when you have four real items. The +default three is filler — drop the weakest term. + +## 3. NO conjunctive fluff at sentence starts + +Forbidden openers: "Moreover", "Furthermore", "Notably", "Importantly", +"It is worth noting", "It should be mentioned", "Crucially", "Indeed", +"Ademais", "Além disso", "Vale destacar", "É importante notar", +"Notadamente", "Cumpre observar". + +Start the sentence with the content. If the link is real, use plain "But", +"And", "However", "So" — sparingly. + +## 4. NO superficial -ing analyses + +Forbidden: "marking a shift in policy", "highlighting the agency's concern", +"reflecting a deeper anxiety", "underscoring the importance", +"demonstrating the scale", "marcando uma mudança", "destacando a +preocupação", "refletindo uma ansiedade", "sublinhando a importância". + +State the conclusion as a finite verb. "The agency changed its policy." +Not "the document, marking a shift in policy, ...". The -ing tail is +almost always filler that hedges the claim into mush. + +## 5. NO inflated symbolism / promotional adjectives + +Forbidden: "stands as a testament", "a beacon of", "speaks volumes", +"watershed moment", "innovative", "groundbreaking", "remarkable", +"unprecedented" (unless the corpus literally uses it), "robust", +"comprehensive", "multifaceted", "nuanced", "rich tapestry", "complex +landscape", "navigate the complexities", "leverage", "delve into", +"shed light on", "paints a picture", "extensive", "myriad". + +PT-BR equivalents also forbidden: "marco histórico", "verdadeira riqueza", +"complexa tapeçaria", "panorama complexo", "navegar pelas complexidades", +"lança luz sobre", "demonstra de forma robusta", "abrangente", +"multifacetado", "pinta um quadro". + +Show, don't characterize. If a result is remarkable, the reader will see +that from the evidence — you don't need the adjective. + +## 6. NO negative parallelisms + +Forbidden: "Not just X, but Y." / "It's not X, it's Y." +Forbidden: "Não apenas X, mas Y." / "Não se trata de X, mas de Y." + +Just say Y. The negation of X is rhetorical scaffolding the editor will +delete. + +## 7. NO vague attribution + +Forbidden: "Some scholars argue...", "Many believe...", "It is widely +held...", "Críticos argumentam...", "Muitos sustentam...". + +Cite the chunk: `[[doc-id/pNNN#cNNNN]]`. If no chunk supports the +attribution, you don't get to make the claim. + +## 8. NO summary closers + +Forbidden last sentences: "In summary...", "In conclusion...", +"Ultimately...", "Em suma...", "Em última análise...", "Em conclusão...", +"Resumindo...". + +End on the last substantive sentence. The reader doesn't need to be told +the section is ending. + +## 9. NO hedging fluff (separate from calibrated confidence_band) + +Forbidden: "It's important to note that...", "It bears mentioning...", +"Of course...", "Naturally...", "Cabe ressaltar que...", "Naturalmente...", +"É claro que...". + +The hedging you ARE allowed: posterior probability + Tetlock band, and +explicit `[no evidence in corpus]` markers. Anything else hedge-shaped is +filler. + +--- + +## Quick self-check before emitting + +Read your draft and ask: +- Did I use any em dash that could be a comma? Replace it. +- Did I write any list of exactly three items? Drop the weakest one. +- Did I start any sentence with "Moreover/Notably/Furthermore"? Cut it. +- Did I use any word from the forbidden list? Find a plain alternative. +- Did I write "in summary" / "em suma"? Delete that sentence. + +The bureau's voice is **plainspoken investigative**. Like a senior detective +reporting facts to a colleague, not like a Wikipedia introduction. diff --git a/investigator-runtime/src/lib/claude.ts b/investigator-runtime/src/lib/claude.ts index 4149e51..bd3c62a 100644 --- a/investigator-runtime/src/lib/claude.ts +++ b/investigator-runtime/src/lib/claude.ts @@ -7,8 +7,28 @@ * so the orchestrator can enforce the per-job budget cap. */ import { spawn } from "node:child_process"; +import { readFile } from "node:fs/promises"; +import path from "node:path"; +import { fileURLToPath } from "node:url"; import { env } from "./env"; +const HERE = path.dirname(fileURLToPath(import.meta.url)); +const HOUSE_STYLE_PATH = path.resolve(HERE, "..", "..", "prompts", "_house-style.md"); + +// Lazy-load the house style once per process. Detective system prompts get +// this preamble prepended automatically (the bureau's anti-AI-tics rules: +// no em dash as comma, no rule-of-three, no "Moreover", no inflated vocab). +let _houseStyleCache: string | null | undefined; +async function loadHouseStyle(): Promise { + if (_houseStyleCache !== undefined) return _houseStyleCache; + try { + _houseStyleCache = await readFile(HOUSE_STYLE_PATH, "utf-8"); + } catch { + _houseStyleCache = null; + } + return _houseStyleCache; +} + export interface ClaudeCallArgs { /** Free-form prompt body to send. */ prompt: string; @@ -69,8 +89,15 @@ export async function callClaude(args: ClaudeCallArgs): Promise typeof s === "string" && s.trim().length > 0) + .join("\n\n---\n\n"); + const fullPrompt = composedSystem + ? `${composedSystem}\n\n---\n\n${args.prompt}` : args.prompt; const t0 = Date.now(); diff --git a/web/app/bureau/page.tsx b/web/app/bureau/page.tsx index 803e95f..a4055ba 100644 --- a/web/app/bureau/page.tsx +++ b/web/app/bureau/page.tsx @@ -8,6 +8,7 @@ import Link from "next/link"; import { pgQuery } from "@/lib/retrieval/db"; import { AuthBar } from "@/components/auth-bar"; +import { BureauNav } from "@/components/bureau-nav"; export const runtime = "nodejs"; export const dynamic = "force-dynamic"; @@ -141,13 +142,9 @@ export default async function BureauPage() { return (
+ -
-
- disclosure.top - / - bureau -
+

▍ The Investigation Bureau

diff --git a/web/app/c/[slug]/page.tsx b/web/app/c/[slug]/page.tsx index 5bdd0e5..b9e53bb 100644 --- a/web/app/c/[slug]/page.tsx +++ b/web/app/c/[slug]/page.tsx @@ -11,6 +11,7 @@ import { readFile } from "node:fs/promises"; import path from "node:path"; import { MarkdownBody } from "@/components/markdown-body"; import { AuthBar } from "@/components/auth-bar"; +import { BureauNav } from "@/components/bureau-nav"; export const runtime = "nodejs"; export const dynamic = "force-dynamic"; @@ -74,15 +75,13 @@ export default async function CaseReportPage({ return (
+ -
-
- disclosure.top - / - case-report - / - {slug} -
+
diff --git a/web/app/h/[hypothesisId]/page.tsx b/web/app/h/[hypothesisId]/page.tsx index 48dc2d5..4adfd13 100644 --- a/web/app/h/[hypothesisId]/page.tsx +++ b/web/app/h/[hypothesisId]/page.tsx @@ -17,6 +17,7 @@ import { readFile } from "node:fs/promises"; import path from "node:path"; import { pgQuery } from "@/lib/retrieval/db"; import { AuthBar } from "@/components/auth-bar"; +import { BureauNav } from "@/components/bureau-nav"; import { RedTeamRequestButton } from "@/components/red-team-request-button"; export const runtime = "nodejs"; @@ -196,15 +197,13 @@ export default async function HypothesisPage({ return (
+ -
-
- disclosure.top - / - hypothesis - / - {hypothesisId} -
+
{/* Header */}
diff --git a/web/app/jobs/[id]/page.tsx b/web/app/jobs/[id]/page.tsx index 0c98294..1ab52e7 100644 --- a/web/app/jobs/[id]/page.tsx +++ b/web/app/jobs/[id]/page.tsx @@ -18,6 +18,7 @@ import { notFound } from "next/navigation"; import Link from "next/link"; import { pgQuery } from "@/lib/retrieval/db"; import { AuthBar } from "@/components/auth-bar"; +import { BureauNav } from "@/components/bureau-nav"; import { JobStatusPoller } from "@/components/job-status-poller"; export const runtime = "nodejs"; @@ -109,15 +110,13 @@ export default async function JobPage({ return (
+ -
-
- disclosure.top - / - investigation - / - {job.job_id.slice(0, 8)} -
+
diff --git a/web/components/bureau-nav.tsx b/web/components/bureau-nav.tsx new file mode 100644 index 0000000..c030a5a --- /dev/null +++ b/web/components/bureau-nav.tsx @@ -0,0 +1,62 @@ +/** + * BureauNav — top navigation bar present on every bureau page. + * + * Solves "I'm stuck on this page, there's no way back" UX. Shows: + * - "← Home" (always → /) + * - "🔎 Bureau" (always → /bureau) + * - Breadcrumb trail of current page (passed via props) + * + * Server component. No client-side history needed because the parent links + * are deterministic (every bureau page has a known parent in the hierarchy). + */ +import Link from "next/link"; +import { ArrowLeft, Home } from "lucide-react"; + +export interface Crumb { + /** Display label. */ + label: string; + /** When omitted the crumb is rendered as the current page (no link). */ + href?: string; +} + +export function BureauNav({ crumbs }: { crumbs: Crumb[] }) { + return ( + + ); +}