Repository navigation
fix(state): transactional governed-memory migration and source-branch casting ownership (#2229, #2425) - #2439
Merged
tamirdresher merged 3 commits intoNov 9, 2026
Conversation
… casting ownership - Align casting state (casting/*.json) as source-branch-owned across git-notes pre-commit guard, worktree gitignore rules, state MCP tool rejection (CASTING_STATE_SOURCE_OWNED), coordinator templates, and docs (Fixes #2229) - Migrate governed memory (memory/{agent}/{tier}/**) and decision inbox (decisions/inbox/*.md) transactionally with byte-preserving writes, legacy memories/ normalization, destination rollback on failure, and clean source pruning (Fixes #2425) Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Contributor
🟠 Impact Analysis — PR #2439Risk tier: 🟠 HIGH 📊 Summary
🎯 Risk Factors
📦 Modules Affectedci-workflows (1 file)
docs (1 file)
root (4 files)
squad-cli (4 files)
squad-sdk (3 files)
templates (1 file)
tests (6 files)
|
Contributor
🛫 PR Readiness Check
PR Scope: 📦🔧 Mixed (product + infrastructure)
|
| Status | Check | Details |
|---|---|---|
| ❌ | Single commit | 3 commits — consider squashing before review |
| ✅ | Not in draft | Ready for review |
| ✅ | Branch up to date | Up to date with dev |
| ❌ | Copilot review | No Copilot review yet — it may still be processing |
| ✅ | Changeset present | Changeset file found |
| ✅ | Scope clean | No .squad/ or docs/proposals/ files |
| ✅ | No merge conflicts | No merge conflicts |
| ❌ | Copilot threads resolved | 8 unresolved Copilot thread(s) — fix and resolve before merging |
| ✅ | CI passing | All checks passing |
Files Changed (20 files, +504 −169)
| File | +/− |
|---|---|
.changeset/casting-source-branch-ownership.md |
+6 −0 |
.changeset/governed-memory-backend-migration.md |
+5 −0 |
.github/agents/squad.agent.md |
+10 −9 |
.squad-templates/squad.agent.md |
+10 −9 |
CHANGELOG.md |
+6 −0 |
docs/src/content/docs/features/state-backends.md |
+8 −6 |
packages/squad-cli/src/cli/commands/doctor.ts |
+10 −2 |
packages/squad-cli/src/cli/commands/install-hooks.ts |
+16 −11 |
packages/squad-cli/src/cli/commands/migrate-backend.ts |
+137 −81 |
packages/squad-cli/templates/squad.agent.md.template |
+10 −9 |
packages/squad-sdk/src/config/gitignore-state.ts |
+18 −8 |
packages/squad-sdk/src/tools/index.ts |
+1 −2 |
packages/squad-sdk/templates/squad.agent.md.template |
+10 −9 |
templates/squad.agent.md.template |
+10 −9 |
test/cli/state-mcp.test.ts |
+2 −2 |
test/gitignore-state.test.ts |
+32 −0 |
test/install-hooks-wi1.test.ts |
+75 −1 |
test/state-backend.test.ts |
+2 −6 |
test/tools.test.ts |
+2 −2 |
test/upgrade-state-backend.test.ts |
+134 −3 |
Total: +504 −169
This check runs automatically on every push. Fix any ❌ items and push again.
See CONTRIBUTING.md and PR Requirements for details.
tamirdresher
enabled auto-merge (squash)
November 9, 2026 16:41
Contributor
There was a problem hiding this comment.
🟡 Changes recommended
Unresolved migration defects can omit identity files from the state backend and read or delete files outside .squad, while an existing doctor test also fails.
8 open issues
Reject symlinks in every source path component · New Include identity files in state collection · New Update the doctor test for removed casting policy detection · New Disable the managed guard when switching to local backends · New Refresh managed hook sections in place · New Compare destination Git blob IDs instead of buffering large files · New Normalize supported source backends before migration validation · New Reject casting-owned state reads with the promised error · New
What changed in this PR
Updates Squad’s migration and casting ownership contracts for #2229 and #2425, preserving governed memory when moving from filesystem storage to Git-backed state.
Changes:
- Adds byte-preserving memory and decision-inbox imports, destination conflict checks, and config/source safeguards.
- Refreshes hook and ignore rules, while removing casting policy from runtime state mutations.
- Updates coordinator guidance, documentation, release metadata, and regression coverage.
| File | Description |
|---|---|
test/upgrade-state-backend.test.ts |
Covers memory migration, retries, conflicts, and init lifting. |
test/tools.test.ts |
Updates casting mutation allowlist expectations. |
test/state-backend.test.ts |
Checks rejection of casting configuration mutations. |
test/install-hooks-wi1.test.ts |
Covers hook refresh, casting commits, and state protections. |
test/gitignore-state.test.ts |
Covers ignore-block upgrades and malformed markers. |
test/cli/state-mcp.test.ts |
Updates casting-policy rejection expectations. |
templates/squad.agent.md.template |
Documents source-owned casting and runtime-state boundaries. |
packages/squad-sdk/templates/squad.agent.md.template |
Mirrors the updated coordinator contract for SDK consumers. |
packages/squad-sdk/src/tools/index.ts |
Removes casting policy from mutable state keys. |
packages/squad-sdk/src/config/gitignore-state.ts |
Adds memory/identity ignore rules and marker validation. |
packages/squad-cli/templates/squad.agent.md.template |
Mirrors the updated coordinator contract for CLI consumers. |
packages/squad-cli/src/cli/commands/migrate-backend.ts |
Imports governed state before switching authority and removing sources. |
packages/squad-cli/src/cli/commands/install-hooks.ts |
Refreshes managed hook sections and adjusts protected paths. |
docs/src/content/docs/features/state-backends.md |
Explains migration safeguards and casting ownership. |
CHANGELOG.md |
Clarifies stable versus preview fix availability. |
.squad-templates/squad.agent.md |
Updates canonical coordinator ownership guidance. |
.github/agents/squad.agent.md |
Aligns the repository coordinator with the updated guidance. |
.changeset/governed-memory-backend-migration.md |
Records the CLI migration patch. |
.changeset/casting-source-branch-ownership.md |
Records CLI/SDK casting ownership changes. |
Review effort: Max (auto)
Comment on lines
+105
to
+107
| if (fs.lstatSync(full).isSymbolicLink()) { | ||
| throw new Error(`Cannot migrate symbolic link .squad/${relative}; source was retained.`); | ||
| } |
| } | ||
| }; | ||
| collectDirectory('memory'); | ||
| collectDirectory('decisions/inbox'); |
Comment on lines
315
to
316
| { root: 'workstreams', kind: 'dir', pattern: /^workstreams\/active\/[A-Za-z0-9._-]+\/decisions\/inbox\/[^/]+$/ }, | ||
| { root: 'casting/policy.json', kind: 'file' }, | ||
| { root: 'agents', kind: 'dir', pattern: /^agents\/[a-zA-Z0-9_-]+\/history\.md$/ }, |
| # Installed by: squad init / squad upgrade --state-backend (two-layer/orphan) | ||
| if [ -z "$SQUAD_SYNC_ACTIVE" ]; then | ||
| STAGED=$(git diff --cached --name-only 2>/dev/null | grep -E '^\\.squad/(decisions\\.md|agents/.+/history\\.md|casting/|routing/)' || true) | ||
| STAGED=$(git diff --cached --diff-filter=ACMRT --name-only 2>/dev/null | grep -E '^\\.squad/(decisions\\.md$|agents/[^/]+/history\\.md$|memory/|identity/)' || true) |
Comment on lines
+186
to
+192
| // Legacy sections end at the unindented outer `fi`. Remove every | ||
| // previously appended section, preserving user content on either side. | ||
| const cleaned = existing.replace(/^# --- squad-sync-hook ---\r?\n[\s\S]*?^fi(?:\r?\n|$)/gm, '').trimEnd(); | ||
| if (cleaned.includes(SQUAD_HOOK_MARKER)) { | ||
| throw new Error(`Cannot safely replace squad section in ${hookPath}; repair the hook manually.`); | ||
| } | ||
| fs.writeFileSync(hookPath, cleaned + '\n\n' + content.split('\n').slice(1).join('\n'), { mode: 0o755 }); |
Comment on lines
+156
to
+159
| const content = execFileSync('git', ['show', `${parentSha}:${f.relPath}`], { | ||
| cwd: dest, stdio: ['pipe', 'pipe', 'pipe'], | ||
| }); | ||
| if (!content.equals(f.content)) { |
Comment on lines
+231
to
236
| const current = config['stateBackend'] ?? 'local'; | ||
| if (typeof current !== 'string' || !VALID_TARGETS.includes(current)) { | ||
| throw new Error(`Unsupported source backend '${String(current)}'; backend was not switched.`); | ||
| } | ||
|
|
||
| if (current === target) { |
| if (!isMutableStateKey(key)) { | ||
| throw new Error( | ||
| 'State mutations are limited to mutable runtime state (decisions, inbox, casting policy, logs, sessions, scratch files, agent history, and identity). The casting registry/history pair must only be changed through the atomic casting protocol. Static config such as config.json, team.md, routing.md, charters, templates, and skills must not be changed with state tools.', | ||
| 'State mutations are limited to mutable runtime state (decisions, inbox, logs, sessions, scratch files, agent history, and identity). Casting is source-branch configuration; the casting registry/history pair must only be changed through the atomic casting protocol on disk. Static config such as casting policy, config.json, team.md, routing.md, charters, templates, and skills must not be changed with state tools.', |
| # Installed by: squad init / squad upgrade --state-backend (two-layer/orphan) | ||
| if [ -z "$SQUAD_SYNC_ACTIVE" ]; then | ||
| STAGED=$(git diff --cached --name-only 2>/dev/null | grep -E '^\\.squad/(decisions\\.md|agents/.+/history\\.md|casting/|routing/)' || true) | ||
| STAGED=$(git diff --cached --diff-filter=ACMRT --name-only 2>/dev/null | grep -E '^\\.squad/(decisions\\.md$|agents/[^/]+/history\\.md$|memory/|identity/)' || true) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.


Summary
Resolves #2229 and #2425 across the state backend, casting, hook, and governed-memory contracts:
1. Casting state (casting/*.json) is source-branch-owned (Fixes #2229)
outing.md, ceremonies.md, identity/, emplates/, skills/, plugins/).
2. Transactional, byte-preserving governed-memory & decision-inbox backend migration (Fixes #2425)
Validation
Fixes #2229
Fixes #2425