diff --git a/.changeset/verified-gh-aw-ownership-staging.md b/.changeset/verified-gh-aw-ownership-staging.md new file mode 100644 index 000000000..020f4f441 --- /dev/null +++ b/.changeset/verified-gh-aw-ownership-staging.md @@ -0,0 +1,6 @@ +--- +"@bradygaster/squad-cli": patch +"@bradygaster/squad-sdk": patch +--- + +Preserve manifest-allowlisted gh-aw ownership metadata through broad consumer ignore rules by force-staging only the exact required ownership file. Verify every required artifact's staged type and bytes before commit or push, using bounded index queries that exclude unrelated consumer files. diff --git a/.squad-templates/skills/gh-aw-enlistment/SKILL.md b/.squad-templates/skills/gh-aw-enlistment/SKILL.md index a8fd5fc76..016f77f15 100644 --- a/.squad-templates/skills/gh-aw-enlistment/SKILL.md +++ b/.squad-templates/skills/gh-aw-enlistment/SKILL.md @@ -11,7 +11,7 @@ tools: when: "Every step: preflight identity/auth, requiring Issues, enabling Actions-created PRs, opening and watching the bootstrap PR." - name: "gh aw" description: "GitHub Agentic Workflows extension (github/gh-aw) — installs and strictly compiles the Squad workflow set." - when: "Installing the immutable native Squad package and compiling its seven workflows into deterministic .lock.yml files." + when: "Installing the immutable native Squad package and compiling its eight workflows into deterministic .lock.yml files." --- ## Context @@ -146,7 +146,7 @@ gh aw add "bradygaster/squad/workflows@${SQUAD_SHA}" The nested `workflows/aw.yml` is the only supported distribution registration. It isolates package auto-discovery from unrelated repository skills and agents, -and installs exactly seven workflows, fifteen runtime resources, and one +and installs exactly eight workflows, seventeen runtime resources, and one `gh-aw-enlistment` skill at the same resolved revision: - `squad.md` + `squad.lock.yml` @@ -156,11 +156,12 @@ and installs exactly seven workflows, fifteen runtime resources, and one - `squad-retro.md` + `squad-retro.lock.yml` - `squad-improvement-worker.md` + `squad-improvement-worker.lock.yml` - `squad-bootstrap.md` + `squad-bootstrap.lock.yml` +- `squad-command-router.md` + `squad-command-router.lock.yml` `squad-improvement-worker` is part of the standard, coherent install above — not a separate opt-in add-on. It stays dormant until a maintainer approves a governance-scoped retrospective proposal (see the gh-aw guide's retrospective -auto-implementation section); installing it alongside the other six keeps the +auto-implementation section); installing it alongside the other seven keeps the full stack consistent and avoids a second bootstrap pass later. Report/proposal-only is the default. Ordinary fixes require the explicit @@ -220,7 +221,7 @@ node .github/workflows/shared/squad-install-verifier.mjs \ This must run after any first-install approval and before committing. Success criteria: -- All seven workflows compile successfully. +- All eight workflows compile successfully. - The **only** permitted warning is the known `squad.md` bot-trigger warning: it configures both slash-command and `github-actions[bot]` triggers, and the bot trigger is required for controlled worker-continuation dispatches. @@ -230,7 +231,7 @@ criteria: ### 6. Require the verifier to prove the complete consumer contract - **STOP** if the verifier reports a missing source/lock pair, missing package - ownership record, stale source/resource digest, incomplete seven-workflow + ownership record, stale source/resource digest, incomplete eight-workflow registration, or mixed revision. - Use only the recovery commands printed by the verifier. They reinstall the complete package at one immutable revision; never repair one workflow or @@ -258,6 +259,8 @@ Stage **only** the documented generated surfaces, then verify the staged set: ```bash git add -- .gitattributes .github/aw/ .github/workflows/ .github/skills/ +node .github/workflows/shared/squad-install-verifier.mjs \ + --verify-staged-install --stage-ownership --source-revision "${SQUAD_SHA}" || exit 1 git diff --cached --stat # No deletions should be staged: test -z "$(git diff --cached --diff-filter=D --name-only)" || { echo "STOP: staged deletions"; exit 1; } @@ -267,6 +270,15 @@ test -z "$(git diff --cached --diff-filter=D --name-only)" || { echo "STOP: stag edits to **unrelated files**, or committed **log/diagnostic output**. Re-scope with explicit `git add -- ` — never `git add .`, `git add -A`, or `git commit -a`. +Consumer rules such as `packages/` can silently ignore the required ownership +JSON under `.github/aw/packages/`. The staged verifier force-adds only the exact +native package ownership JSON when it is ignored and untracked. It then verifies +every manifest-required source, lock, runtime, skill, manifest and ownership +file against the validated working-tree bytes in a snapshot of the Git index. +**STOP before commit/push** on missing metadata, a staging failure, an omitted +required file or a staged digest mismatch. Never force-add a directory or glob. +Rerun this gate after changing or restaging any installation file. + ### 8. Commit, push, and open the bootstrap PR to the captured default branch ```bash @@ -360,6 +372,8 @@ node .github/workflows/shared/squad-install-verifier.mjs \ --verify-install --source-revision "${SQUAD_SHA}" --strict-compile git add -- .gitattributes .github/aw/ .github/workflows/ .github/skills/ +node .github/workflows/shared/squad-install-verifier.mjs \ + --verify-staged-install --stage-ownership --source-revision "${SQUAD_SHA}" || exit 1 git commit -m "ci: add Squad agentic workflow" git push -u origin HEAD gh pr create --base "${default_branch}" \ diff --git a/docs/src/content/docs/guide/gh-aw.md b/docs/src/content/docs/guide/gh-aw.md index 8eff81f8c..41f6f8359 100644 --- a/docs/src/content/docs/guide/gh-aw.md +++ b/docs/src/content/docs/guide/gh-aw.md @@ -84,6 +84,8 @@ node .github/workflows/shared/squad-install-verifier.mjs \ # 7. Commit the generated files and open the bootstrap PR git add -- .gitattributes .github/aw/ .github/workflows/ .github/skills/ +node .github/workflows/shared/squad-install-verifier.mjs \ + --verify-staged-install --stage-ownership --source-revision "${SQUAD_SHA}" || exit 1 git diff --cached --stat test -z "$(git diff --cached --diff-filter=D --name-only)" git commit -m "ci: add Squad agentic workflow" @@ -364,6 +366,8 @@ done ```bash git add -- .gitattributes .github/aw/ .github/workflows/ .github/skills/ +node .github/workflows/shared/squad-install-verifier.mjs \ + --verify-staged-install --stage-ownership --source-revision "${SQUAD_SHA}" || exit 1 git diff --cached --stat test -z "$(git diff --cached --diff-filter=D --name-only)" git commit -m "ci: add Squad agentic workflow" @@ -381,6 +385,16 @@ state under `.github/aw/`, the installed skills, and `.gitattributes`. Review the complete generated diff in the bootstrap PR, address Copilot review feedback, and wait for required checks. Merge only after human approval. +Consumer ignore rules such as `packages/` can silently omit the required +`.github/aw/packages/` ownership JSON from directory staging. The staged verifier +force-adds only the exact native package ownership JSON when ignored and +untracked, never unrelated ignored files. It checks every manifest-required +source, lock, runtime, skill, manifest and ownership file in the Git index +against the verified working-tree bytes. Missing metadata, failed staging, +omitted required files or staged digest mismatches stop the install before +commit/push. Never force-add a directory or glob; rerun the gate after edits or +restaging, including package upgrades. + `gh aw add` may also create `.vscode/settings.json` to enable Copilot for Markdown workflow files. The command above intentionally leaves that optional editor setting untracked. Delete it if you do not want the local setting, or diff --git a/packages/squad-cli/templates/skills/gh-aw-enlistment/SKILL.md b/packages/squad-cli/templates/skills/gh-aw-enlistment/SKILL.md index a8fd5fc76..016f77f15 100644 --- a/packages/squad-cli/templates/skills/gh-aw-enlistment/SKILL.md +++ b/packages/squad-cli/templates/skills/gh-aw-enlistment/SKILL.md @@ -11,7 +11,7 @@ tools: when: "Every step: preflight identity/auth, requiring Issues, enabling Actions-created PRs, opening and watching the bootstrap PR." - name: "gh aw" description: "GitHub Agentic Workflows extension (github/gh-aw) — installs and strictly compiles the Squad workflow set." - when: "Installing the immutable native Squad package and compiling its seven workflows into deterministic .lock.yml files." + when: "Installing the immutable native Squad package and compiling its eight workflows into deterministic .lock.yml files." --- ## Context @@ -146,7 +146,7 @@ gh aw add "bradygaster/squad/workflows@${SQUAD_SHA}" The nested `workflows/aw.yml` is the only supported distribution registration. It isolates package auto-discovery from unrelated repository skills and agents, -and installs exactly seven workflows, fifteen runtime resources, and one +and installs exactly eight workflows, seventeen runtime resources, and one `gh-aw-enlistment` skill at the same resolved revision: - `squad.md` + `squad.lock.yml` @@ -156,11 +156,12 @@ and installs exactly seven workflows, fifteen runtime resources, and one - `squad-retro.md` + `squad-retro.lock.yml` - `squad-improvement-worker.md` + `squad-improvement-worker.lock.yml` - `squad-bootstrap.md` + `squad-bootstrap.lock.yml` +- `squad-command-router.md` + `squad-command-router.lock.yml` `squad-improvement-worker` is part of the standard, coherent install above — not a separate opt-in add-on. It stays dormant until a maintainer approves a governance-scoped retrospective proposal (see the gh-aw guide's retrospective -auto-implementation section); installing it alongside the other six keeps the +auto-implementation section); installing it alongside the other seven keeps the full stack consistent and avoids a second bootstrap pass later. Report/proposal-only is the default. Ordinary fixes require the explicit @@ -220,7 +221,7 @@ node .github/workflows/shared/squad-install-verifier.mjs \ This must run after any first-install approval and before committing. Success criteria: -- All seven workflows compile successfully. +- All eight workflows compile successfully. - The **only** permitted warning is the known `squad.md` bot-trigger warning: it configures both slash-command and `github-actions[bot]` triggers, and the bot trigger is required for controlled worker-continuation dispatches. @@ -230,7 +231,7 @@ criteria: ### 6. Require the verifier to prove the complete consumer contract - **STOP** if the verifier reports a missing source/lock pair, missing package - ownership record, stale source/resource digest, incomplete seven-workflow + ownership record, stale source/resource digest, incomplete eight-workflow registration, or mixed revision. - Use only the recovery commands printed by the verifier. They reinstall the complete package at one immutable revision; never repair one workflow or @@ -258,6 +259,8 @@ Stage **only** the documented generated surfaces, then verify the staged set: ```bash git add -- .gitattributes .github/aw/ .github/workflows/ .github/skills/ +node .github/workflows/shared/squad-install-verifier.mjs \ + --verify-staged-install --stage-ownership --source-revision "${SQUAD_SHA}" || exit 1 git diff --cached --stat # No deletions should be staged: test -z "$(git diff --cached --diff-filter=D --name-only)" || { echo "STOP: staged deletions"; exit 1; } @@ -267,6 +270,15 @@ test -z "$(git diff --cached --diff-filter=D --name-only)" || { echo "STOP: stag edits to **unrelated files**, or committed **log/diagnostic output**. Re-scope with explicit `git add -- ` — never `git add .`, `git add -A`, or `git commit -a`. +Consumer rules such as `packages/` can silently ignore the required ownership +JSON under `.github/aw/packages/`. The staged verifier force-adds only the exact +native package ownership JSON when it is ignored and untracked. It then verifies +every manifest-required source, lock, runtime, skill, manifest and ownership +file against the validated working-tree bytes in a snapshot of the Git index. +**STOP before commit/push** on missing metadata, a staging failure, an omitted +required file or a staged digest mismatch. Never force-add a directory or glob. +Rerun this gate after changing or restaging any installation file. + ### 8. Commit, push, and open the bootstrap PR to the captured default branch ```bash @@ -360,6 +372,8 @@ node .github/workflows/shared/squad-install-verifier.mjs \ --verify-install --source-revision "${SQUAD_SHA}" --strict-compile git add -- .gitattributes .github/aw/ .github/workflows/ .github/skills/ +node .github/workflows/shared/squad-install-verifier.mjs \ + --verify-staged-install --stage-ownership --source-revision "${SQUAD_SHA}" || exit 1 git commit -m "ci: add Squad agentic workflow" git push -u origin HEAD gh pr create --base "${default_branch}" \ diff --git a/packages/squad-sdk/templates/skills/gh-aw-enlistment/SKILL.md b/packages/squad-sdk/templates/skills/gh-aw-enlistment/SKILL.md index a8fd5fc76..016f77f15 100644 --- a/packages/squad-sdk/templates/skills/gh-aw-enlistment/SKILL.md +++ b/packages/squad-sdk/templates/skills/gh-aw-enlistment/SKILL.md @@ -11,7 +11,7 @@ tools: when: "Every step: preflight identity/auth, requiring Issues, enabling Actions-created PRs, opening and watching the bootstrap PR." - name: "gh aw" description: "GitHub Agentic Workflows extension (github/gh-aw) — installs and strictly compiles the Squad workflow set." - when: "Installing the immutable native Squad package and compiling its seven workflows into deterministic .lock.yml files." + when: "Installing the immutable native Squad package and compiling its eight workflows into deterministic .lock.yml files." --- ## Context @@ -146,7 +146,7 @@ gh aw add "bradygaster/squad/workflows@${SQUAD_SHA}" The nested `workflows/aw.yml` is the only supported distribution registration. It isolates package auto-discovery from unrelated repository skills and agents, -and installs exactly seven workflows, fifteen runtime resources, and one +and installs exactly eight workflows, seventeen runtime resources, and one `gh-aw-enlistment` skill at the same resolved revision: - `squad.md` + `squad.lock.yml` @@ -156,11 +156,12 @@ and installs exactly seven workflows, fifteen runtime resources, and one - `squad-retro.md` + `squad-retro.lock.yml` - `squad-improvement-worker.md` + `squad-improvement-worker.lock.yml` - `squad-bootstrap.md` + `squad-bootstrap.lock.yml` +- `squad-command-router.md` + `squad-command-router.lock.yml` `squad-improvement-worker` is part of the standard, coherent install above — not a separate opt-in add-on. It stays dormant until a maintainer approves a governance-scoped retrospective proposal (see the gh-aw guide's retrospective -auto-implementation section); installing it alongside the other six keeps the +auto-implementation section); installing it alongside the other seven keeps the full stack consistent and avoids a second bootstrap pass later. Report/proposal-only is the default. Ordinary fixes require the explicit @@ -220,7 +221,7 @@ node .github/workflows/shared/squad-install-verifier.mjs \ This must run after any first-install approval and before committing. Success criteria: -- All seven workflows compile successfully. +- All eight workflows compile successfully. - The **only** permitted warning is the known `squad.md` bot-trigger warning: it configures both slash-command and `github-actions[bot]` triggers, and the bot trigger is required for controlled worker-continuation dispatches. @@ -230,7 +231,7 @@ criteria: ### 6. Require the verifier to prove the complete consumer contract - **STOP** if the verifier reports a missing source/lock pair, missing package - ownership record, stale source/resource digest, incomplete seven-workflow + ownership record, stale source/resource digest, incomplete eight-workflow registration, or mixed revision. - Use only the recovery commands printed by the verifier. They reinstall the complete package at one immutable revision; never repair one workflow or @@ -258,6 +259,8 @@ Stage **only** the documented generated surfaces, then verify the staged set: ```bash git add -- .gitattributes .github/aw/ .github/workflows/ .github/skills/ +node .github/workflows/shared/squad-install-verifier.mjs \ + --verify-staged-install --stage-ownership --source-revision "${SQUAD_SHA}" || exit 1 git diff --cached --stat # No deletions should be staged: test -z "$(git diff --cached --diff-filter=D --name-only)" || { echo "STOP: staged deletions"; exit 1; } @@ -267,6 +270,15 @@ test -z "$(git diff --cached --diff-filter=D --name-only)" || { echo "STOP: stag edits to **unrelated files**, or committed **log/diagnostic output**. Re-scope with explicit `git add -- ` — never `git add .`, `git add -A`, or `git commit -a`. +Consumer rules such as `packages/` can silently ignore the required ownership +JSON under `.github/aw/packages/`. The staged verifier force-adds only the exact +native package ownership JSON when it is ignored and untracked. It then verifies +every manifest-required source, lock, runtime, skill, manifest and ownership +file against the validated working-tree bytes in a snapshot of the Git index. +**STOP before commit/push** on missing metadata, a staging failure, an omitted +required file or a staged digest mismatch. Never force-add a directory or glob. +Rerun this gate after changing or restaging any installation file. + ### 8. Commit, push, and open the bootstrap PR to the captured default branch ```bash @@ -360,6 +372,8 @@ node .github/workflows/shared/squad-install-verifier.mjs \ --verify-install --source-revision "${SQUAD_SHA}" --strict-compile git add -- .gitattributes .github/aw/ .github/workflows/ .github/skills/ +node .github/workflows/shared/squad-install-verifier.mjs \ + --verify-staged-install --stage-ownership --source-revision "${SQUAD_SHA}" || exit 1 git commit -m "ci: add Squad agentic workflow" git push -u origin HEAD gh pr create --base "${default_branch}" \ diff --git a/scripts/gh-aw-hosted-e2e.mjs b/scripts/gh-aw-hosted-e2e.mjs index 2a949f556..7d418532b 100644 --- a/scripts/gh-aw-hosted-e2e.mjs +++ b/scripts/gh-aw-hosted-e2e.mjs @@ -21,6 +21,7 @@ import { TRIGGER_PROBE_DESTINATION, checkSource, verifyInstall, + verifyStagedInstall, } from '../workflows/shared/squad-install-verifier.mjs'; const TRUSTED_SOURCE = Object.freeze({ @@ -802,6 +803,10 @@ function hosted(args, repositoryRoot) { const installPaths = ['.gitattributes', '.github/aw', '.github/workflows', '.github/skills', '.vscode'] .filter((path) => existsSync(resolve(checkout, path))); runChild('git', ['add', '--', ...installPaths], { cwd: checkout }); + const staged = verifyStagedInstall(checkout, { expectedRevision: sourceSha, stageOwnership: true }); + if (staged.failures.length > 0) { + throw new Error(`STOP: required installation files could not be staged and verified:\n${staged.failures.join('\n')}`); + } runChild('git', ['commit', '-m', 'ci: install Squad agentic workflows'], { cwd: checkout }); const installStartedAt = Date.now(); const installation = createAndMergePr({ diff --git a/scripts/sync-templates.mjs b/scripts/sync-templates.mjs index b3cdfe98e..dd4a8e6f5 100644 --- a/scripts/sync-templates.mjs +++ b/scripts/sync-templates.mjs @@ -117,6 +117,10 @@ for (const relFile of sourceFiles) { targets.push(join(AGENT_MD_TARGET, AGENT_MD_FILE)); } + if (relFile === 'skills/gh-aw-enlistment/SKILL.md') { + targets.push(join(ROOT, 'workflows', relFile)); + } + if (targets.length === 0) continue; for (const dest of targets) { diff --git a/templates/skills/gh-aw-enlistment/SKILL.md b/templates/skills/gh-aw-enlistment/SKILL.md index a8fd5fc76..016f77f15 100644 --- a/templates/skills/gh-aw-enlistment/SKILL.md +++ b/templates/skills/gh-aw-enlistment/SKILL.md @@ -11,7 +11,7 @@ tools: when: "Every step: preflight identity/auth, requiring Issues, enabling Actions-created PRs, opening and watching the bootstrap PR." - name: "gh aw" description: "GitHub Agentic Workflows extension (github/gh-aw) — installs and strictly compiles the Squad workflow set." - when: "Installing the immutable native Squad package and compiling its seven workflows into deterministic .lock.yml files." + when: "Installing the immutable native Squad package and compiling its eight workflows into deterministic .lock.yml files." --- ## Context @@ -146,7 +146,7 @@ gh aw add "bradygaster/squad/workflows@${SQUAD_SHA}" The nested `workflows/aw.yml` is the only supported distribution registration. It isolates package auto-discovery from unrelated repository skills and agents, -and installs exactly seven workflows, fifteen runtime resources, and one +and installs exactly eight workflows, seventeen runtime resources, and one `gh-aw-enlistment` skill at the same resolved revision: - `squad.md` + `squad.lock.yml` @@ -156,11 +156,12 @@ and installs exactly seven workflows, fifteen runtime resources, and one - `squad-retro.md` + `squad-retro.lock.yml` - `squad-improvement-worker.md` + `squad-improvement-worker.lock.yml` - `squad-bootstrap.md` + `squad-bootstrap.lock.yml` +- `squad-command-router.md` + `squad-command-router.lock.yml` `squad-improvement-worker` is part of the standard, coherent install above — not a separate opt-in add-on. It stays dormant until a maintainer approves a governance-scoped retrospective proposal (see the gh-aw guide's retrospective -auto-implementation section); installing it alongside the other six keeps the +auto-implementation section); installing it alongside the other seven keeps the full stack consistent and avoids a second bootstrap pass later. Report/proposal-only is the default. Ordinary fixes require the explicit @@ -220,7 +221,7 @@ node .github/workflows/shared/squad-install-verifier.mjs \ This must run after any first-install approval and before committing. Success criteria: -- All seven workflows compile successfully. +- All eight workflows compile successfully. - The **only** permitted warning is the known `squad.md` bot-trigger warning: it configures both slash-command and `github-actions[bot]` triggers, and the bot trigger is required for controlled worker-continuation dispatches. @@ -230,7 +231,7 @@ criteria: ### 6. Require the verifier to prove the complete consumer contract - **STOP** if the verifier reports a missing source/lock pair, missing package - ownership record, stale source/resource digest, incomplete seven-workflow + ownership record, stale source/resource digest, incomplete eight-workflow registration, or mixed revision. - Use only the recovery commands printed by the verifier. They reinstall the complete package at one immutable revision; never repair one workflow or @@ -258,6 +259,8 @@ Stage **only** the documented generated surfaces, then verify the staged set: ```bash git add -- .gitattributes .github/aw/ .github/workflows/ .github/skills/ +node .github/workflows/shared/squad-install-verifier.mjs \ + --verify-staged-install --stage-ownership --source-revision "${SQUAD_SHA}" || exit 1 git diff --cached --stat # No deletions should be staged: test -z "$(git diff --cached --diff-filter=D --name-only)" || { echo "STOP: staged deletions"; exit 1; } @@ -267,6 +270,15 @@ test -z "$(git diff --cached --diff-filter=D --name-only)" || { echo "STOP: stag edits to **unrelated files**, or committed **log/diagnostic output**. Re-scope with explicit `git add -- ` — never `git add .`, `git add -A`, or `git commit -a`. +Consumer rules such as `packages/` can silently ignore the required ownership +JSON under `.github/aw/packages/`. The staged verifier force-adds only the exact +native package ownership JSON when it is ignored and untracked. It then verifies +every manifest-required source, lock, runtime, skill, manifest and ownership +file against the validated working-tree bytes in a snapshot of the Git index. +**STOP before commit/push** on missing metadata, a staging failure, an omitted +required file or a staged digest mismatch. Never force-add a directory or glob. +Rerun this gate after changing or restaging any installation file. + ### 8. Commit, push, and open the bootstrap PR to the captured default branch ```bash @@ -360,6 +372,8 @@ node .github/workflows/shared/squad-install-verifier.mjs \ --verify-install --source-revision "${SQUAD_SHA}" --strict-compile git add -- .gitattributes .github/aw/ .github/workflows/ .github/skills/ +node .github/workflows/shared/squad-install-verifier.mjs \ + --verify-staged-install --stage-ownership --source-revision "${SQUAD_SHA}" || exit 1 git commit -m "ci: add Squad agentic workflow" git push -u origin HEAD gh pr create --base "${default_branch}" \ diff --git a/test/gh-aw-enlistment-skill.test.ts b/test/gh-aw-enlistment-skill.test.ts index bf895d7b4..f7667511c 100644 --- a/test/gh-aw-enlistment-skill.test.ts +++ b/test/gh-aw-enlistment-skill.test.ts @@ -28,6 +28,7 @@ const CANONICAL = `.squad-templates/skills/${SKILL_ID}/SKILL.md`; const GUIDE = 'docs/src/content/docs/guide/gh-aw.md'; const AGENT_GUIDE = '.github/agents.md'; const MIRRORS = [ + `workflows/skills/${SKILL_ID}/SKILL.md`, `templates/skills/${SKILL_ID}/SKILL.md`, `packages/squad-cli/templates/skills/${SKILL_ID}/SKILL.md`, `packages/squad-sdk/templates/skills/${SKILL_ID}/SKILL.md`, @@ -128,10 +129,11 @@ describe('gh-aw-enlistment skill', () => { expect(bulletLines.length, 'allowlist region must contain exactly 2 bullet lines').toBe(2); }); - it('installs one immutable native package containing all seven workflows', () => { + it('installs one immutable native package with the 8/17/1 topology', () => { expect(content).toContain('SQUAD_SHA="$(gh api repos/bradygaster/squad/commits/dev'); expect(content).toContain('^' + '[0-9a-f]{40}' + '$'); expect(content).toContain('gh aw add "bradygaster/squad/workflows@${SQUAD_SHA}"'); + expect(content).toContain('exactly eight workflows, seventeen runtime resources, and one'); for (const workflow of [ 'squad.md', 'squad-implement-worker.md', @@ -140,6 +142,7 @@ describe('gh-aw-enlistment skill', () => { 'squad-retro.md', 'squad-improvement-worker.md', 'squad-bootstrap.md', + 'squad-command-router.md', ]) expect(content).toContain(workflow); }); @@ -219,6 +222,9 @@ describe('gh-aw-enlistment skill', () => { it('forbids blanket staging and mandates explicit paths', () => { expect(content).toMatch(/git add \.|git add -A|git commit -a/); // referenced as an anti-pattern expect(content).toContain('git add -- .gitattributes .github/aw/ .github/workflows/ .github/skills/'); + expect(content.match(/--verify-staged-install --stage-ownership --source-revision "\$\{SQUAD_SHA\}" \|\| exit 1/g)) + .toHaveLength(2); + expect(content).toContain('Never force-add a directory or glob'); }); it('never auto-merges and defers automatic casting until after the bootstrap PR merges', () => { @@ -244,6 +250,11 @@ describe('gh-aw-enlistment skill', () => { const guide = readLF(GUIDE); const agentGuide = readLF(AGENT_GUIDE); + it('checks the staged installation before every bootstrap commit', () => { + expect(guide.match(/--verify-staged-install --stage-ownership --source-revision "\$\{SQUAD_SHA\}" \|\| exit 1/g)) + .toHaveLength(2); + }); + it('requires agents to execute the complete quick start through PR creation', () => { expect(guide).toContain('**Agent action required:**'); expect(guide).toContain('the agent must execute this entire block'); diff --git a/test/gh-aw-hosted-e2e.test.ts b/test/gh-aw-hosted-e2e.test.ts index 53ee052eb..49c3df3af 100644 --- a/test/gh-aw-hosted-e2e.test.ts +++ b/test/gh-aw-hosted-e2e.test.ts @@ -118,6 +118,16 @@ const currentOutputs = ( }); describe('Squad gh-aw hosted E2E controller', () => { + it('verifies and narrowly repairs staged ownership metadata before the installation commit', () => { + const gate = SCRIPT.indexOf('const staged = verifyStagedInstall(checkout,'); + const commit = SCRIPT.indexOf("['commit', '-m', 'ci: install Squad agentic workflows']"); + expect(gate).toBeGreaterThan(SCRIPT.indexOf("['add', '--', ...installPaths]")); + expect(gate).toBeLessThan(commit); + expect(SCRIPT.slice(gate, commit)).toContain('stageOwnership: true'); + expect(SCRIPT.slice(gate, commit)).toContain('if (staged.failures.length > 0)'); + expect(SCRIPT.slice(gate, commit)).toContain('throw new Error'); + }); + it('uses only a default-branch repository_dispatch controller and one PAT step', () => { const parsed = parse(WORKFLOW); expect(parsed.on.repository_dispatch.types).toEqual(['squad-gh-aw-hosted-e2e']); diff --git a/test/gh-aw-install-staging.test.ts b/test/gh-aw-install-staging.test.ts new file mode 100644 index 000000000..670a49add --- /dev/null +++ b/test/gh-aw-install-staging.test.ts @@ -0,0 +1,205 @@ +import { afterEach, describe, expect, it, vi } from 'vitest'; +import { execFileSync, spawnSync } from 'node:child_process'; +import { mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs'; +import { tmpdir } from 'node:os'; +import { dirname, join, resolve } from 'node:path'; +import { + CONTRACT_DESTINATION, + OWNERSHIP_DESTINATION, + materializeRuntime, + verifyInstall, + verifyStagedInstall, +} from '../workflows/shared/squad-install-verifier.mjs'; +import { createFirstInstallFixture } from './helpers/gh-aw-install-fixture.js'; + +vi.mock('node:child_process', async (importOriginal) => { + const actual = await importOriginal(); + return { ...actual, spawnSync: vi.fn(actual.spawnSync) }; +}); + +const revision = 'a'.repeat(40); +const roots: string[] = []; +const verifier = resolve('workflows/shared/squad-install-verifier.mjs'); + +function git(root: string, ...args: string[]): string { + return execFileSync('git', args, { cwd: root, encoding: 'utf8' }); +} + +function write(root: string, path: string, content: string | Buffer): void { + mkdirSync(dirname(join(root, path)), { recursive: true }); + writeFileSync(join(root, path), content); +} + +function consumer(): string { + const root = mkdtempSync(join(tmpdir(), 'squad-install-staging-')); + roots.push(root); + git(root, 'init', '--quiet'); + git(root, 'config', 'core.autocrlf', 'false'); + const fixture = createFirstInstallFixture(revision, 'package'); + for (const [path, content] of fixture.consumerFiles) write(root, path, content); + write(root, OWNERSHIP_DESTINATION, `${JSON.stringify(fixture.provenance, null, 2)}\n`); + write(root, '.gitattributes', '* -text\n'); + write(root, '.gitignore', 'packages/\nlogs/\n*.local\n'); + write(root, '.github/aw/packages/unrelated.json', '{"private":true}\n'); + write(root, 'packages/unrelated.txt', 'ignored consumer file\n'); + write(root, '.github/aw/logs/private.log', 'ignored diagnostic\n'); + write(root, '.github/skills/secret.local', 'ignored local file\n'); + materializeRuntime(root); + git(root, 'add', '--', '.gitattributes', '.github/aw/', '.github/workflows/', '.github/skills/'); + return root; +} + +function stagedPaths(root: string): string[] { + return git(root, 'ls-files', '-z').split('\0').filter(Boolean).sort(); +} + +afterEach(() => { + vi.clearAllMocks(); + for (const root of roots.splice(0)) rmSync(root, { recursive: true, force: true }); +}); + +describe('gh-aw: verified ownership staging under consumer ignore rules', () => { + it('bounds staged reads to required paths in a large unrelated consumer tree', () => { + const root = consumer(); + const unrelated = 'consumer-data'; + for (let index = 0; index < 20_000; index += 1) { + write(root, `${unrelated}/ordinary-file-${index.toString().padStart(5, '0')}.txt`, 'consumer content\n'); + } + git(root, 'add', '--', unrelated); + git(root, '-c', 'user.name=Test', '-c', 'user.email=test@example.invalid', + '-c', 'core.hooksPath=/dev/null', 'commit', '--quiet', '-m', 'large consumer baseline'); + write(root, `${unrelated}/ordinary-file-00000.txt`, 'unstaged consumer edit\n'); + write(root, `${unrelated}/untracked.txt`, 'untracked consumer content\n'); + const before = stagedPaths(root); + const tree = git(root, 'write-tree').trim(); + const unbounded = spawnSync('git', ['ls-tree', '-r', '-z', tree], { cwd: root }); + expect(unbounded.error).toMatchObject({ code: 'ENOBUFS' }); + const manifest = JSON.parse(readFileSync(join(root, CONTRACT_DESTINATION), 'utf8')); + const required = [ + CONTRACT_DESTINATION, + OWNERSHIP_DESTINATION, + ...manifest.workflows.flatMap((entry: { destination: string; lock: string }) => + [entry.destination, entry.lock]), + ...manifest.shared_runtime.flatMap((entry: { package_destination: string; destination: string }) => + [entry.package_destination, entry.destination]), + ...manifest.skills.map((entry: { destination: string }) => entry.destination), + ]; + const spawn = vi.mocked(spawnSync); + spawn.mockClear(); + expect(verifyStagedInstall(root, { expectedRevision: revision, stageOwnership: true }).failures).toEqual([]); + const queries = spawn.mock.calls.filter(([command, args]) => command === 'git' && args?.[0] === 'ls-tree'); + expect(queries).toHaveLength(1); + expect(queries[0][1]).toEqual(['ls-tree', '-z', expect.any(String), '--', ...new Set(required)]); + const unrelatedObject = git(root, 'rev-parse', `HEAD:${unrelated}/ordinary-file-00000.txt`).trim(); + const blobReads = spawn.mock.calls.filter(([command, args]) => command === 'git' && args?.[0] === 'cat-file'); + expect(blobReads).toHaveLength(new Set(required).size); + expect(blobReads.some(([, args]) => args?.includes(unrelatedObject))).toBe(false); + expect(stagedPaths(root)).toEqual([...before, OWNERSHIP_DESTINATION].sort()); + expect(git(root, 'diff', '--cached', '--name-only').trim()).toBe(OWNERSHIP_DESTINATION); + expect(git(root, 'diff', '--name-only').trim()).toBe(`${unrelated}/ordinary-file-00000.txt`); + expect(readFileSync(join(root, unrelated, 'untracked.txt'), 'utf8')).toBe('untracked consumer content\n'); + }, 30_000); + + it('reproduces the silent omission, then force-stages only the exact required JSON', () => { + const root = consumer(); + expect(verifyInstall(root, { expectedRevision: revision }).failures).toEqual([]); + const before = stagedPaths(root); + expect(before).not.toContain(OWNERSHIP_DESTINATION); + expect(verifyStagedInstall(root).failures.join('\n')).toContain( + `Required file is missing from staged tree: ${OWNERSHIP_DESTINATION}`, + ); + + expect(verifyStagedInstall(root, { expectedRevision: revision, stageOwnership: true }).failures).toEqual([]); + expect(stagedPaths(root)).toEqual([...before, OWNERSHIP_DESTINATION].sort()); + expect(git(root, 'show', `:${OWNERSHIP_DESTINATION}`)) + .toBe(readFileSync(join(root, OWNERSHIP_DESTINATION), 'utf8')); + for (const path of [ + '.github/aw/packages/unrelated.json', 'packages/unrelated.txt', + '.github/aw/logs/private.log', '.github/skills/secret.local', + ]) expect(stagedPaths(root)).not.toContain(path); + expect(verifyStagedInstall(root, { stageOwnership: true }).failures).toEqual([]); + + git(root, '-c', 'user.name=Test', '-c', 'user.email=test@example.invalid', + '-c', 'core.hooksPath=/dev/null', 'commit', '--quiet', '-m', 'fixture install'); + expect(git(root, 'diff', '--cached', '--name-only')).toBe(''); + expect(verifyStagedInstall(root).failures).toEqual([]); + }); + + it('runs the shipped CLI gate successfully and returns nonzero when metadata is missing', () => { + const root = consumer(); + const args = [verifier, '--verify-staged-install', '--stage-ownership', '--source-revision', revision]; + expect(spawnSync(process.execPath, args, { cwd: root }).status).toBe(0); + rmSync(join(root, OWNERSHIP_DESTINATION)); + const failed = spawnSync(process.execPath, args, { cwd: root, encoding: 'utf8' }); + expect(failed.status).toBe(1); + expect(failed.stderr).toContain('do not commit/push'); + expect(failed.stderr).toMatch(/ownership record|ownership metadata/); + }); + + it('fails clearly when Git cannot force-stage the required ignored metadata', () => { + const root = consumer(); + write(root, '.git/index.lock', 'locked\n'); + const result = verifyStagedInstall(root, { stageOwnership: true }); + expect(result.failures.join('\n')).toMatch(/git failed:[\s\S]*index\.lock/); + expect(stagedPaths(root)).not.toContain(OWNERSHIP_DESTINATION); + }); + + it('fails closed when the verified index cannot be snapshotted', () => { + const root = consumer(); + expect(verifyStagedInstall(root, { stageOwnership: true }).failures).toEqual([]); + write(root, '.git/index.lock', 'locked\n'); + expect(verifyStagedInstall(root).failures.join('\n')).toMatch(/git failed:[\s\S]*index\.lock/); + }); + + it.each([ + CONTRACT_DESTINATION, + '.github/workflows/squad.md', + '.github/workflows/squad.lock.yml', + '.github/workflows/shared/squad.md', + '.github/aw/squad/runtime/shared/squad.md', + '.github/skills/gh-aw-enlistment/SKILL.md', + ])('rejects an omitted required artifact without force-adding it: %s', (path) => { + const root = consumer(); + git(root, 'update-index', '--force-remove', '--', path); + write(root, '.gitignore', `packages/\nlogs/\n*.local\n/${path}\n`); + const result = verifyStagedInstall(root, { stageOwnership: true }); + expect(result.failures.join('\n')).toContain(`Required file is missing from staged tree: ${path}`); + expect(stagedPaths(root)).not.toContain(path); + }); + + it.each([OWNERSHIP_DESTINATION, '.github/workflows/squad.lock.yml'])( + 'rejects stale staged bytes even when working-tree verification succeeds: %s', (path) => { + const root = consumer(); + expect(verifyStagedInstall(root, { stageOwnership: true }).failures).toEqual([]); + const original = readFileSync(join(root, path)); + write(root, path, 'stale staged contents\n'); + git(root, 'add', '--force', '--', path); + write(root, path, original); + expect(verifyInstall(root).failures).toEqual([]); + expect(verifyStagedInstall(root, { stageOwnership: true }).failures.join('\n')) + .toContain(`Staged digest mismatch for ${path}`); + }, + ); + + it('rejects a symlink staged in place of a required regular file', () => { + const root = consumer(); + expect(verifyStagedInstall(root, { stageOwnership: true }).failures).toEqual([]); + const path = '.github/workflows/squad.md'; + const object = git(root, 'rev-parse', `:${path}`).trim(); + git(root, 'update-index', '--cacheinfo', `120000,${object},${path}`); + expect(verifyStagedInstall(root).failures.join('\n')) + .toContain(`Required staged path is not a regular file: ${path}`); + }); + + it('rejects a required path replaced by an index directory without reading its descendants', () => { + const root = consumer(); + expect(verifyStagedInstall(root, { stageOwnership: true }).failures).toEqual([]); + const path = '.github/workflows/squad.md'; + const object = git(root, 'rev-parse', `:${path}`).trim(); + git(root, 'update-index', '--force-remove', '--', path); + git(root, 'update-index', '--add', '--cacheinfo', `100644,${object},${path}/unexpected.txt`); + expect(verifyInstall(root).failures).toEqual([]); + expect(verifyStagedInstall(root).failures.join('\n')) + .toContain(`Required staged path is not a regular file: ${path}`); + }); +}); diff --git a/workflows/package/squad-bootstrap.md b/workflows/package/squad-bootstrap.md index 3fdf6b27a..be842e20b 100644 --- a/workflows/package/squad-bootstrap.md +++ b/workflows/package/squad-bootstrap.md @@ -152,7 +152,7 @@ pre-agent-steps: node --check "$path" >/dev/null } # BEGIN GENERATED RESOURCE DIGESTS - check_hash "$install_verifier" "514e210ebeae3b355ff73ed81bfd17f37207c9a3576ca44eaf8af7bfa21fc173" + check_hash "$install_verifier" "40753edd5995c5f7ba058f57fbcc5aff0ccf3341c721b764fa0cf1dc70eba3f1" check_hash "$cast_validator" "0988e04aeef316f4d7a0107c902bbbcf6538b8899b9fffba5f62150717967685" check_hash "$bootstrap_validator" "4fc1bcc79b887bb4f2f78e7eef8693d5c1eaf817d8715ce3c92169934aa225b7" # END GENERATED RESOURCE DIGESTS diff --git a/workflows/shared/squad-install-verifier.mjs b/workflows/shared/squad-install-verifier.mjs index 66d029b8a..63a62f9b0 100644 --- a/workflows/shared/squad-install-verifier.mjs +++ b/workflows/shared/squad-install-verifier.mjs @@ -836,7 +836,7 @@ function strictCompileMatches(root) { } } -function spawnChecked(command, args, cwd) { +function spawnChecked(command, args, cwd, options = {}) { const safeEnvironment = Object.fromEntries( ['CI', 'GH_CONFIG_DIR', 'GH_HOST', 'HOME', 'LANG', 'LC_ALL', 'NO_COLOR', 'PATH', 'SHELL', 'TERM', 'TMPDIR'] .filter((key) => process.env[key] !== undefined) @@ -846,6 +846,7 @@ function spawnChecked(command, args, cwd) { cwd, encoding: 'utf8', env: safeEnvironment, + ...options, }); if (result.error || result.status !== 0) { throw result.error ?? new Error(`${command} failed:\n${result.stdout}${result.stderr}`); @@ -872,6 +873,59 @@ export function verifyInstall(root, { expectedRevision = '', strictCompile = fal return { failures, revision }; } +export function verifyStagedInstall(root, { expectedRevision = '', stageOwnership = false } = {}) { + const result = verifyInstall(root, { expectedRevision }); + if (result.failures.length > 0) return result; + try { + const { contract } = parseInstalledContract(root); + // Only the native package's exact metadata path may bypass consumer ignore rules. + const ownership = readRequired(root, OWNERSHIP_DESTINATION); + if (JSON.parse(ownership).package !== PACKAGE_NAME) { + throw new Error(`Package ownership identity is invalid: ${OWNERSHIP_DESTINATION}`); + } + const required = new Set([ + CONTRACT_DESTINATION, + OWNERSHIP_DESTINATION, + ...contract.workflows.flatMap(entry => [entry.destination, entry.lock]), + ...contract.shared_runtime.flatMap(entry => [entry.package_destination, entry.destination]), + ...contract.skills.map(entry => entry.destination), + ]); + const expected = new Map([...required].map(path => [path, fileDigest(root, path)])); + if (stageOwnership) { + const ignored = spawnChecked('git', [ + 'ls-files', '--others', '--ignored', '--exclude-standard', '-z', '--', OWNERSHIP_DESTINATION, + ], root).stdout.split('\0'); + if (ignored.includes(OWNERSHIP_DESTINATION)) { + spawnChecked('git', ['add', '--force', '--', OWNERSHIP_DESTINATION], root); + } + } + // Snapshot the index, not HEAD or the working tree, including unchanged tracked files. + const tree = spawnChecked('git', ['write-tree'], root).stdout.trim(); + // Exact allowlisted paths bound output; no recursion into unrelated or substituted trees. + const entries = spawnChecked('git', ['ls-tree', '-z', tree, '--', ...required], root).stdout + .split('\0').filter(Boolean); + const staged = new Map(entries.map(entry => { + const tab = entry.indexOf('\t'); + return [entry.slice(tab + 1), entry.slice(0, tab).split(' ')]; + })); + for (const [path, digest] of expected) { + const entry = staged.get(path); + if (!entry) throw new Error(`Required file is missing from staged tree: ${path}`); + const [mode, type, object] = entry; + if (type !== 'blob' || !['100644', '100755'].includes(mode)) { + throw new Error(`Required staged path is not a regular file: ${path}`); + } + const bytes = spawnChecked('git', ['cat-file', 'blob', object], root, { encoding: null }).stdout; + if (sha256(bytes) !== digest) { + throw new Error(`Staged digest mismatch for ${path}; stage the verified file before commit/push.`); + } + } + } catch (error) { + result.failures.push(error instanceof Error ? error.message : String(error)); + } + return result; +} + export function verifyCanonicalManifest(sourceRoot, installedRoot) { const canonical = readRequired(sourceRoot, CONTRACT_SOURCE); const parsed = JSON.parse(canonical); @@ -999,11 +1053,24 @@ export function run(argv = process.argv.slice(2), cwd = process.cwd()) { console.log(`Squad gh-aw installation verified at ${result.revision}.`); return 0; } + if (argv.includes('--verify-staged-install')) { + const result = verifyStagedInstall(root, { + expectedRevision: optionValue(argv, '--source-revision'), + stageOwnership: argv.includes('--stage-ownership'), + }); + if (result.failures.length > 0) { + console.error('STOP: Squad required installation files could not be staged and verified; do not commit/push:'); + for (const failure of result.failures) console.error(`- ${failure}`); + return 1; + } + console.log(`Squad gh-aw staged installation verified at ${result.revision}.`); + return 0; + } } catch (error) { console.error(error instanceof Error ? error.message : String(error)); return 1; } - console.error('Usage: squad-install-verifier.mjs --check-source|--write-source|--materialize-runtime|--verify-install|--verify-resource '); + console.error('Usage: squad-install-verifier.mjs --check-source|--write-source|--materialize-runtime|--verify-install|--verify-staged-install [--stage-ownership]|--verify-resource '); return 2; } diff --git a/workflows/skills/gh-aw-enlistment/SKILL.md b/workflows/skills/gh-aw-enlistment/SKILL.md index c704a9df6..016f77f15 100644 --- a/workflows/skills/gh-aw-enlistment/SKILL.md +++ b/workflows/skills/gh-aw-enlistment/SKILL.md @@ -156,11 +156,12 @@ and installs exactly eight workflows, seventeen runtime resources, and one - `squad-retro.md` + `squad-retro.lock.yml` - `squad-improvement-worker.md` + `squad-improvement-worker.lock.yml` - `squad-bootstrap.md` + `squad-bootstrap.lock.yml` +- `squad-command-router.md` + `squad-command-router.lock.yml` `squad-improvement-worker` is part of the standard, coherent install above — not a separate opt-in add-on. It stays dormant until a maintainer approves a governance-scoped retrospective proposal (see the gh-aw guide's retrospective -auto-implementation section); installing it alongside the other six keeps the +auto-implementation section); installing it alongside the other seven keeps the full stack consistent and avoids a second bootstrap pass later. Report/proposal-only is the default. Ordinary fixes require the explicit @@ -258,6 +259,8 @@ Stage **only** the documented generated surfaces, then verify the staged set: ```bash git add -- .gitattributes .github/aw/ .github/workflows/ .github/skills/ +node .github/workflows/shared/squad-install-verifier.mjs \ + --verify-staged-install --stage-ownership --source-revision "${SQUAD_SHA}" || exit 1 git diff --cached --stat # No deletions should be staged: test -z "$(git diff --cached --diff-filter=D --name-only)" || { echo "STOP: staged deletions"; exit 1; } @@ -267,6 +270,15 @@ test -z "$(git diff --cached --diff-filter=D --name-only)" || { echo "STOP: stag edits to **unrelated files**, or committed **log/diagnostic output**. Re-scope with explicit `git add -- ` — never `git add .`, `git add -A`, or `git commit -a`. +Consumer rules such as `packages/` can silently ignore the required ownership +JSON under `.github/aw/packages/`. The staged verifier force-adds only the exact +native package ownership JSON when it is ignored and untracked. It then verifies +every manifest-required source, lock, runtime, skill, manifest and ownership +file against the validated working-tree bytes in a snapshot of the Git index. +**STOP before commit/push** on missing metadata, a staging failure, an omitted +required file or a staged digest mismatch. Never force-add a directory or glob. +Rerun this gate after changing or restaging any installation file. + ### 8. Commit, push, and open the bootstrap PR to the captured default branch ```bash @@ -360,6 +372,8 @@ node .github/workflows/shared/squad-install-verifier.mjs \ --verify-install --source-revision "${SQUAD_SHA}" --strict-compile git add -- .gitattributes .github/aw/ .github/workflows/ .github/skills/ +node .github/workflows/shared/squad-install-verifier.mjs \ + --verify-staged-install --stage-ownership --source-revision "${SQUAD_SHA}" || exit 1 git commit -m "ci: add Squad agentic workflow" git push -u origin HEAD gh pr create --base "${default_branch}" \ diff --git a/workflows/squad-bootstrap.md b/workflows/squad-bootstrap.md index abc69c98a..e954c84f2 100644 --- a/workflows/squad-bootstrap.md +++ b/workflows/squad-bootstrap.md @@ -159,7 +159,7 @@ pre-agent-steps: node --check "$path" >/dev/null } # BEGIN GENERATED RESOURCE DIGESTS - check_hash "$install_verifier" "514e210ebeae3b355ff73ed81bfd17f37207c9a3576ca44eaf8af7bfa21fc173" + check_hash "$install_verifier" "40753edd5995c5f7ba058f57fbcc5aff0ccf3341c721b764fa0cf1dc70eba3f1" check_hash "$cast_validator" "0988e04aeef316f4d7a0107c902bbbcf6538b8899b9fffba5f62150717967685" check_hash "$bootstrap_validator" "4fc1bcc79b887bb4f2f78e7eef8693d5c1eaf817d8715ce3c92169934aa225b7" # END GENERATED RESOURCE DIGESTS diff --git a/workflows/squad-workflows.manifest.json b/workflows/squad-workflows.manifest.json index 62a4c2b7a..1a8161d93 100644 --- a/workflows/squad-workflows.manifest.json +++ b/workflows/squad-workflows.manifest.json @@ -68,9 +68,9 @@ "source": "workflows/package/squad-bootstrap.md", "destination": ".github/workflows/squad-bootstrap.md", "lock": ".github/workflows/squad-bootstrap.lock.yml", - "source_sha256": "288a551bceb8f396c2653ef2cd2a24fec30172500f0f271cddec29115563b5af", - "lock_sha256": "54e72227f9071157259893efe561378b8476b9e6cec0b67536639f0ddf804923", - "package_lock_sha256": "5c32a151e97ece6e2cfeae7672497b1344a41defc81b5dff475a00cf6d0dfdd8" + "source_sha256": "56cfa51bfe122ec17b2af0ad3361b58b71d29010db55caf5ac15330df080c705", + "lock_sha256": "7df45f7f65d5716e4f2412a48a9a57c65a2fc27470fddc0b18439c480c102de0", + "package_lock_sha256": "455c079fcd1ee6314907614687ee73860daffcd66e95f04f198cbe571d377547" }, { "name": "squad-command-router", @@ -97,7 +97,7 @@ "package_destination": ".github/workflows/shared/squad-install-verifier.mjs", "destination": ".github/workflows/shared/squad-install-verifier.mjs", "ownership": "manifest", - "sha256": "514e210ebeae3b355ff73ed81bfd17f37207c9a3576ca44eaf8af7bfa21fc173" + "sha256": "40753edd5995c5f7ba058f57fbcc5aff0ccf3341c721b764fa0cf1dc70eba3f1" }, { "path": "shared/squad-command-contract.mjs", @@ -225,7 +225,7 @@ "name": "gh-aw-enlistment", "source": "workflows/skills/gh-aw-enlistment/SKILL.md", "destination": ".github/skills/gh-aw-enlistment/SKILL.md", - "sha256": "0ac1979ab70bce2b5aa428ce9319a91a664ba8bca3a9b8ca1dbca277cec87d99" + "sha256": "16f32f86b0a468891819e2b669bf834db60293cc1b5e0b492c75db146c937bc9" } ], "bootstrap": {