Skip to content

Commit 1175f82

Browse files
tamirdresherCopilotCopilot
authored
fix(gh-aw): align squad-cast history.json playbook with validator and add skill-discovery fallback (#2474)
The squad-cast playbook told the agent to initialize .squad/casting/history.json with assigned_at/agent_count and empty assignment_cast_snapshots, which squad-cast-validator.mjs rejects (used_at, a cast-revision-1 snapshot stamped with registry.generated_at, and no transaction_id/registry_revision are required). Every first /squad cast therefore failed with "universe usage history is malformed" and cascaded into specialists=0 false positives. - Replace the Step 3 history.json example with a validator-passing fresh-Cast example and spell out the schema rules. - Add an Execute Mode fallback: when the skill tool reports "Skill not found" (headless SDK sessions), read .github/skills/<name>/SKILL.md from disk. - Add a drift test that extracts the playbook example, pairs it with a matching revision-1 registry, and runs it through the validators; also asserts the package copy carries the identical example. - Regenerate workflows/package/squad.md and manifest digests. Fixes #2473 Co-authored-by: Copilot <Copilot@users.noreply.github.com> Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
1 parent 699589d commit 1175f82

4 files changed

Lines changed: 94 additions & 9 deletions

File tree

‎test/gh-aw-cast-validator.test.ts‎

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -691,6 +691,53 @@ describe('GH-AW Cast final-tree validator', () => {
691691
expect(result.status, result.stderr).toBe(0);
692692
});
693693

694+
it('accepts the fresh-Cast history.json example shipped in the squad-cast playbook', async () => {
695+
const extractExample = (source: string): string => {
696+
const match = source.match(
697+
/<!-- SQUAD:CAST-HISTORY-EXAMPLE:BEGIN -->\r?\n\s*```json\r?\n([\s\S]*?)\r?\n\s*```\r?\n\s*<!-- SQUAD:CAST-HISTORY-EXAMPLE:END -->/,
698+
);
699+
if (!match) throw new Error('squad-cast playbook history.json example markers are missing');
700+
return match[1].split(/\r?\n/).map(line => line.trim()).join('\n');
701+
};
702+
const example = extractExample(readFileSync(workflowPath, 'utf8'));
703+
expect(extractExample(readFileSync(join(process.cwd(), 'workflows', 'package', 'squad.md'), 'utf8'))).toBe(example);
704+
705+
const history = JSON.parse(example) as {
706+
universe_usage_history: Array<{ universe: string; used_at: string }>;
707+
assignment_cast_snapshots: Record<string, { created_at: string; universe: string; agents: string[] }>;
708+
};
709+
expect(Object.keys(history.assignment_cast_snapshots)).toEqual(['cast-revision-1']);
710+
const snapshot = history.assignment_cast_snapshots['cast-revision-1'];
711+
expect(snapshot.agents).toEqual(active.map(({ id }) => id));
712+
expect(history.universe_usage_history).toEqual([{ universe: snapshot.universe, used_at: snapshot.created_at }]);
713+
714+
const fixture = createFixture();
715+
write(fixture.root, '.squad/casting/registry.json', JSON.stringify({
716+
schema: 'squad-agent-provenance/v1',
717+
schema_version: 1,
718+
revision: 1,
719+
generated_at: snapshot.created_at,
720+
agents: Object.fromEntries(active.map(({ id, name, role }) => [
721+
id,
722+
{
723+
display_name: name,
724+
persistent_name: name,
725+
role,
726+
status: 'active',
727+
universe: snapshot.universe,
728+
created_at: '2026-09-20T00:00:00.000Z',
729+
updated_at: snapshot.created_at,
730+
},
731+
])),
732+
}));
733+
write(fixture.root, '.squad/casting/history.json', example);
734+
const registryFile = join(fixture.root, '.squad', 'casting', 'registry.json');
735+
736+
await expect(validateCastingPairFiles(registryFile)).resolves.toMatchObject({ revision: 1 });
737+
const result = validateWorkflowResource(fixture.root, fixture.payload);
738+
expect(result.status, result.stderr).toBe(0);
739+
});
740+
694741
it.each([
695742
{
696743
name: 'revision-2 registry with unrelated empty history',

‎workflows/package/squad.md‎

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2290,7 +2290,9 @@ Each mode's playbook ships as a **skill**. Enter this section only after **Actor
22902290
22912291
**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.
22922292
2293-
If the parsed mode's skill cannot be loaded, report the failure in plain language and stop. Never improvise a mode playbook from memory.
2293+
**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.
2294+
2295+
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.
22942296
22952297
---
22962298
@@ -2495,7 +2497,24 @@ Guidelines: 4–7 active agents. Min: Lead + 2 specialists + 1 quality role.
24952497
- Descriptive mode: keep names role-derived, short, and unique; do not assign fictional character names.
24962498
- 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.
24972499
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"`.
2498-
5. Initialize `.squad/casting/history.json`: `{ "universe_usage_history": [{ "universe": "descriptive-or-Universe", "assigned_at": "ISO", "agent_count": N }], "assignment_cast_snapshots": {} }`
2500+
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`:
2501+
<!-- SQUAD:CAST-HISTORY-EXAMPLE:BEGIN -->
2502+
```json
2503+
{
2504+
"universe_usage_history": [
2505+
{ "universe": "descriptive", "used_at": "2026-09-21T00:00:00.000Z" }
2506+
],
2507+
"assignment_cast_snapshots": {
2508+
"cast-revision-1": {
2509+
"created_at": "2026-09-21T00:00:00.000Z",
2510+
"universe": "descriptive",
2511+
"agents": ["lead", "builder", "tester"]
2512+
}
2513+
}
2514+
}
2515+
```
2516+
<!-- SQUAD:CAST-HISTORY-EXAMPLE:END -->
2517+
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.
24992518
25002519
##### Step 4: Generate Scaffolding
25012520

‎workflows/squad-workflows.manifest.json‎

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,9 @@
1515
"source": "workflows/package/squad.md",
1616
"destination": ".github/workflows/squad.md",
1717
"lock": ".github/workflows/squad.lock.yml",
18-
"source_sha256": "b3e9b33c8ddbf578e277d036c21ea5d1a5136cd234aa6b153627dc5f345bbf3d",
19-
"lock_sha256": "36e56e20eedb07007d81a64ebcae38fffe7441c69ff0dbc792048ea9c5576e48",
20-
"package_lock_sha256": "f744139e41ce8667555d8b6508b30391d4bf2a142bbefc85e418035c95c7dd97"
18+
"source_sha256": "f145131ddebed1f9e96d9d3f7b344b3137bbc4fc93c6c3aa5b60fce4d0393c38",
19+
"lock_sha256": "9eb9a91fd119f3a9847d57856376e973031bfc38aea4a1df95b825a3de5cd663",
20+
"package_lock_sha256": "2eaae88e8fc4ccc7c3a4dd12edd7e30e1cb63179bd085a18cb273e99a6bc963d"
2121
},
2222
{
2323
"name": "squad-implement-worker",
@@ -89,8 +89,8 @@
8989
"workflows": [
9090
{
9191
"name": "squad",
92-
"lock_sha256": "36e56e20eedb07007d81a64ebcae38fffe7441c69ff0dbc792048ea9c5576e48",
93-
"package_lock_sha256": "f744139e41ce8667555d8b6508b30391d4bf2a142bbefc85e418035c95c7dd97"
92+
"lock_sha256": "9eb9a91fd119f3a9847d57856376e973031bfc38aea4a1df95b825a3de5cd663",
93+
"package_lock_sha256": "2eaae88e8fc4ccc7c3a4dd12edd7e30e1cb63179bd085a18cb273e99a6bc963d"
9494
},
9595
{
9696
"name": "squad-implement-worker",

‎workflows/squad.md‎

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1087,7 +1087,9 @@ Each mode's playbook ships as a **skill**. Enter this section only after **Actor
10871087
10881088
**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.
10891089
1090-
If the parsed mode's skill cannot be loaded, report the failure in plain language and stop. Never improvise a mode playbook from memory.
1090+
**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.
1091+
1092+
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.
10911093
10921094
---
10931095
@@ -1292,7 +1294,24 @@ Guidelines: 4–7 active agents. Min: Lead + 2 specialists + 1 quality role.
12921294
- Descriptive mode: keep names role-derived, short, and unique; do not assign fictional character names.
12931295
- 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.
12941296
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"`.
1295-
5. Initialize `.squad/casting/history.json`: `{ "universe_usage_history": [{ "universe": "descriptive-or-Universe", "assigned_at": "ISO", "agent_count": N }], "assignment_cast_snapshots": {} }`
1297+
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`:
1298+
<!-- SQUAD:CAST-HISTORY-EXAMPLE:BEGIN -->
1299+
```json
1300+
{
1301+
"universe_usage_history": [
1302+
{ "universe": "descriptive", "used_at": "2026-09-21T00:00:00.000Z" }
1303+
],
1304+
"assignment_cast_snapshots": {
1305+
"cast-revision-1": {
1306+
"created_at": "2026-09-21T00:00:00.000Z",
1307+
"universe": "descriptive",
1308+
"agents": ["lead", "builder", "tester"]
1309+
}
1310+
}
1311+
}
1312+
```
1313+
<!-- SQUAD:CAST-HISTORY-EXAMPLE:END -->
1314+
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.
12961315
12971316
##### Step 4: Generate Scaffolding
12981317

0 commit comments

Comments
 (0)