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
8 changes: 8 additions & 0 deletions .changeset/gh-aw-runtime-resource-closure.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
"@bradygaster/squad-cli": patch
"@bradygaster/squad-sdk": patch
---

Install every shared runtime guard required by the Squad agentic workflows,
authorize lifecycle repair from live repository permission, and keep the
advisory reviewer free of workflow-dispatch authority.
10 changes: 10 additions & 0 deletions .squad-templates/skills/gh-aw-enlistment/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,13 @@ for workflow in squad squad-implement-worker squad-review squad-deps-worker squa
test -f ".github/workflows/${workflow}.lock.yml" || { echo "MISSING ${workflow}.lock.yml"; exit 1; }
done

for runtime_module in squad-cast-validator squad-improvement-gate squad-retro-evidence squad-retro-provenance; do
test -f ".github/workflows/shared/${runtime_module}.mjs" || {
echo "MISSING shared/${runtime_module}.mjs"
exit 1
}
done

# gh-aw strict compilation can still emit a JSON-escaped operator inside a
# GitHub expression. GitHub rejects that workflow before any job starts.
if grep -nE '\$\{\{[^}]*\\u00(26|3[cCeE])' .github/workflows/*.lock.yml; then
Expand All @@ -205,6 +212,9 @@ fi
- **STOP** and rerun `gh aw compile --strict` if any `.lock.yml` is missing. Do not
open or merge the bootstrap PR until all **twelve** files exist and strict
compilation passes.
- **STOP** if any required `shared/*.mjs` runtime module is missing. The
dispatcher must install the complete transitive resource set; compile success
alone does not prove the worker pre-agent and output guards can execute.
- **STOP** if the escaped-operator scan prints any line. Compile success alone is
insufficient: GitHub rejects these emitted expressions before creating jobs,
so the resulting failed run has no job logs to inspect.
Expand Down
66 changes: 50 additions & 16 deletions docs/src/content/docs/guide/gh-aw.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ Seven steps from zero to a validated, reviewable Squad bootstrap:
> a human reviews and merges it.

```bash
set -euo pipefail

# 1. Verify GitHub access, resolve this repo, and install gh-aw if needed
gh auth status
owner_repo="$(gh repo view --json nameWithOwner --jq '.nameWithOwner')"
Expand Down Expand Up @@ -57,8 +59,16 @@ gh aw compile --strict

# Verify every supported workflow has a source and generated lockfile
for workflow in squad squad-implement-worker squad-review squad-deps-worker squad-retro squad-improvement-worker; do
test -f ".github/workflows/${workflow}.md"
test -f ".github/workflows/${workflow}.lock.yml"
test -f ".github/workflows/${workflow}.md" || { echo "MISSING ${workflow}.md"; exit 1; }
test -f ".github/workflows/${workflow}.lock.yml" || { echo "MISSING ${workflow}.lock.yml"; exit 1; }
done

# Verify every local runtime module referenced by those workflows was installed
for runtime_module in squad-cast-validator squad-improvement-gate squad-retro-evidence squad-retro-provenance; do
test -f ".github/workflows/shared/${runtime_module}.mjs" || {
echo "MISSING shared/${runtime_module}.mjs"
exit 1
}
done
Comment thread
bradygaster marked this conversation as resolved.
Resolved

# Strict compilation validates gh-aw's source contract; also reject JSON-escaped
Expand Down Expand Up @@ -167,6 +177,18 @@ The installed top-level workflow set is:
- `squad-retro.md` and `squad-retro.lock.yml`
- `squad-improvement-worker.md` and `squad-improvement-worker.lock.yml`

The install must also contain these executable runtime resources:

- `shared/squad-cast-validator.mjs`
- `shared/squad-improvement-gate.mjs`
- `shared/squad-retro-evidence.mjs`
- `shared/squad-retro-provenance.mjs`

The dispatcher declares the complete transitive resource set because gh-aw
installs it first, discovers the dependent workflows, and then skips the later
explicit worker entries as duplicates. Without that root declaration, a strict
compile can succeed while the installed workers fail before the agent starts.

`squad-improvement-worker` is part of this standard install, not a separate
add-on — it stays dormant until a maintainer approves a governance-scoped
retrospective proposal (see [Retrospective
Expand Down Expand Up @@ -233,8 +255,15 @@ Verify the complete source/lock surface:

```bash
for workflow in squad squad-implement-worker squad-review squad-deps-worker squad-retro squad-improvement-worker; do
test -f ".github/workflows/${workflow}.md"
test -f ".github/workflows/${workflow}.lock.yml"
test -f ".github/workflows/${workflow}.md" || { echo "MISSING ${workflow}.md"; exit 1; }
test -f ".github/workflows/${workflow}.lock.yml" || { echo "MISSING ${workflow}.lock.yml"; exit 1; }
done

for runtime_module in squad-cast-validator squad-improvement-gate squad-retro-evidence squad-retro-provenance; do
test -f ".github/workflows/shared/${runtime_module}.mjs" || {
echo "MISSING shared/${runtime_module}.mjs"
exit 1
}
done
```

Expand Down Expand Up @@ -1507,18 +1536,23 @@ top-level files. Fetch every Squad shared import and resource at the same SHA:
```bash
mkdir -p .github/workflows/shared

curl --fail --silent --show-error --location \
"https://raw.githubusercontent.com/bradygaster/squad/${SQUAD_SHA}/workflows/shared/squad.md" \
--output .github/workflows/shared/squad.md
curl --fail --silent --show-error --location \
"https://raw.githubusercontent.com/bradygaster/squad/${SQUAD_SHA}/workflows/shared/squad-cast-validator.mjs" \
--output .github/workflows/shared/squad-cast-validator.mjs
curl --fail --silent --show-error --location \
"https://raw.githubusercontent.com/bradygaster/squad/${SQUAD_SHA}/workflows/shared/squad-planning-ontology.md" \
--output .github/workflows/shared/squad-planning-ontology.md
curl --fail --silent --show-error --location \
"https://raw.githubusercontent.com/bradygaster/squad/${SQUAD_SHA}/workflows/shared/squad-planning-policy.md" \
--output .github/workflows/shared/squad-planning-policy.md
for shared_file in \
squad.md \
squad-planning-ontology.md \
squad-planning-policy.md \
squad-cast-validator.mjs \
squad-improvement-gate.mjs \
squad-retro-evidence.mjs \
squad-retro-provenance.mjs \
builtins/scribe-charter.md \
builtins/ralph-charter.md \
builtins/rai-charter.md \
builtins/fact-checker-charter.md; do
mkdir -p ".github/workflows/shared/$(dirname "$shared_file")"
curl --fail --silent --show-error --location \
"https://raw.githubusercontent.com/bradygaster/squad/${SQUAD_SHA}/workflows/shared/${shared_file}" \
--output ".github/workflows/shared/${shared_file}"
done

gh aw compile --strict
```
Expand Down
10 changes: 10 additions & 0 deletions packages/squad-cli/templates/skills/gh-aw-enlistment/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,13 @@ for workflow in squad squad-implement-worker squad-review squad-deps-worker squa
test -f ".github/workflows/${workflow}.lock.yml" || { echo "MISSING ${workflow}.lock.yml"; exit 1; }
done

for runtime_module in squad-cast-validator squad-improvement-gate squad-retro-evidence squad-retro-provenance; do
test -f ".github/workflows/shared/${runtime_module}.mjs" || {
echo "MISSING shared/${runtime_module}.mjs"
exit 1
}
done

# gh-aw strict compilation can still emit a JSON-escaped operator inside a
# GitHub expression. GitHub rejects that workflow before any job starts.
if grep -nE '\$\{\{[^}]*\\u00(26|3[cCeE])' .github/workflows/*.lock.yml; then
Expand All @@ -205,6 +212,9 @@ fi
- **STOP** and rerun `gh aw compile --strict` if any `.lock.yml` is missing. Do not
open or merge the bootstrap PR until all **twelve** files exist and strict
compilation passes.
- **STOP** if any required `shared/*.mjs` runtime module is missing. The
dispatcher must install the complete transitive resource set; compile success
alone does not prove the worker pre-agent and output guards can execute.
- **STOP** if the escaped-operator scan prints any line. Compile success alone is
insufficient: GitHub rejects these emitted expressions before creating jobs,
so the resulting failed run has no job logs to inspect.
Expand Down
10 changes: 10 additions & 0 deletions packages/squad-sdk/templates/skills/gh-aw-enlistment/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,13 @@ for workflow in squad squad-implement-worker squad-review squad-deps-worker squa
test -f ".github/workflows/${workflow}.lock.yml" || { echo "MISSING ${workflow}.lock.yml"; exit 1; }
done

for runtime_module in squad-cast-validator squad-improvement-gate squad-retro-evidence squad-retro-provenance; do
test -f ".github/workflows/shared/${runtime_module}.mjs" || {
echo "MISSING shared/${runtime_module}.mjs"
exit 1
}
done

# gh-aw strict compilation can still emit a JSON-escaped operator inside a
# GitHub expression. GitHub rejects that workflow before any job starts.
if grep -nE '\$\{\{[^}]*\\u00(26|3[cCeE])' .github/workflows/*.lock.yml; then
Expand All @@ -205,6 +212,9 @@ fi
- **STOP** and rerun `gh aw compile --strict` if any `.lock.yml` is missing. Do not
open or merge the bootstrap PR until all **twelve** files exist and strict
compilation passes.
- **STOP** if any required `shared/*.mjs` runtime module is missing. The
dispatcher must install the complete transitive resource set; compile success
alone does not prove the worker pre-agent and output guards can execute.
- **STOP** if the escaped-operator scan prints any line. Compile success alone is
insufficient: GitHub rejects these emitted expressions before creating jobs,
so the resulting failed run has no job logs to inspect.
Expand Down
10 changes: 10 additions & 0 deletions templates/skills/gh-aw-enlistment/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,13 @@ for workflow in squad squad-implement-worker squad-review squad-deps-worker squa
test -f ".github/workflows/${workflow}.lock.yml" || { echo "MISSING ${workflow}.lock.yml"; exit 1; }
done

for runtime_module in squad-cast-validator squad-improvement-gate squad-retro-evidence squad-retro-provenance; do
test -f ".github/workflows/shared/${runtime_module}.mjs" || {
echo "MISSING shared/${runtime_module}.mjs"
exit 1
}
done

# gh-aw strict compilation can still emit a JSON-escaped operator inside a
# GitHub expression. GitHub rejects that workflow before any job starts.
if grep -nE '\$\{\{[^}]*\\u00(26|3[cCeE])' .github/workflows/*.lock.yml; then
Expand All @@ -205,6 +212,9 @@ fi
- **STOP** and rerun `gh aw compile --strict` if any `.lock.yml` is missing. Do not
open or merge the bootstrap PR until all **twelve** files exist and strict
compilation passes.
- **STOP** if any required `shared/*.mjs` runtime module is missing. The
dispatcher must install the complete transitive resource set; compile success
alone does not prove the worker pre-agent and output guards can execute.
- **STOP** if the escaped-operator scan prints any line. Compile success alone is
insufficient: GitHub rejects these emitted expressions before creating jobs,
so the resulting failed run has no job logs to inspect.
Expand Down
27 changes: 27 additions & 0 deletions test/gh-aw-enlistment-skill.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,18 @@ describe('gh-aw-enlistment skill', () => {
expect(content).toContain('all **twelve** files');
});

it('requires every shared runtime guard in the emitted repository', () => {
for (const runtimeModule of [
'squad-cast-validator',
'squad-improvement-gate',
'squad-retro-evidence',
'squad-retro-provenance',
]) {
expect(content).toContain(runtimeModule);
}
expect(content).toContain('if any required `shared/*.mjs` runtime module is missing');
});

it('requires a final strict compile without --approve', () => {
// The standalone command must appear as its own line (start-of-line in a
// fenced bash block), not merely as a prose/backtick mention. The
Expand Down Expand Up @@ -237,6 +249,21 @@ describe('gh-aw-enlistment skill', () => {
'`/squad` slash commands become active only after that merge reaches',
);
});

it('makes public-guide runtime checks fail fast and upgrades every shared guard', () => {
expect(guide).toContain(
'test -f ".github/workflows/shared/${runtime_module}.mjs" || {',
);
for (const runtimeModule of [
'squad-cast-validator.mjs',
'squad-improvement-gate.mjs',
'squad-retro-evidence.mjs',
'squad-retro-provenance.mjs',
]) {
expect(guide).toContain(runtimeModule);
}
expect(guide).toContain('builtins/fact-checker-charter.md');
});
});

describe('template mirror parity', () => {
Expand Down
54 changes: 53 additions & 1 deletion test/gh-aw-lifecycle-upsert.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,9 @@ async function runResearchUpsert(items: unknown[], comments: Comment[] = []) {
async function runLifecycleRepair(
comments: Comment[],
command = '/squad activate',
actorPermission = 'write',
actor = 'maintainer',
permissionError?: Error,
) {
const created: Array<Record<string, unknown>> = [];
const updated: Array<Record<string, unknown>> = [];
Expand All @@ -146,14 +149,23 @@ async function runLifecycleRepair(
const github = {
paginate: async () => comments,
rest: {
repos: {
getCollaboratorPermissionLevel: async () => {
if (permissionError) throw permissionError;
return { data: { permission: actorPermission } };
},
},
issues: {
listComments: () => undefined,
createComment: async (params: Record<string, unknown>) => created.push(params),
updateComment: async (params: Record<string, unknown>) => updated.push(params),
},
},
};
const context = { repo: { owner: 'octodemo', repo: 'consumer' } };
const context = {
repo: { owner: 'octodemo', repo: 'consumer' },
payload: { comment: { user: { login: actor } } },
};
const previousIssue = process.env.ISSUE_NUMBER;
const previousCommand = process.env.SQUAD_COMMAND;
process.env.ISSUE_NUMBER = '5';
Expand Down Expand Up @@ -621,4 +633,44 @@ describe('#1928: deterministic terminal lifecycle repair', () => {
'No trusted whole-plan acceptance artifact; lifecycle repair is not applicable.',
);
});

it.each(['read', 'triage', 'none'])(
'does not write lifecycle state for %s permission',
async permission => {
const result = await runLifecycleRepair([accepted, stale], '/squad activate', permission);

expect(result.failures).toEqual([]);
expect(result.created).toEqual([]);
expect(result.updated).toEqual([]);
expect(result.info).toContain(
`Lifecycle repair is not authorized for maintainer with ${permission} permission.`,
);
},
);

it('fails closed without writes when live permission cannot be verified', async () => {
const result = await runLifecycleRepair(
[accepted, stale],
'/squad activate',
'write',
'maintainer',
new Error('permission endpoint unavailable'),
);

expect(result.created).toEqual([]);
expect(result.updated).toEqual([]);
expect(result.failures).toEqual([
'Unable to verify lifecycle repair permission for maintainer: permission endpoint unavailable',
]);
});

it('fails closed without writes when the comment author is missing', async () => {
const result = await runLifecycleRepair([accepted, stale], '/squad activate', 'write', '');

expect(result.created).toEqual([]);
expect(result.updated).toEqual([]);
expect(result.failures).toEqual([
'Lifecycle repair requires an identifiable comment author.',
]);
});
});
Loading
Loading