diff --git a/test/gh-aw-cast-validator.test.ts b/test/gh-aw-cast-validator.test.ts index dda392b70..9f76211d9 100644 --- a/test/gh-aw-cast-validator.test.ts +++ b/test/gh-aw-cast-validator.test.ts @@ -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( + /\r?\n\s*```json\r?\n([\s\S]*?)\r?\n\s*```\r?\n\s*/, + ); + 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; + }; + 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', diff --git a/workflows/package/squad.md b/workflows/package/squad.md index 2652eeb4f..dd54b8354 100644 --- a/workflows/package/squad.md +++ b/workflows/package/squad.md @@ -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.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. --- @@ -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`: + + ```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"] + } + } + } + ``` + + 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 diff --git a/workflows/squad-workflows.manifest.json b/workflows/squad-workflows.manifest.json index de3df5c09..67773a410 100644 --- a/workflows/squad-workflows.manifest.json +++ b/workflows/squad-workflows.manifest.json @@ -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", @@ -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", diff --git a/workflows/squad.md b/workflows/squad.md index 1b2449b1e..ed6c63184 100644 --- a/workflows/squad.md +++ b/workflows/squad.md @@ -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.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. --- @@ -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`: + + ```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"] + } + } + } + ``` + + 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