Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/agents/squad.agent.md
Original file line number Diff line number Diff line change
Expand Up @@ -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).

Expand Down
4 changes: 2 additions & 2 deletions .squad-templates/casting-policy.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
"Lost",
"Marvel Cinematic Universe",
"DC Universe",
"Futurama (custom)"
"Futurama"
],
"universe_capacity": {
"The Usual Suspects": 6,
Expand All @@ -32,6 +32,6 @@
"Lost": 18,
"Marvel Cinematic Universe": 25,
"DC Universe": 18,
"Futurama (custom)": 8
"Futurama": 12
}
}
104 changes: 104 additions & 0 deletions .squad-templates/casting-reference.md
Original file line number Diff line number Diff line change
@@ -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"
}
}
}
```
2 changes: 1 addition & 1 deletion .squad-templates/squad.agent.md
Original file line number Diff line number Diff line change
Expand Up @@ -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).

Expand Down
6 changes: 4 additions & 2 deletions templates/casting-policy.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@
"Breaking Bad",
"Lost",
"Marvel Cinematic Universe",
"DC Universe"
"DC Universe",
"Futurama"
],
"universe_capacity": {
"The Usual Suspects": 6,
Expand All @@ -30,6 +31,7 @@
"Breaking Bad": 12,
"Lost": 18,
"Marvel Cinematic Universe": 25,
"DC Universe": 18
"DC Universe": 18,
"Futurama": 12
}
}
104 changes: 104 additions & 0 deletions templates/casting-reference.md
Original file line number Diff line number Diff line change
@@ -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"
}
}
}
```
2 changes: 1 addition & 1 deletion templates/squad.agent.md
Original file line number Diff line number Diff line change
Expand Up @@ -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).

Expand Down
104 changes: 104 additions & 0 deletions test/template-sync.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
/**
* 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', () => {
// 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);
});

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(a).toEqual(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);
}
});
});
Loading