diff --git a/.github/agents/squad.agent.md b/.github/agents/squad.agent.md index 4ae46d229..adbda0ee8 100644 --- a/.github/agents/squad.agent.md +++ b/.github/agents/squad.agent.md @@ -736,7 +736,7 @@ prompt: | 3. DECISION INBOX: Merge .squad/decisions/inbox/ → decisions.md, delete inbox files. Deduplicate. 4. CROSS-AGENT: Append team updates to affected agents' history.md. 5. DECISIONS ARCHIVE: If decisions.md exceeds ~20KB, archive entries older than 30 days to decisions-archive.md. - 6. GIT COMMIT: git add .squad/ && commit (write msg to temp file, use -F). Skip if nothing staged. + 6. GIT COMMIT: Stage with `git add .squad/`, then unstage runtime state that must not reach protected branches: `git reset HEAD -- .squad/orchestration-log/ .squad/log/ .squad/decisions/inbox/ .squad/sessions/ 2>/dev/null`. Commit remaining staged changes (write msg to temp file, use -F). Skip if nothing staged after reset. 7. HISTORY SUMMARIZATION: If any history.md >12KB, summarize old entries to ## Core Context. Never speak to user. ⚠️ End with plain text summary after all tool calls. diff --git a/.gitignore b/.gitignore index a0bf5b84a..9db759386 100644 --- a/.gitignore +++ b/.gitignore @@ -5,9 +5,11 @@ dist/ coverage/ .vscode/ *.log -# Squad: ignore generated logs +# Squad: ignore runtime state (logs, inbox, sessions) .squad/orchestration-log/ .squad/log/ +.squad/decisions/inbox/ +.squad/sessions/ .squad/config.json .test-cli-* # Docs site generated files diff --git a/.squad/agents/fenster/history.md b/.squad/agents/fenster/history.md index 0138c2836..02adfaac7 100644 --- a/.squad/agents/fenster/history.md +++ b/.squad/agents/fenster/history.md @@ -695,4 +695,19 @@ px tsx), it logs a test-mode warning but still validates config — useful for d - For TypeScript Azure Functions: always add a `build` step (`tsc`) and ensure `main` points to `dist/` output (e.g., `"main": "dist/functions/squad-prompt.js"`). The `start` script should chain build + func start: `npm run build && func start`. - A `prestart:func` npm script ensures `func start` always gets fresh compiled JS, preventing stale-build confusion. -📌 Team update (2026-03-06): Fixed Azure Function sample — added missing `main` field to package.json, updated `start` script to build-then-run, added build documentation to README. Root cause was runtime couldn't discover function registration without `main` pointing to compiled output. — decided by Fenster +📌 Team update (2026-03-06): Fixed Azure Function sample — added missing `main` field to package.json, updated `start` script to build-then-run, added build documentation to README. Root cause was runtime couldn't discover function registration without `main` pointing to compiled output. — decided by Fenster + +## Issue #228 — Squad Guard vs Scribe Runtime State + +**Problem:** Scribe's commit step (`git add .squad/`) stages runtime state files (orchestration-log/, log/, decisions/inbox/, sessions/) on feature branches. When PRs target main, the squad-main-guard workflow catches them — causing CI failures that users didn't cause. + +**Fix (defense in depth):** +1. Updated Scribe commit instruction in both `squad.agent.md` files to `git reset HEAD` on forbidden paths after `git add .squad/` +2. Added `.squad/decisions/inbox/` and `.squad/sessions/` to `.gitignore` entries in `init.ts` (orchestration-log/ and log/ were already covered) +3. Updated init test to verify all four runtime state paths + +## Learnings + +- The squad-main-guard blocks ALL `.squad/` paths from protected branches — this is correct. Runtime state must be prevented at the commit stage, not the guard stage. +- `.gitignore` is first-line defense (prevents staging new files) but doesn't help for already-tracked files. The `git reset HEAD` in the Scribe instruction is the belt-and-suspenders fix. +- Four runtime state paths to always exclude: `orchestration-log/`, `log/`, `decisions/inbox/`, `sessions/` diff --git a/packages/squad-sdk/src/config/init.ts b/packages/squad-sdk/src/config/init.ts index 484bc552e..845f01fc9 100644 --- a/packages/squad-sdk/src/config/init.ts +++ b/packages/squad-sdk/src/config/init.ts @@ -754,13 +754,17 @@ ${projectDescription ? `- **Description:** ${projectDescription}\n` : ''}- **Cre } // ------------------------------------------------------------------------- - // Create .gitignore entries for logs + // Create .gitignore entries for runtime state (logs, inbox, sessions) + // These paths are written during normal squad operation but must never + // reach protected branches — the squad-main-guard workflow rejects them. // ------------------------------------------------------------------------- const gitignorePath = join(teamRoot, '.gitignore'); const ignoreEntries = [ '.squad/orchestration-log/', '.squad/log/', + '.squad/decisions/inbox/', + '.squad/sessions/', ]; let existingIgnore = ''; @@ -771,7 +775,7 @@ ${projectDescription ? `- **Description:** ${projectDescription}\n` : ''}- **Cre const missingIgnore = ignoreEntries.filter(entry => !existingIgnore.includes(entry)); if (missingIgnore.length > 0) { const block = (existingIgnore && !existingIgnore.endsWith('\n') ? '\n' : '') - + '# Squad: ignore generated logs\n' + + '# Squad: ignore runtime state (logs, inbox, sessions)\n' + missingIgnore.join('\n') + '\n'; await appendFile(gitignorePath, block); createdFiles.push(toRelativePath(gitignorePath)); diff --git a/templates/squad.agent.md b/templates/squad.agent.md index 4b0f0189c..0581ef820 100644 --- a/templates/squad.agent.md +++ b/templates/squad.agent.md @@ -735,7 +735,7 @@ prompt: | 3. DECISION INBOX: Merge .squad/decisions/inbox/ → decisions.md, delete inbox files. Deduplicate. 4. CROSS-AGENT: Append team updates to affected agents' history.md. 5. DECISIONS ARCHIVE: If decisions.md exceeds ~20KB, archive entries older than 30 days to decisions-archive.md. - 6. GIT COMMIT: git add .squad/ && commit (write msg to temp file, use -F). Skip if nothing staged. + 6. GIT COMMIT: Stage with `git add .squad/`, then unstage runtime state that must not reach protected branches: `git reset HEAD -- .squad/orchestration-log/ .squad/log/ .squad/decisions/inbox/ .squad/sessions/ 2>/dev/null`. Commit remaining staged changes (write msg to temp file, use -F). Skip if nothing staged after reset. 7. HISTORY SUMMARIZATION: If any history.md >12KB, summarize old entries to ## Core Context. Never speak to user. ⚠️ End with plain text summary after all tool calls. diff --git a/test/cli/init.test.ts b/test/cli/init.test.ts index 646e95e74..9f47e1fb8 100644 --- a/test/cli/init.test.ts +++ b/test/cli/init.test.ts @@ -95,7 +95,7 @@ describe('CLI: init command', () => { expect(content).toContain('.squad/orchestration-log/** merge=union'); }); - it('should append to .gitignore with log exclusions', async () => { + it('should append to .gitignore with runtime state exclusions', async () => { await runInit(TEST_ROOT); const gitignorePath = join(TEST_ROOT, '.gitignore'); @@ -104,6 +104,8 @@ describe('CLI: init command', () => { const content = await readFile(gitignorePath, 'utf-8'); expect(content).toContain('.squad/orchestration-log/'); expect(content).toContain('.squad/log/'); + expect(content).toContain('.squad/decisions/inbox/'); + expect(content).toContain('.squad/sessions/'); }); it('should copy templates to .squad/templates/', async () => {