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
47 changes: 47 additions & 0 deletions test/gh-aw-cast-validator.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -691,6 +691,53 @@ describe('GH-AW Cast final-tree validator', () => {
expect(result.status, result.stderr).toBe(0);
});

it('accepts the fresh-Cast history.json example shipped in the squad-cast playbook', async () => {
const extractExample = (source: string): string => {
const match = source.match(
/<!-- SQUAD:CAST-HISTORY-EXAMPLE:BEGIN -->\r?\n\s*```json\r?\n([\s\S]*?)\r?\n\s*```\r?\n\s*<!-- SQUAD:CAST-HISTORY-EXAMPLE:END -->/,
);
if (!match) throw new Error('squad-cast playbook history.json example markers are missing');
return match[1].split(/\r?\n/).map(line => line.trim()).join('\n');
};
const example = extractExample(readFileSync(workflowPath, 'utf8'));
expect(extractExample(readFileSync(join(process.cwd(), 'workflows', 'package', 'squad.md'), 'utf8'))).toBe(example);

const history = JSON.parse(example) as {
universe_usage_history: Array<{ universe: string; used_at: string }>;
assignment_cast_snapshots: Record<string, { created_at: string; universe: string; agents: string[] }>;
};
expect(Object.keys(history.assignment_cast_snapshots)).toEqual(['cast-revision-1']);
const snapshot = history.assignment_cast_snapshots['cast-revision-1'];
expect(snapshot.agents).toEqual(active.map(({ id }) => id));
expect(history.universe_usage_history).toEqual([{ universe: snapshot.universe, used_at: snapshot.created_at }]);

const fixture = createFixture();
write(fixture.root, '.squad/casting/registry.json', JSON.stringify({
schema: 'squad-agent-provenance/v1',
schema_version: 1,
revision: 1,
generated_at: snapshot.created_at,
agents: Object.fromEntries(active.map(({ id, name, role }) => [
id,
{
display_name: name,
persistent_name: name,
role,
status: 'active',
universe: snapshot.universe,
created_at: '2026-09-20T00:00:00.000Z',
updated_at: snapshot.created_at,
},
])),
}));
write(fixture.root, '.squad/casting/history.json', example);
const registryFile = join(fixture.root, '.squad', 'casting', 'registry.json');

await expect(validateCastingPairFiles(registryFile)).resolves.toMatchObject({ revision: 1 });
const result = validateWorkflowResource(fixture.root, fixture.payload);
expect(result.status, result.stderr).toBe(0);
});

