From 803ad372754007279bf4406382b68b9663ca4272 Mon Sep 17 00:00:00 2001 From: Copilot <223556219+Copilot@users.noreply.github.com> Date: Thu, 19 Mar 2026 10:47:07 -0700 Subject: [PATCH 1/4] fix: align casting universe count with reality, create casting-reference.md (#459) - Create casting-reference.md (referenced by squad.agent.md but never existed) - Update '31 universes' claim to match actual count (15) in casting-policy.json - Restore Futurama universe on dev (regression) - Keep .squad-templates/ and templates/ in sync Fixes #459 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .github/agents/squad.agent.md | 2 +- .squad-templates/casting-reference.md | 104 ++++++++++++++++++++++++++ .squad-templates/squad.agent.md | 2 +- templates/casting-policy.json | 6 +- templates/casting-reference.md | 104 ++++++++++++++++++++++++++ templates/squad.agent.md | 2 +- 6 files changed, 215 insertions(+), 5 deletions(-) create mode 100644 .squad-templates/casting-reference.md create mode 100644 templates/casting-reference.md diff --git a/.github/agents/squad.agent.md b/.github/agents/squad.agent.md index 02457e292..3ff306728 100644 --- a/.github/agents/squad.agent.md +++ b/.github/agents/squad.agent.md @@ -681,7 +681,7 @@ Agent names are drawn from a single fictional universe per assignment. Names are **Rules (always loaded):** - ONE UNIVERSE PER ASSIGNMENT. NEVER MIX. -- 31 universes available (capacity 6–25). See reference file for full list. +- 15 universes available (capacity 6–25). See reference file for full list. - Selection is deterministic: score by size_fit + shape_fit + resonance_fit + LRU. - Same inputs → same choice (unless LRU changes). diff --git a/.squad-templates/casting-reference.md b/.squad-templates/casting-reference.md new file mode 100644 index 000000000..f0a72e094 --- /dev/null +++ b/.squad-templates/casting-reference.md @@ -0,0 +1,104 @@ +# Casting Reference + +On-demand reference for Squad's casting system. Loaded during Init Mode or when adding team members. + +## Universe Table + +| Universe | Capacity | Shape Tags | Resonance Signals | +|---|---|---|---| +| The Usual Suspects | 6 | small, noir, ensemble | crime, heist, mystery, deception | +| Reservoir Dogs | 8 | small, noir, ensemble | crime, heist, tension, loyalty | +| Alien | 8 | small, sci-fi, survival | space, isolation, threat, engineering | +| Ocean's Eleven | 14 | medium, heist, ensemble | planning, coordination, roles, charm | +| Arrested Development | 15 | medium, comedy, ensemble | dysfunction, business, family, satire | +| Star Wars | 12 | medium, sci-fi, epic | conflict, mentorship, legacy, rebellion | +| The Matrix | 10 | medium, sci-fi, cyberpunk | systems, reality, hacking, philosophy | +| Firefly | 10 | medium, sci-fi, western | frontier, crew, independence, smuggling | +| The Goonies | 8 | small, adventure, ensemble | exploration, treasure, kids, teamwork | +| The Simpsons | 20 | large, comedy, ensemble | satire, community, family, absurdity | +| Breaking Bad | 12 | medium, drama, tension | chemistry, transformation, consequence, power | +| Lost | 18 | large, mystery, ensemble | survival, mystery, groups, leadership | +| Marvel Cinematic Universe | 25 | large, action, ensemble | heroism, teamwork, powers, scale | +| DC Universe | 18 | large, action, ensemble | justice, duality, powers, mythology | +| Futurama | 12 | medium, sci-fi, comedy | future, robots, space, absurdity | + +**Total: 15 universes** — capacity range 6–25. + +## Selection Algorithm + +Universe selection is deterministic. Score each universe and pick the highest: + +``` +score = size_fit + shape_fit + resonance_fit + LRU +``` + +| Factor | Description | +|---|---| +| `size_fit` | How well the universe capacity matches the team size. Prefer universes where capacity ≥ agent_count with minimal waste. | +| `shape_fit` | Match universe shape tags against the assignment shape derived from the project description. | +| `resonance_fit` | Match universe resonance signals against session and repo context signals. | +| `LRU` | Least-recently-used bonus — prefer universes not used in recent assignments (from `history.json`). | + +Same inputs → same choice (unless LRU changes between assignments). + +## Casting State File Schemas + +### policy.json + +Source template: `.squad/templates/casting-policy.json` +Runtime location: `.squad/casting/policy.json` + +```json +{ + "casting_policy_version": "1.1", + "allowlist_universes": ["Universe Name", "..."], + "universe_capacity": { + "Universe Name": 10 + } +} +``` + +### registry.json + +Source template: `.squad/templates/casting-registry.json` +Runtime location: `.squad/casting/registry.json` + +```json +{ + "agents": { + "agent-role-id": { + "persistent_name": "CharacterName", + "universe": "Universe Name", + "created_at": "ISO-8601", + "legacy_named": false, + "status": "active" + } + } +} +``` + +### history.json + +Source template: `.squad/templates/casting-history.json` +Runtime location: `.squad/casting/history.json` + +```json +{ + "universe_usage_history": [ + { + "universe": "Universe Name", + "assignment_id": "unique-id", + "used_at": "ISO-8601" + } + ], + "assignment_cast_snapshots": { + "assignment-id": { + "universe": "Universe Name", + "agents": { + "role-id": "CharacterName" + }, + "created_at": "ISO-8601" + } + } +} +``` diff --git a/.squad-templates/squad.agent.md b/.squad-templates/squad.agent.md index fff5de0e3..e0957fd27 100644 --- a/.squad-templates/squad.agent.md +++ b/.squad-templates/squad.agent.md @@ -817,7 +817,7 @@ Agent names are drawn from a single fictional universe per assignment. Names are **Rules (always loaded):** - ONE UNIVERSE PER ASSIGNMENT. NEVER MIX. -- 31 universes available (capacity 6–25). See reference file for full list. +- 15 universes available (capacity 6–25). See reference file for full list. - Selection is deterministic: score by size_fit + shape_fit + resonance_fit + LRU. - Same inputs → same choice (unless LRU changes). diff --git a/templates/casting-policy.json b/templates/casting-policy.json index b3858c78d..12a57cca8 100644 --- a/templates/casting-policy.json +++ b/templates/casting-policy.json @@ -14,7 +14,8 @@ "Breaking Bad", "Lost", "Marvel Cinematic Universe", - "DC Universe" + "DC Universe", + "Futurama" ], "universe_capacity": { "The Usual Suspects": 6, @@ -30,6 +31,7 @@ "Breaking Bad": 12, "Lost": 18, "Marvel Cinematic Universe": 25, - "DC Universe": 18 + "DC Universe": 18, + "Futurama": 12 } } diff --git a/templates/casting-reference.md b/templates/casting-reference.md new file mode 100644 index 000000000..f0a72e094 --- /dev/null +++ b/templates/casting-reference.md @@ -0,0 +1,104 @@ +# Casting Reference + +On-demand reference for Squad's casting system. Loaded during Init Mode or when adding team members. + +## Universe Table + +| Universe | Capacity | Shape Tags | Resonance Signals | +|---|---|---|---| +| The Usual Suspects | 6 | small, noir, ensemble | crime, heist, mystery, deception | +| Reservoir Dogs | 8 | small, noir, ensemble | crime, heist, tension, loyalty | +| Alien | 8 | small, sci-fi, survival | space, isolation, threat, engineering | +| Ocean's Eleven | 14 | medium, heist, ensemble | planning, coordination, roles, charm | +| Arrested Development | 15 | medium, comedy, ensemble | dysfunction, business, family, satire | +| Star Wars | 12 | medium, sci-fi, epic | conflict, mentorship, legacy, rebellion | +| The Matrix | 10 | medium, sci-fi, cyberpunk | systems, reality, hacking, philosophy | +| Firefly | 10 | medium, sci-fi, western | frontier, crew, independence, smuggling | +| The Goonies | 8 | small, adventure, ensemble | exploration, treasure, kids, teamwork | +| The Simpsons | 20 | large, comedy, ensemble | satire, community, family, absurdity | +| Breaking Bad | 12 | medium, drama, tension | chemistry, transformation, consequence, power | +| Lost | 18 | large, mystery, ensemble | survival, mystery, groups, leadership | +| Marvel Cinematic Universe | 25 | large, action, ensemble | heroism, teamwork, powers, scale | +| DC Universe | 18 | large, action, ensemble | justice, duality, powers, mythology | +| Futurama | 12 | medium, sci-fi, comedy | future, robots, space, absurdity | + +**Total: 15 universes** — capacity range 6–25. + +## Selection Algorithm + +Universe selection is deterministic. Score each universe and pick the highest: + +``` +score = size_fit + shape_fit + resonance_fit + LRU +``` + +| Factor | Description | +|---|---| +| `size_fit` | How well the universe capacity matches the team size. Prefer universes where capacity ≥ agent_count with minimal waste. | +| `shape_fit` | Match universe shape tags against the assignment shape derived from the project description. | +| `resonance_fit` | Match universe resonance signals against session and repo context signals. | +| `LRU` | Least-recently-used bonus — prefer universes not used in recent assignments (from `history.json`). | + +Same inputs → same choice (unless LRU changes between assignments). + +## Casting State File Schemas + +### policy.json + +Source template: `.squad/templates/casting-policy.json` +Runtime location: `.squad/casting/policy.json` + +```json +{ + "casting_policy_version": "1.1", + "allowlist_universes": ["Universe Name", "..."], + "universe_capacity": { + "Universe Name": 10 + } +} +``` + +### registry.json + +Source template: `.squad/templates/casting-registry.json` +Runtime location: `.squad/casting/registry.json` + +```json +{ + "agents": { + "agent-role-id": { + "persistent_name": "CharacterName", + "universe": "Universe Name", + "created_at": "ISO-8601", + "legacy_named": false, + "status": "active" + } + } +} +``` + +### history.json + +Source template: `.squad/templates/casting-history.json` +Runtime location: `.squad/casting/history.json` + +```json +{ + "universe_usage_history": [ + { + "universe": "Universe Name", + "assignment_id": "unique-id", + "used_at": "ISO-8601" + } + ], + "assignment_cast_snapshots": { + "assignment-id": { + "universe": "Universe Name", + "agents": { + "role-id": "CharacterName" + }, + "created_at": "ISO-8601" + } + } +} +``` diff --git a/templates/squad.agent.md b/templates/squad.agent.md index 0bbc779c4..9378ce969 100644 --- a/templates/squad.agent.md +++ b/templates/squad.agent.md @@ -832,7 +832,7 @@ Agent names are drawn from a single fictional universe per assignment. Names are **Rules (always loaded):** - ONE UNIVERSE PER ASSIGNMENT. NEVER MIX. -- 31 universes available (capacity 6–25). See reference file for full list. +- 15 universes available (capacity 6–25). See reference file for full list. - Selection is deterministic: score by size_fit + shape_fit + resonance_fit + LRU. - Same inputs → same choice (unless LRU changes). From 44f69664286f9ed7f1703b807484c0a13e6d2ba8 Mon Sep 17 00:00:00 2001 From: Copilot <223556219+Copilot@users.noreply.github.com> Date: Thu, 19 Mar 2026 11:11:07 -0700 Subject: [PATCH 2/4] test: add template sync and casting parity tests (#459) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- test/template-sync.test.ts | 101 +++++++++++++++++++++++++++++++++++++ 1 file changed, 101 insertions(+) create mode 100644 test/template-sync.test.ts diff --git a/test/template-sync.test.ts b/test/template-sync.test.ts new file mode 100644 index 000000000..cb9fcf16a --- /dev/null +++ b/test/template-sync.test.ts @@ -0,0 +1,101 @@ +/** + * Template Sync & Casting Parity Tests (Issue #459) + * + * Prevents the casting universe mismatch from recurring by verifying: + * - Universe count in squad.agent.md matches casting-policy.json + * - All 3 copies of squad.agent.md agree on universe count + * - Referenced casting-reference.md files exist + * - casting-policy.json is identical across template dirs + * - Every allowlisted universe has a capacity entry and vice versa + */ + +import { describe, it, expect } from 'vitest'; +import { readFileSync, existsSync } from 'node:fs'; +import { join } from 'node:path'; + +const ROOT = process.cwd(); + +const SQUAD_TEMPLATES = join(ROOT, '.squad-templates'); +const TEMPLATES = join(ROOT, 'templates'); +const GITHUB_AGENTS = join(ROOT, '.github', 'agents'); + +/** Extract the "N universes available" number from a squad.agent.md file. */ +function extractUniverseCount(filePath: string): number { + const content = readFileSync(filePath, 'utf-8'); + const match = content.match(/(\d+)\s+universes?\s+available/i); + if (!match) throw new Error(`No "N universes available" found in ${filePath}`); + return parseInt(match[1], 10); +} + +describe('Template sync & casting parity (issue #459)', () => { + // --- Test 1: Universe count in squad.agent.md matches casting-policy.json --- + + describe('universe count matches casting-policy.json', () => { + const policy = JSON.parse(readFileSync(join(SQUAD_TEMPLATES, 'casting-policy.json'), 'utf-8')); + const policyCount = policy.allowlist_universes.length; + + it('.squad-templates/squad.agent.md matches policy', () => { + const claimed = extractUniverseCount(join(SQUAD_TEMPLATES, 'squad.agent.md')); + expect(claimed).toBe(policyCount); + }); + + it('templates/squad.agent.md matches policy', () => { + const claimed = extractUniverseCount(join(TEMPLATES, 'squad.agent.md')); + expect(claimed).toBe(policyCount); + }); + + it('.github/agents/squad.agent.md matches policy', () => { + const claimed = extractUniverseCount(join(GITHUB_AGENTS, 'squad.agent.md')); + expect(claimed).toBe(policyCount); + }); + }); + + // --- Test 2: All 3 copies agree on universe count --- + + it('all 3 squad.agent.md copies claim the same universe count', () => { + const counts = [ + extractUniverseCount(join(SQUAD_TEMPLATES, 'squad.agent.md')), + extractUniverseCount(join(TEMPLATES, 'squad.agent.md')), + extractUniverseCount(join(GITHUB_AGENTS, 'squad.agent.md')), + ]; + expect(counts[0]).toBe(counts[1]); + expect(counts[1]).toBe(counts[2]); + }); + + // --- Test 3: casting-reference.md exists where referenced --- + + describe('casting-reference.md exists in template dirs', () => { + it('.squad-templates/casting-reference.md exists', () => { + expect(existsSync(join(SQUAD_TEMPLATES, 'casting-reference.md'))).toBe(true); + }); + + it('templates/casting-reference.md exists', () => { + expect(existsSync(join(TEMPLATES, 'casting-reference.md'))).toBe(true); + }); + }); + + // --- Test 4: casting-policy.json is identical across template dirs --- + + it('casting-policy.json is identical in .squad-templates and templates', () => { + const a = JSON.parse(readFileSync(join(SQUAD_TEMPLATES, 'casting-policy.json'), 'utf-8')); + const b = JSON.parse(readFileSync(join(TEMPLATES, 'casting-policy.json'), 'utf-8')); + expect(JSON.stringify(a)).toBe(JSON.stringify(b)); + }); + + // --- Test 5: Every allowlisted universe has a capacity entry and vice versa --- + + it('allowlist_universes and universe_capacity are in sync', () => { + const policy = JSON.parse(readFileSync(join(SQUAD_TEMPLATES, 'casting-policy.json'), 'utf-8')); + const allowlist: string[] = policy.allowlist_universes; + const capacityKeys = Object.keys(policy.universe_capacity); + + // Every allowlisted universe has a capacity entry + for (const universe of allowlist) { + expect(capacityKeys).toContain(universe); + } + // No orphaned capacity entries + for (const key of capacityKeys) { + expect(allowlist).toContain(key); + } + }); +}); From 50cf1dedc90efd63f9730cbea256fa018c8047e4 Mon Sep 17 00:00:00 2001 From: Copilot <223556219+Copilot@users.noreply.github.com> Date: Thu, 19 Mar 2026 11:22:32 -0700 Subject: [PATCH 3/4] =?UTF-8?q?fix:=20address=20test=20review=20feedback?= =?UTF-8?q?=20=E2=80=94=20toEqual=20+=20scope=20comment?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- test/template-sync.test.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/test/template-sync.test.ts b/test/template-sync.test.ts index cb9fcf16a..9e66048ae 100644 --- a/test/template-sync.test.ts +++ b/test/template-sync.test.ts @@ -65,6 +65,9 @@ describe('Template sync & casting parity (issue #459)', () => { // --- Test 3: casting-reference.md exists where referenced --- describe('casting-reference.md exists in template dirs', () => { + // casting-reference.md is a template-only file that gets copied to .squad/templates/ + // during squad init. It belongs in .squad-templates/ and templates/ (both template dirs), + // but NOT in .github/agents/ (which only contains squad.agent.md, the live governance file). it('.squad-templates/casting-reference.md exists', () => { expect(existsSync(join(SQUAD_TEMPLATES, 'casting-reference.md'))).toBe(true); }); @@ -79,7 +82,7 @@ describe('Template sync & casting parity (issue #459)', () => { it('casting-policy.json is identical in .squad-templates and templates', () => { const a = JSON.parse(readFileSync(join(SQUAD_TEMPLATES, 'casting-policy.json'), 'utf-8')); const b = JSON.parse(readFileSync(join(TEMPLATES, 'casting-policy.json'), 'utf-8')); - expect(JSON.stringify(a)).toBe(JSON.stringify(b)); + expect(a).toEqual(b); }); // --- Test 5: Every allowlisted universe has a capacity entry and vice versa --- From 70ea7b3ebf4df791382698c9e4b091e0663c30fe Mon Sep 17 00:00:00 2001 From: Copilot <223556219+Copilot@users.noreply.github.com> Date: Thu, 19 Mar 2026 12:29:11 -0700 Subject: [PATCH 4/4] fix: use Futurama with capacity 12 (not custom/8) to match templates/ --- .squad-templates/casting-policy.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.squad-templates/casting-policy.json b/.squad-templates/casting-policy.json index 281d75700..12a57cca8 100644 --- a/.squad-templates/casting-policy.json +++ b/.squad-templates/casting-policy.json @@ -15,7 +15,7 @@ "Lost", "Marvel Cinematic Universe", "DC Universe", - "Futurama (custom)" + "Futurama" ], "universe_capacity": { "The Usual Suspects": 6, @@ -32,6 +32,6 @@ "Lost": 18, "Marvel Cinematic Universe": 25, "DC Universe": 18, - "Futurama (custom)": 8 + "Futurama": 12 } }