From 1aaead41109d1f48dee598486c87cbe733ca83c7 Mon Sep 17 00:00:00 2001 From: Tamir Dresher Date: Sun, 7 Jun 2026 15:39:28 +0300 Subject: [PATCH 1/2] fix: auto-scaffold Fact Checker agent during init and cast (#1222) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The Fact Checker role landed in v0.10.0 (#789) with catalog entry, charter template, skill, AGENT_TEMPLATES map entry, and template manifest entry — but was never wired into the user-facing onboarding flow. Users running 'squad init' got Scribe/Ralph/Rai but never saw Fact Checker as a default or cast option. This mirrors how Rai was wired: - init.ts: adds 'fact-checker' to the default agents: array passed to sdkInitSquad() - cast.ts: adds factCheckerMember(), factCheckerCharter(), hasFactChecker branches in castTeam(), and the roster banner line Smoke-tested locally: 'squad init' in a clean repo now produces .squad/agents/fact-checker/charter.md alongside scribe/ralph/Rai. Closes #1222 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .changeset/fix-fact-checker-auto-scaffold.md | 13 +++ packages/squad-cli/src/cli/core/cast.ts | 86 ++++++++++++++++++++ packages/squad-cli/src/cli/core/init.ts | 5 ++ 3 files changed, 104 insertions(+) create mode 100644 .changeset/fix-fact-checker-auto-scaffold.md diff --git a/.changeset/fix-fact-checker-auto-scaffold.md b/.changeset/fix-fact-checker-auto-scaffold.md new file mode 100644 index 000000000..8f3df77f5 --- /dev/null +++ b/.changeset/fix-fact-checker-auto-scaffold.md @@ -0,0 +1,13 @@ +--- +"@bradygaster/squad-cli": patch +--- + +Fix #1222: Auto-scaffold Fact Checker agent during `squad init` and `squad cast` + +The Fact Checker role was added in v0.10.0 (#789) with its catalog entry, charter template, skill, AGENT_TEMPLATES map entry, and template manifest entry — but it was never wired into the user-facing onboarding flow. Users running `squad init` got Scribe/Ralph/Rai but never saw Fact Checker as a default or cast option. + +This change mirrors how Rai was wired: +- `init.ts` — adds `fact-checker` to the default `agents:` array passed to `sdkInitSquad()` +- `cast.ts` — adds `factCheckerMember()`, `factCheckerCharter()`, `hasFactChecker` branches in `castTeam()`, and the roster banner line + +Result: `squad init` (with or without `cast`) now produces `.squad/agents/fact-checker/charter.md` alongside the existing always-on agents. diff --git a/packages/squad-cli/src/cli/core/cast.ts b/packages/squad-cli/src/cli/core/cast.ts index ac7309cef..eb3126a06 100644 --- a/packages/squad-cli/src/cli/core/cast.ts +++ b/packages/squad-cli/src/cli/core/cast.ts @@ -498,6 +498,82 @@ function RaiMember(): CastMember { return { name: 'Rai', role: 'RAI Reviewer', scope: 'Content safety, bias detection, credential scanning, ethical review', emoji: '🛡️' }; } +function factCheckerMember(): CastMember { + return { name: 'Fact Checker', role: 'Fact Checker', scope: 'Claim verification, hallucination detection, counter-hypothesis analysis, source validation', emoji: '🔍' }; +} + +function factCheckerCharter(): string { + return `# Fact Checker + +> Trust, but verify. Every claim gets a source check. + +## Identity + +- **Name:** Fact Checker +- **Role:** Devil's Advocate & Verification Agent +- **Emoji:** 🔍 +- **Style:** Rigorous but constructive. Flags issues clearly without being abrasive. + +## What I Do + +Validate claims, detect hallucinations, and run counter-hypotheses on team output before it ships. + +## Verification Methodology + +For every claim or assertion I review: + +1. **Source Check:** What evidence supports this? Can I verify it? +2. **Counter-Hypothesis:** What would disprove this? Is there an alternative explanation? +3. **Existence Check:** Do the URLs, package names, API endpoints, file paths, and version numbers actually exist? +4. **Consistency Check:** Does this contradict anything in \`.squad/decisions.md\` or prior team output? + +## Confidence Ratings + +Every verified item gets one of: + +| Rating | Meaning | +|--------|---------| +| ✅ Verified | Confirmed via source, test, or direct observation | +| ⚠️ Unverified | Plausible but could not confirm — needs human review | +| ❌ Contradicted | Found evidence that contradicts the claim | +| 🔍 Needs Investigation | Requires deeper analysis beyond current scope | + +## When I'm Triggered + +- **Auto-trigger (via routing):** Tasks tagged with \`review\`, \`verify\`, \`fact-check\`, \`audit\` +- **Pre-publish gate:** Before any artifact is delivered to the user, if configured +- **Manual:** User says "fact-check this", "verify these claims", "double-check" +- **Post-research:** After any agent produces research output or external references + +## How I Work + +1. **Read the artifact** — understand what's being claimed +2. **Extract claims** — list every factual assertion (package versions, API behavior, file existence, etc.) +3. **Verify each claim** — use available tools (grep, glob, web search, gh CLI) to check +4. **Run counter-hypotheses** — for key assumptions, ask "what if this is wrong?" +5. **Produce a verification report** +6. **Write decision** if I found issues: \`.squad/decisions/inbox/fact-checker-{slug}.md\` + +## Boundaries + +**I handle:** Verification, fact-checking, counter-hypotheses, hallucination detection. + +**I don't handle:** Implementation, design, testing, or docs. I review, not create. + +**I am not a blocker by default.** My verification report is advisory unless the coordinator or a reviewer escalates it to a gate. + +## Collaboration + +Before starting work, run \`git rev-parse --show-toplevel\` to find the repo root, or use the \`TEAM ROOT\` provided in the spawn prompt. All \`.squad/\` paths must be resolved relative to this root. + +After making a decision others should know, write it to \`.squad/decisions/inbox/fact-checker-{brief-slug}.md\`. + +## Learnings + +Initial setup complete. Ready for verification work. +`; +} + function RaiCharter(): string { return `# Rai — RAI Reviewer @@ -618,6 +694,9 @@ export async function createTeam(teamRoot: string, proposal: CastProposal): Prom const hasRai = proposal.members.some(m => /Rai/i.test(m.name)); if (!hasRai) allMembers.push(RaiMember()); + const hasFactChecker = proposal.members.some(m => /fact.?checker/i.test(m.name)); + if (!hasFactChecker) allMembers.push(factCheckerMember()); + // Create agent directories and files for (const member of allMembers) { const nameLower = member.name.toLowerCase(); @@ -631,6 +710,8 @@ export async function createTeam(teamRoot: string, proposal: CastProposal): Prom charter = ralphCharter(); } else if (member.name === 'Rai' && !hasRai) { charter = RaiCharter(); + } else if (member.name === 'Fact Checker' && !hasFactChecker) { + charter = factCheckerCharter(); } else { charter = generateCharter(member); } @@ -805,5 +886,10 @@ export function formatCastSummary(proposal: CastProposal): string { lines.push(`🛡️ ${'Rai'.padEnd(10)} — ${'(background)'.padEnd(15)} RAI awareness, content safety`); } + const hasFactChecker = proposal.members.some(m => /fact.?checker/i.test(m.name)); + if (!hasFactChecker) { + lines.push(`🔍 ${'Fact Checker'.padEnd(10)} — ${'(advisory)'.padEnd(15)} Claim verification, hallucination detection`); + } + return lines.join('\n'); } diff --git a/packages/squad-cli/src/cli/core/init.ts b/packages/squad-cli/src/cli/core/init.ts index 3280e3a1b..38c709487 100644 --- a/packages/squad-cli/src/cli/core/init.ts +++ b/packages/squad-cli/src/cli/core/init.ts @@ -226,6 +226,11 @@ export async function runInit(dest: string, options: RunInitOptions = {}): Promi name: 'Rai', role: 'Rai', displayName: 'Rai', + }, + { + name: 'fact-checker', + role: 'fact-checker', + displayName: 'Fact Checker', } ], configFormat: options.sdk ? 'sdk' : 'markdown', From 010f6fc06965c409595d54e4e649abadf21cedef Mon Sep 17 00:00:00 2001 From: Tamir Dresher Date: Sun, 7 Jun 2026 17:09:31 +0300 Subject: [PATCH 2/2] fix(upgrade): auto-scaffold Rai + Fact Checker on squad upgrade (#1222) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Extends the #1222 fix to the third code path. \squad upgrade\ was intentionally silent on agents (preserves user state). For users upgrading from v0.9.x or earlier (no Rai) or v0.10.0 (no fact-checker), this means they'd never get the built-in agents unless they re-ran \squad init\ (which would overwrite other state). Adds \nsureBuiltinAgents()\ to \ unEnsureChecks()\. Idempotent — only scaffolds when the agent directory is absent. Never overwrites existing charters or history files. Sources content from the shipped \ emplates/{Rai,fact-checker}-charter.md\ templates (already present via TEMPLATE_MANIFEST). Scribe and Ralph are intentionally NOT scaffolded by upgrade — they predate this fix in every squad, and their charters are inlined in cast.ts (no shipped template file). Smoke tested locally: - Set up a simulated v0.9.4 squad (scribe + ralph only) - Ran \squad upgrade\ → 'scaffolded 2 built-in agent(s): Rai, fact-checker' - Ran upgrade again → no re-scaffold (idempotent) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .changeset/fix-fact-checker-auto-scaffold.md | 15 ++-- packages/squad-cli/src/cli/core/upgrade.ts | 78 ++++++++++++++++++++ 2 files changed, 87 insertions(+), 6 deletions(-) diff --git a/.changeset/fix-fact-checker-auto-scaffold.md b/.changeset/fix-fact-checker-auto-scaffold.md index 8f3df77f5..899c60d98 100644 --- a/.changeset/fix-fact-checker-auto-scaffold.md +++ b/.changeset/fix-fact-checker-auto-scaffold.md @@ -2,12 +2,15 @@ "@bradygaster/squad-cli": patch --- -Fix #1222: Auto-scaffold Fact Checker agent during `squad init` and `squad cast` +Fix #1222: Auto-scaffold Fact Checker agent during `squad init`, `squad cast`, and `squad upgrade` -The Fact Checker role was added in v0.10.0 (#789) with its catalog entry, charter template, skill, AGENT_TEMPLATES map entry, and template manifest entry — but it was never wired into the user-facing onboarding flow. Users running `squad init` got Scribe/Ralph/Rai but never saw Fact Checker as a default or cast option. +The Fact Checker role was added in v0.10.0 (#789) with its catalog entry, charter template, skill, AGENT_TEMPLATES map entry, and template manifest entry — but it was never wired into the user-facing onboarding flow. Users running `squad init` got Scribe/Ralph/Rai but never saw Fact Checker. Users running `squad upgrade` from older versions never got Rai or Fact Checker scaffolded either (upgrade was intentionally silent on agents). -This change mirrors how Rai was wired: -- `init.ts` — adds `fact-checker` to the default `agents:` array passed to `sdkInitSquad()` -- `cast.ts` — adds `factCheckerMember()`, `factCheckerCharter()`, `hasFactChecker` branches in `castTeam()`, and the roster banner line +This change wires Fact Checker (and Rai, as a defensive backfill) into three code paths: + +- **`init.ts`** — adds `fact-checker` to the default `agents:` array passed to `sdkInitSquad()`. Fresh `squad init` now produces `.squad/agents/fact-checker/`. +- **`cast.ts`** — adds `factCheckerMember()`, `factCheckerCharter()`, `hasFactChecker` branches in `castTeam()`, and the roster banner line. Interactive `squad cast` now offers Fact Checker as an always-on background agent. +- **`upgrade.ts`** — new `ensureBuiltinAgents()` runs in `runEnsureChecks()`. Idempotently scaffolds `.squad/agents/Rai/` and `.squad/agents/fact-checker/` from shipped charter templates if missing. Never overwrites existing charters or history files. Scribe and Ralph are intentionally NOT scaffolded by upgrade (they predate this fix in all squads, and their charters are inlined in cast.ts). + +Result: any squad — fresh init, interactive cast, or upgrade from any prior version — now ends up with Fact Checker available. -Result: `squad init` (with or without `cast`) now produces `.squad/agents/fact-checker/charter.md` alongside the existing always-on agents. diff --git a/packages/squad-cli/src/cli/core/upgrade.ts b/packages/squad-cli/src/cli/core/upgrade.ts index c8a74b608..84a984b43 100644 --- a/packages/squad-cli/src/cli/core/upgrade.ts +++ b/packages/squad-cli/src/cli/core/upgrade.ts @@ -679,6 +679,13 @@ async function runEnsureChecks(dest: string, templatesDir: string, filesUpdated: filesUpdated.push(...memoryFiles); } + const builtinAgents = ensureBuiltinAgents(dest, templatesDir); + if (builtinAgents.length > 0) { + const uniqueAgentNames = Array.from(new Set(builtinAgents.map(p => path.basename(path.dirname(p))))); + success(`scaffolded ${uniqueAgentNames.length} built-in agent(s): ${uniqueAgentNames.join(', ')}`); + filesUpdated.push(...builtinAgents); + } + const skillCount = syncAllSkills(dest, templatesDir); if (skillCount > 0) { success(`synced ${skillCount} skills to .copilot/skills/`); @@ -757,6 +764,77 @@ export function ensureMemoryGovernanceUpgradeDefaults(dest: string): string[] { return created; } +/** + * Scaffold always-on built-in agent charters (Rai, Fact Checker) that ship + * as templates but may be missing from older squads. Idempotent — only writes + * when the agent directory is absent. Never overwrites existing charters or + * history. Sources charter content from the shipped `templates/{name}-charter.md` + * files when available, falling back to a minimal placeholder otherwise. + * + * Scribe and Ralph are intentionally NOT scaffolded here — they should already + * exist in any squad that ran a prior init, and their charters are inlined in + * cast.ts (no shipped template file). Adding them here would risk overwriting + * customized versions on legacy squads. + * + * @param dest Root directory containing .squad/ + * @param templatesDir Directory containing shipped charter templates + * @returns Paths (relative to dest) of created agent files + */ +export function ensureBuiltinAgents(dest: string, templatesDir: string): string[] { + const agentsDir = path.join(dest, '.squad', 'agents'); + if (!storage.existsSync(agentsDir)) { + storage.mkdirSync(agentsDir, { recursive: true }); + } + + // Built-in agents that ship as charter templates. Each entry maps to: + // - dirName: case-preserving directory name under .squad/agents/ + // - templateFile: filename under templatesDir (charter template) + // - displayName: shown in history.md header + const builtins: Array<{ dirName: string; templateFile: string; displayName: string }> = [ + { dirName: 'Rai', templateFile: 'Rai-charter.md', displayName: 'Rai' }, + { dirName: 'fact-checker', templateFile: 'fact-checker-charter.md', displayName: 'Fact Checker' }, + ]; + + const created: string[] = []; + for (const agent of builtins) { + const agentDir = path.join(agentsDir, agent.dirName); + const charterPath = path.join(agentDir, 'charter.md'); + const historyPath = path.join(agentDir, 'history.md'); + + // Idempotent: skip if agent directory already exists. Never overwrite + // existing charters or history files (preserves user customization). + if (storage.existsSync(agentDir)) continue; + + // Source charter content from the shipped template; fall back to a + // minimal placeholder if the template file is missing (defensive — should + // not happen in a well-formed install, but better than crashing upgrade). + const tplPath = path.join(templatesDir, agent.templateFile); + let charterContent: string; + if (storage.existsSync(tplPath)) { + charterContent = storage.readSync(tplPath) ?? ''; + } else { + charterContent = `# ${agent.displayName}\n\n> Charter template not found in shipped templates. Run \`squad upgrade\` after reinstalling the CLI to repair.\n`; + } + + try { + storage.mkdirSync(agentDir, { recursive: true }); + storage.writeSync(charterPath, charterContent); + created.push(path.join('.squad', 'agents', agent.dirName, 'charter.md')); + + storage.writeSync(historyPath, `# ${agent.displayName} — History\n\n## Learnings\n\nInitial scaffold via \`squad upgrade\`. Ready for work.\n`); + created.push(path.join('.squad', 'agents', agent.dirName, 'history.md')); + } catch (err: unknown) { + if (err instanceof Error && 'code' in err && ['EPERM', 'EACCES'].includes((err as NodeJS.ErrnoException).code ?? '')) { + warn(`Could not scaffold built-in agent ${agent.displayName} (read-only). Create .squad/agents/${agent.dirName}/ manually.`); + continue; + } + throw err; + } + } + + return created; +} + /** * Run the upgrade command */