Repository navigation
fix(sdk,cli): resolve linked team state for both teamRoot forms - #2119
Conversation
🏗️ Architectural Review
Automated architectural review — informational only. |
🛫 PR Readiness Check
PR Scope: 📦🔧 Mixed (product + infrastructure)
|
| Status | Check | Details |
|---|---|---|
| ❌ | Single commit | 5 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 | 0 active Copilot thread(s) resolved (2 outdated skipped) |
| ❌ | CI passing | 6 check(s) still running |
Files Changed (16 files, +417 −36)
| File | +/− |
|---|---|
.changeset/2107-linked-team-root-forms.md |
+10 −0 |
docs/src/content/docs/guide/faq.md |
+2 −0 |
packages/squad-cli/src/cli/commands/cast.ts |
+9 −8 |
packages/squad-cli/src/cli/commands/doctor.ts |
+75 −0 |
packages/squad-cli/src/cli/commands/state-mcp.ts |
+1 −1 |
packages/squad-cli/src/cli/core/effective-squad-dir.ts |
+1 −2 |
packages/squad-sdk/src/resolution.ts |
+36 −3 |
packages/squad-sdk/src/tools/index.ts |
+23 −15 |
test/cli/cast.test.ts |
+27 −1 |
test/cli/doctor.test.ts |
+98 −3 |
test/cli/state-mcp.test.ts |
+27 −1 |
test/dual-root-resolver.test.ts |
+64 −0 |
test/effective-squad-dir.test.ts |
+14 −0 |
test/ensure-squad-path-dual.test.ts |
+2 −0 |
test/state-backend.test.ts |
+1 −1 |
test/tools.test.ts |
+27 −1 |
Total: +417 −36
This check runs automatically on every push. Fix any ❌ items and push again.
See CONTRIBUTING.md and PR Requirements for details.
🟠 Impact Analysis — PR #2119Risk tier: 🟠 HIGH 📊 Summary
🎯 Risk Factors
📦 Modules Affecteddocs (1 file)
root (1 file)
squad-cli (4 files)
squad-sdk (2 files)
tests (8 files)
|
There was a problem hiding this comment.
Copilot review overview
🟡 Changes recommended
The new doctor check can throw on unreadable directories and gives unsafe whole-directory migration guidance.
Review effort: Balanced
Findings: 1
Open (2)
What changed in this PR
Centralizes linked-team path resolution so both supported teamRoot forms consistently locate team state.
Changes:
- Adds
teamSquadDirto SDK path resolution. - Updates CLI consumers and migration diagnostics.
- Adds regression tests, documentation, and a changeset.
| File | Description |
|---|---|
.changeset/2107-linked-team-root-forms.md |
Documents package and migration changes. |
docs/src/content/docs/guide/faq.md |
Documents both teamRoot forms. |
packages/squad-sdk/src/resolution.ts |
Resolves canonical team squad paths. |
packages/squad-cli/src/cli/core/effective-squad-dir.ts |
Uses the resolved team squad directory. |
packages/squad-cli/src/cli/commands/state-mcp.ts |
Roots state tools correctly. |
packages/squad-cli/src/cli/commands/cast.ts |
Discovers linked-team agents correctly. |
packages/squad-cli/src/cli/commands/doctor.ts |
Detects stranded legacy state. |
test/dual-root-resolver.test.ts |
Covers resolver path forms. |
test/effective-squad-dir.test.ts |
Covers effective directory resolution. |
test/ensure-squad-path-dual.test.ts |
Updates typed fixtures. |
test/state-backend.test.ts |
Updates state registry wiring. |
test/cli/state-mcp.test.ts |
Verifies linked decision placement. |
test/cli/cast.test.ts |
Verifies linked agent discovery. |
test/cli/doctor.test.ts |
Covers migration warnings and exclusions. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
28f2ea5 to
839efe6
Compare
|
This looks good overall, but there's one migration case I'd fix before merging. Older versions could write more than the decisions inbox and agent histories into the team repo root. They could also write things like identity/now.md, casting/policy.json and session files there. After this change, the tools look inside .squad instead, but doctor only warns about the old inbox and history files. With an existing team/identity/now.md, doctor gives no migration warning, even though the tools no longer find that file. Nothing gets deleted, but to the user it looks like their state disappeared. Could we have doctor check all the state paths the MCP tools support and add tests for those cases? No need to move anything automatically. Just point out the files that need moving or merging into .squad, without overwriting newer state. |
teamRoot can name the team repo (what `squad link` writes) or the team's .squad/ dir (what the docs show), but callers assumed different forms: effectiveSquadDir and cast appended .squad, while the state MCP server did not. The docs form broke `squad loop`/`watch`/`cast`; the link form made squad_decide write decisions outside .squad/. Add ResolvedSquadPaths.teamSquadDir, which resolves either form to the team's squad dir (legacy .ai-team included), and use it in effectiveSquadDir, cast, state-mcp, and the local-backend storage root. Document on teamDir that callers must not join team-state paths onto it. `squad doctor` now warns when a linked team repo still holds state-mcp output outside .squad/. It checks every path the state tools can write (exported as MUTABLE_STATE_PATHS, with isMutableStateKey) and lists each .md/.json file to move, or to merge by hand when the same file already exists under .squad/. It reports only and does not move files. The SDK changeset is minor because teamSquadDir is a required field on the exported ResolvedSquadPaths type. Closes github#2107 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
|
@tamirdresher Thanks, fixed in a231e2f (squashed and rebased onto
|
839efe6 to
a231e2f
Compare
…b#2119 github#2119 introduced a richer, shared resolveSquadPaths-based resolution utility in effective-squad-dir.ts. github#2057's remote-mode doctor fix already targeted the resulting effectiveSquadDir() combined helper (local + stateDir), so the only real conflict was the import line switching from the module's standalone resolveStateDir export to effectiveSquadDir; the call site and both PRs' new tests merge cleanly side by side with no semantic overlap.


What
Linked teams now work with both forms of
teamRootin.squad/config.json:"../team"), which is whatsquad linkwrites.squad/directory ("../team/.squad"), which is what the docs showBefore this change, each form broke different commands. With the docs form,
squad loop,watchandcastreported "No squad found" or found no agents. With thesquad linkform, the state MCP server'ssquad_decidewrote decisions to<team>/decisions/inbox/, outside.squad/, where Scribe never reads them.Why
Closes #2107
resolveSquadPaths()returnedteamDirexactly as configured, and each caller guessed which form it held:effectiveSquadDir()(loop/watch) andcastjoined.squadonto it, so they assumed the team-repo form.state-mcpused it as the squad dir directly, so it assumed the.squadform.The coordinator (
squad.agent.md) already accepts both forms.How
ResolvedSquadPathshas a new field,teamSquadDir. It holds the team's squad dir for either form:{teamDir}/.squad(or legacy.ai-team) is a directory, use it. The project's own dir name is probed first.teamDiris named.squad/.ai-teamor holdsteam.md, useteamDir.{teamDir}/.squad, so a missing team still reports "not found".projectDir.effectiveSquadDir,cast(which now passes an explicit agents dir) andstate-mcpreadteamSquadDir.resolveSquadState()roots local-backend storage there too.teamDiris unchanged for API compatibility, and its JSDoc now says not to join team-state paths onto it.squad linkusers. Files that state-mcp already wrote into the team repo root are no longer read. The SDK now exportsMUTABLE_STATE_PATHSandisMutableStateKeyfrom@bradygaster/squad-sdk/tools, so the state tools and doctor use one list of 9 paths:decisions.md,decisions/inbox/,casting/policy.json,agents/*/history.md,log/,orchestration-log/,sessions/,.scratch/andidentity/.squad doctorlists each stray.md/.jsonfile at those paths. It marks the file "move" or, if the same relative path already exists under.squad/, "merge by hand, do not overwrite the newer state". It only reports and never moves files. Other files (for example anagents/README.mdorlog/development.log) do not trigger the warning.squad_skillwrote to.github/skills/in the folder that contains the team repo. Doctor does not scan that folder; the changeset tells users to move those skills to<team>/.github/skills/.Tests. Each new test's failure message names the
teamRootinput.test/dual-root-resolver.test.ts: both forms, legacy.ai-team, a team repo holding both.squadand.ai-team(checked from a.squadproject and from an.ai-teamproject), a team dir holding onlyteam.md, a missing team, and local mode.test/effective-squad-dir.test.ts: the docs form (thesquad looprepro).test/cli/cast.test.ts: the docs form.test/cli/state-mcp.test.ts:squad_decidewith the link form writes into<team>/.squad/decisions/inbox/.test/cli/doctor.test.ts: a table with one row for each of the 9 state paths (move, or merge when the file already exists under.squad/). A clean repo, the.squadform, an empty inbox,log/development.log, and unrelatedagents//decisions/files do not warn, and doctor does not move files. 12 stray files show 10 and "and 2 more". If a team folder cannot be listed, doctor does not throw.test/tools.test.ts:isMutableStateKeyaccepts the 9 paths and rejects 15 other keys;squad_state_writestill rejectscasting/registry.json.test/ensure-squad-path-dual.test.tsandtest/state-backend.test.ts: fixtures updated for the new field.Red → green. I put
dev's versions of the 5 changed source files back, rebuilt, and ran the new tests. 13 failed:The 4 that pass on
devare doctor "no warning" cases, anddevhas no such check. I also ran two mutation checks:resolveTeamSquadDir()forced to always return{teamDir}/.squad: 5 tests fail..squadfirst): the.ai-teamproject test fails.With this branch's code, all 97 tests in the 6 files pass.
Review fixes (Copilot comments). I added one doctor test in which
listSyncthrowsEACCESon<team>/agents, and I made the 2 warning rows check the new "move each file" wording. On the previous commit (28f2ea50), those 3 tests failed:runDoctor()threwEACCES, and the message did not containsame relative path. With the fix, they pass.Review fix (all state paths). Doctor now checks every path the state tools can write, not only the inbox and agent histories. With the previous commit's
doctor.tsandtools/index.tsput back, 35 of the new doctor andisMutableStateKeytests fail (for example,identity/now.mdgives no warning). With the fix, all 177 tests in the 7 related files pass.Out of scope, for follow-up issues:
squad doctor'steam.mdcheck does not useteamSquadDir. This issquad doctorreports false failures for a linked (remote team root) project #2056, which still reproduces; PR fix(cli): squad doctor validates state files against resolved team root in remote mode #2057 fixes it.resolveSquadState()ignoresstateLocation: external.projectDir.npx changeset add/.changeset/*.md, directCHANGELOG.mdentry for maintainers, orskip-changeloglabel for no user-facing changes)PR Readiness Checklist
Branch & Commit
dev(notmain)dev(git fetch upstream && git rebase upstream/dev)git diff --cached --stat)Build & Test
npm run buildpassesnpm testpasses (all tests green)npm run lintpasses (type check clean)npm run lint:eslintpassesValidation, all run locally on Windows 11 / Node v24.13.0:
SKIP_BUILD_BUMP=1 npm run buildnpm run lintnpm run lint:eslintdev)npx vitest run test/dual-root-resolver.test.ts test/cli/cast.test.ts test/cli/state-mcp.test.ts test/effective-squad-dir.test.ts test/ensure-squad-path-dual.test.ts test/cli/doctor.test.ts test/tools.test.tsnpx vitest run test/state-backend.test.tsdeleteDirSync removes nested subtrees (git-notes)hits the 5 s timeout, also when run alone. This PR does not touch that code.npx vitest run(full suite)dev(see #2114). CI on ubuntu is the gate.Changeset
npx changeset add(ifpackages/squad-sdk/src/orpackages/squad-cli/src/changed)CHANGELOG.mdentry (maintainers only — write-protected for external contributors)skip-changeloglabel applied (if no user-facing changes).changeset/2107-linked-team-root-forms.md:@bradygaster/squad-sdkminor,@bradygaster/squad-clipatch.Docs
guide/faq.mdlinked-team entry now says bothteamRootforms workExports
Breaking Changes
ResolvedSquadPaths.teamSquadDiris a new required field. Code that only readsResolvedSquadPathsis not affected. Code that builds the object itself (mocks, adapters) must now set it. That is why the SDK changeset isminor.squad linkprojects: the state MCP server now writes team state under<team>/.squad/instead of<team>/. Files already at the old location are not moved.squad doctorreports them, and the changeset tells users to move them.Waivers
None requested.
npm testis unchecked because some tests already fail on Windows before this change. CI on ubuntu is the gate.