it.each([
{
name: 'revision-2 registry with unrelated empty history',
Expand Down
23 changes: 21 additions & 2 deletions workflows/package/squad.md
Original file line number Diff line number Diff line change
Expand Up @@ -2290,7 +2290,9 @@ Each mode's playbook ships as a **skill**. Enter this section only after **Actor

**Planning modes only** — before running the mode skill, also load `squad-planning-policy` (policy resolution) and `squad-planning-ontology` (artifact schemas and the lifecycle state machine). The non-planning modes (Cast, Connect, Adopt, Cast Member, Retire, Status, Review Relay, Approve Improvement, Revoke Improvement, Implement) must not load them.

If the parsed mode's skill cannot be loaded, report the failure in plain language and stop. Never improvise a mode playbook from memory.
**Skill discovery fallback:** if the `skill` tool reports `Skill not found` for a skill listed above (headless SDK sessions may not register workspace skills), read the installed copy `.github/skills/<skill-name>/SKILL.md` from the workspace with `bash` (`cat`) and follow that file verbatim as the playbook. Apply the same fallback to `squad-planning-policy` and `squad-planning-ontology`. Do not `report_incomplete` or stop solely because the skill tool is not registered.

If the parsed mode's skill cannot be loaded by either path, report the failure in plain language and stop. Never improvise a mode playbook from memory.

---

Expand Down Expand Up @@ -2495,7 +2497,24 @@ Guidelines: 4–7 active agents. Min: Lead + 2 specialists + 1 quality role.
- Descriptive mode: keep names role-derived, short, and unique; do not assign fictional character names.
- Themed modes: use one universe only, pressure/function over authority, no spoilers, and early-introduction names. For a custom universe, apply the same one-universe and spoiler-safety rules.
4. Write `.squad/casting/registry.json` as `squad-agent-provenance/v1`. Compare the committed registry: preserve every ID/tombstone, immutable role/creation time, and avatar unless explicitly cleared; set `revision` greater than the committed revision (legacy is 0). Keys are immutable IDs/directories. Records hold equal names, role, universe, lifecycle/status, and optional ID-owned avatar under `.squad/agents/{id}/`. Rename uses the existing ID; deletion tombstones; never reuse or infer IDs. In descriptive mode every registry entry has `universe` set to `"descriptive"`.
5. Initialize `.squad/casting/history.json`: `{ "universe_usage_history": [{ "universe": "descriptive-or-Universe", "assigned_at": "ISO", "agent_count": N }], "assignment_cast_snapshots": {} }`
5. Write `.squad/casting/history.json` so it passes the deterministic Cast validator (`squad-cast-validator.mjs`), replacing the disposable activation preset file entirely. For a fresh Cast (`revision: 1`) the file must contain exactly one usage record and one snapshot keyed `cast-revision-1`; `used_at` and `created_at` must both equal the registry `generated_at`; `universe` must match the registry (`"descriptive"` in descriptive mode); `agents` must list every registry agent ID; and the only top-level fields are `universe_usage_history` and `assignment_cast_snapshots` — never carry over `transaction_id`, `registry_revision`, `assigned_at`, or `agent_count`. Example for a registry with `generated_at` `2026-09-21T00:00:00.000Z` and agents `lead`, `builder`, `tester`:
<!-- SQUAD:CAST-HISTORY-EXAMPLE:BEGIN -->
```json
{
"universe_usage_history": [
{ "universe": "descriptive", "used_at": "2026-09-21T00:00:00.000Z" }
],
"assignment_cast_snapshots": {
"cast-revision-1": {
"created_at": "2026-09-21T00:00:00.000Z",
"universe": "descriptive",
"agents": ["lead", "builder", "tester"]
}
}
}
```
<!-- SQUAD:CAST-HISTORY-EXAMPLE:END -->
For a re-Cast at revision N, append one `cast-revision-N` snapshot and one usage record (both stamped with the new `generated_at`) to the committed history instead of rewriting it.

##### Step 4: Generate Scaffolding

Expand Down
10 changes: 5 additions & 5 deletions workflows/squad-workflows.manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,9 @@
"source": "workflows/package/squad.md",
"destination": ".github/workflows/squad.md",
"lock": ".github/workflows/squad.lock.yml",
"source_sha256": "b3e9b33c8ddbf578e277d036c21ea5d1a5136cd234aa6b153627dc5f345bbf3d",
"lock_sha256": "36e56e20eedb07007d81a64ebcae38fffe7441c69ff0dbc792048ea9c5576e48",
"package_lock_sha256": "f744139e41ce8667555d8b6508b30391d4bf2a142bbefc85e418035c95c7dd97"
"source_sha256": "f145131ddebed1f9e96d9d3f7b344b3137bbc4fc93c6c3aa5b60fce4d0393c38",
"lock_sha256": "9eb9a91fd119f3a9847d57856376e973031bfc38aea4a1df95b825a3de5cd663",
"package_lock_sha256": "2eaae88e8fc4ccc7c3a4dd12edd7e30e1cb63179bd085a18cb273e99a6bc963d"
},
{
"name": "squad-implement-worker",
Expand Down Expand Up @@ -89,8 +89,8 @@
"workflows": [
{
"name": "squad",
"lock_sha256": "36e56e20eedb07007d81a64ebcae38fffe7441c69ff0dbc792048ea9c5576e48",
"package_lock_sha256": "f744139e41ce8667555d8b6508b30391d4bf2a142bbefc85e418035c95c7dd97"
"lock_sha256": "9eb9a91fd119f3a9847d57856376e973031bfc38aea4a1df95b825a3de5cd663",
"package_lock_sha256": "2eaae88e8fc4ccc7c3a4dd12edd7e30e1cb63179bd085a18cb273e99a6bc963d"
},
{
"name": "squad-implement-worker",
Expand Down
23 changes: 21 additions & 2 deletions workflows/squad.md
Original file line number Diff line number Diff line change
Expand Up @@ -1087,7 +1087,9 @@ Each mode's playbook ships as a **skill**. Enter this section only after **Actor

**Planning modes only** — before running the mode skill, also load `squad-planning-policy` (policy resolution) and `squad-planning-ontology` (artifact schemas and the lifecycle state machine). The non-planning modes (Cast, Connect, Adopt, Cast Member, Retire, Status, Review Relay, Approve Improvement, Revoke Improvement, Implement) must not load them.

If the parsed mode's skill cannot be loaded, report the failure in plain language and stop. Never improvise a mode playbook from memory.
**Skill discovery fallback:** if the `skill` tool reports `Skill not found` for a skill listed above (headless SDK sessions may not register workspace skills), read the installed copy `.github/skills/<skill-name>/SKILL.md` from the workspace with `bash` (`cat`) and follow that file verbatim as the playbook. Apply the same fallback to `squad-planning-policy` and `squad-planning-ontology`. Do not `report_incomplete` or stop solely because the skill tool is not registered.

If the parsed mode's skill cannot be loaded by either path, report the failure in plain language and stop. Never improvise a mode playbook from memory.

---

Expand Down Expand Up @@ -1292,7 +1294,24 @@ Guidelines: 4–7 active agents. Min: Lead + 2 specialists + 1 quality role.
- Descriptive mode: keep names role-derived, short, and unique; do not assign fictional character names.
- Themed modes: use one universe only, pressure/function over authority, no spoilers, and early-introduction names. For a custom universe, apply the same one-universe and spoiler-safety rules.
4. Write `.squad/casting/registry.json` as `squad-agent-provenance/v1`. Compare the committed registry: preserve every ID/tombstone, immutable role/creation time, and avatar unless explicitly cleared; set `revision` greater than the committed revision (legacy is 0). Keys are immutable IDs/directories. Records hold equal names, role, universe, lifecycle/status, and optional ID-owned avatar under `.squad/agents/{id}/`. Rename uses the existing ID; deletion tombstones; never reuse or infer IDs. In descriptive mode every registry entry has `universe` set to `"descriptive"`.
5. Initialize `.squad/casting/history.json`: `{ "universe_usage_history": [{ "universe": "descriptive-or-Universe", "assigned_at": "ISO", "agent_count": N }], "assignment_cast_snapshots": {} }`
5. Write `.squad/casting/history.json` so it passes the deterministic Cast validator (`squad-cast-validator.mjs`), replacing the disposable activation preset file entirely. For a fresh Cast (`revision: 1`) the file must contain exactly one usage record and one snapshot keyed `cast-revision-1`; `used_at` and `created_at` must both equal the registry `generated_at`; `universe` must match the registry (`"descriptive"` in descriptive mode); `agents` must list every registry agent ID; and the only top-level fields are `universe_usage_history` and `assignment_cast_snapshots` — never carry over `transaction_id`, `registry_revision`, `assigned_at`, or `agent_count`. Example for a registry with `generated_at` `2026-09-21T00:00:00.000Z` and agents `lead`, `builder`, `tester`:
<!-- SQUAD:CAST-HISTORY-EXAMPLE:BEGIN -->
```json
{
"universe_usage_history": [
{ "universe": "descriptive", "used_at": "2026-09-21T00:00:00.000Z" }
],
"assignment_cast_snapshots": {
"cast-revision-1": {
"created_at": "2026-09-21T00:00:00.000Z",
"universe": "descriptive",
"agents": ["lead", "builder", "tester"]
}
}
}
```
<!-- SQUAD:CAST-HISTORY-EXAMPLE:END -->
For a re-Cast at revision N, append one `cast-revision-N` snapshot and one usage record (both stamped with the new `generated_at`) to the committed history instead of rewriting it.

##### Step 4: Generate Scaffolding

Expand Down
Loading