Skip to content

fix(sdk,cli): resolve linked team state for both teamRoot forms - #2119

Merged
tamirdresher merged 5 commits into
github:devfrom
cherie-q:users/cherieq/squad-2107-team-root
Oct 2, 2026
Merged

tamirdresher merged 5 commits into
github:devfrom
cherie-q:users/cherieq/squad-2107-team-root

Conversation

@cherie-q

@cherie-q cherie-q commented Sep 30, 2026 •

Copy link
Copy Markdown
Contributor

What

Linked teams now work with both forms of teamRoot in .squad/config.json:

  • the team repo ("../team"), which is what squad link writes
  • the team's .squad/ directory ("../team/.squad"), which is what the docs show

Before this change, each form broke different commands. With the docs form, squad loop, watch and cast reported "No squad found" or found no agents. With the squad link form, the state MCP server's squad_decide wrote decisions to <team>/decisions/inbox/, outside .squad/, where Scribe never reads them.

Why

Closes #2107

resolveSquadPaths() returned teamDir exactly as configured, and each caller guessed which form it held:

  • effectiveSquadDir() (loop/watch) and cast joined .squad onto it, so they assumed the team-repo form.
  • state-mcp used it as the squad dir directly, so it assumed the .squad form.

The coordinator (squad.agent.md) already accepts both forms.

How

  1. One resolver in the SDK. ResolvedSquadPaths has a new field, teamSquadDir. It holds the team's squad dir for either form:
    • If {teamDir}/.squad (or legacy .ai-team) is a directory, use it. The project's own dir name is probed first.
    • Else, if teamDir is named .squad/.ai-team or holds team.md, use teamDir.
    • Else, use {teamDir}/.squad, so a missing team still reports "not found".
    • In local mode it equals projectDir.
  2. Callers use it. effectiveSquadDir, cast (which now passes an explicit agents dir) and state-mcp read teamSquadDir. resolveSquadState() roots local-backend storage there too. teamDir is unchanged for API compatibility, and its JSDoc now says not to join team-state paths onto it.
  3. Existing squad link users. Files that state-mcp already wrote into the team repo root are no longer read. The SDK now exports MUTABLE_STATE_PATHS and isMutableStateKey from @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/ and identity/. squad doctor lists each stray .md/.json file 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 an agents/README.md or log/development.log) do not trigger the warning. squad_skill wrote 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/.
  4. Docs. The FAQ entry for linked teams now says both forms work.

Tests. Each new test's failure message names the teamRoot input.

  • test/dual-root-resolver.test.ts: both forms, legacy .ai-team, a team repo holding both .squad and .ai-team (checked from a .squad project and from an .ai-team project), a team dir holding only team.md, a missing team, and local mode.
  • test/effective-squad-dir.test.ts: the docs form (the squad loop repro).
  • test/cli/cast.test.ts: the docs form.
  • test/cli/state-mcp.test.ts: squad_decide with 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 .squad form, an empty inbox, log/development.log, and unrelated agents//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: isMutableStateKey accepts the 9 paths and rejects 15 other keys; squad_state_write still rejects casting/registry.json.
  • test/ensure-squad-path-dual.test.ts and test/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:

FAIL test/cli/cast.test.ts > discovers agents in remote mode when teamRoot names the team's .squad dir (#2107)
FAIL test/cli/doctor.test.ts > linked team state check: 'team repo with stray agent history' (#2107)
FAIL test/cli/doctor.test.ts > linked team state check: 'team repo with stray decisions' (#2107)
FAIL test/cli/state-mcp.test.ts > writes linked-team decisions inside the team's .squad dir when teamRoot names the team repo (#2107)
FAIL test/dual-root-resolver.test.ts > resolves teamSquadDir when teamRoot is the 'team repo (what `squad link` writes)'
FAIL test/dual-root-resolver.test.ts > resolves teamSquadDir when teamRoot is the 'team's .squad dir (what the docs show)'
FAIL test/dual-root-resolver.test.ts > resolves teamSquadDir when teamRoot is the 'team repo with a legacy .ai-team dir'
FAIL test/dual-root-resolver.test.ts > resolves teamSquadDir when teamRoot is the 'team repo that has not been created yet'
FAIL test/dual-root-resolver.test.ts > resolves teamSquadDir when teamRoot is the 'team repo holding both .squad and .ai-team' (x2)
FAIL test/dual-root-resolver.test.ts > sets teamSquadDir to projectDir in local mode
FAIL test/dual-root-resolver.test.ts > treats a teamRoot dir that holds team.md as the team squad dir
FAIL test/effective-squad-dir.test.ts > resolves linked team state when teamRoot names the team's .squad dir (#2107)
Tests  13 failed | 4 passed | 79 skipped (96)

The 4 that pass on dev are doctor "no warning" cases, and dev has no such check. I also ran two mutation checks:

  • resolveTeamSquadDir() forced to always return {teamDir}/.squad: 5 tests fail.
  • The probe order made fixed (.squad first): the .ai-team project 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 listSync throws EACCES on <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() threw EACCES, and the message did not contain same 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.ts and tools/index.ts put back, 35 of the new doctor and isMutableStateKey tests fail (for example, identity/now.md gives no warning). With the fix, all 177 tests in the 7 related files pass.

Out of scope, for follow-up issues:


⚠️ Quick Check

  • If SDK/CLI source files changed: completed the applicable Changeset step below (npx changeset add / .changeset/*.md, direct CHANGELOG.md entry for maintainers, or skip-changelog label for no user-facing changes)

PR Readiness Checklist

The PR readiness bot will validate these automatically after push.
Check each item before requesting review. See CONTRIBUTING.md for full details.

Branch & Commit

  • Branch created from dev (not main)
  • Branch is up to date with dev (git fetch upstream && git rebase upstream/dev)
  • Verified diff contains only intended changes (git diff --cached --stat)
  • PR is not in draft mode (mark ready when checks pass)
  • Commit history is clean (squash fixups before review)

Build & Test

  • npm run build passes
  • npm test passes (all tests green)
  • npm run lint passes (type check clean)
  • npm run lint:eslint passes
  • For migration PRs (>20 files): include test output summary in PR description

Validation, all run locally on Windows 11 / Node v24.13.0:

Command Result
SKIP_BUILD_BUMP=1 npm run build ✅ passed
npm run lint ✅ passed
npm run lint:eslint ✅ passed (0 errors; warnings already present on dev)
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.ts ✅ 177/177 passed
npx vitest run test/state-backend.test.ts ⚠️ 147/148. The changed test passes. deleteDirSync 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) Not run. The full suite already has Windows-only failures on dev (see #2114). CI on ubuntu is the gate.

Changeset

  • Changeset added via npx changeset add (if packages/squad-sdk/src/ or packages/squad-cli/src/ changed)
  • Or direct CHANGELOG.md entry (maintainers only — write-protected for external contributors)
  • Or skip-changelog label applied (if no user-facing changes)

.changeset/2107-linked-team-root-forms.md: @bradygaster/squad-sdk minor, @bradygaster/squad-cli patch.

Docs

  • README section updated (if new feature/module): N/A, bug fix
  • Docs feature page (if new user-facing capability): guide/faq.md linked-team entry now says both teamRoot forms work

Exports

  • package.json subpath exports updated (if new module): N/A, no new modules

Breaking Changes

  • SDK type: ResolvedSquadPaths.teamSquadDir is a new required field. Code that only reads ResolvedSquadPaths is not affected. Code that builds the object itself (mocks, adapters) must now set it. That is why the SDK changeset is minor.
  • State location for squad link projects: the state MCP server now writes team state under <team>/.squad/ instead of <team>/. Files already at the old location are not moved. squad doctor reports them, and the changeset tells users to move them.

Waivers

None requested. npm test is unchecked because some tests already fail on Windows before this change. CI on ubuntu is the gate.

Copilot AI balanced review requested due to automatic review settings September 30, 2026 16:35
@github-actions

Copy link
Copy Markdown
Contributor

🏗️ Architectural Review

Authoritative evidence: base-controlled pull_request_target reporter.

⚠️ Architectural review: 1 warning(s).

Severity Category Finding Files
🟡 warning bootstrap-area 1 file(s) in the bootstrap area (packages/squad-cli/src/cli/core/) were modified. These files must maintain zero external dependencies. Review carefully. packages/squad-cli/src/cli/core/effective-squad-dir.ts

Automated architectural review — informational only.

@github-actions

github-actions Bot commented Sep 30, 2026 •

Copy link
Copy Markdown
Contributor

🛫 PR Readiness Check

ℹ️ This comment updates on each push. Last checked: commit 2a64a9a

PR Scope: 📦🔧 Mixed (product + infrastructure)

⚠️ 3 item(s) to address before review

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.

@github-actions

github-actions Bot commented Sep 30, 2026 •

Copy link
Copy Markdown
Contributor

🟠 Impact Analysis — PR #2119

Risk tier: 🟠 HIGH

📊 Summary

Metric Count
Files changed 16
Files added 1
Files modified 15
Files deleted 0
Modules touched 5
Critical files 1

🎯 Risk Factors

  • 16 files changed (6-20 → MEDIUM)
  • 5 modules touched (5-8 → HIGH)
  • Critical files touched: packages/squad-sdk/src/tools/index.ts

📦 Modules Affected

docs (1 file)
  • docs/src/content/docs/guide/faq.md
root (1 file)
  • .changeset/2107-linked-team-root-forms.md
squad-cli (4 files)
  • packages/squad-cli/src/cli/commands/cast.ts
  • packages/squad-cli/src/cli/commands/doctor.ts
  • packages/squad-cli/src/cli/commands/state-mcp.ts
  • packages/squad-cli/src/cli/core/effective-squad-dir.ts
squad-sdk (2 files)
  • packages/squad-sdk/src/resolution.ts
  • packages/squad-sdk/src/tools/index.ts
tests (8 files)
  • test/cli/cast.test.ts
  • test/cli/doctor.test.ts
  • test/cli/state-mcp.test.ts
  • test/dual-root-resolver.test.ts
  • test/effective-squad-dir.test.ts
  • test/ensure-squad-path-dual.test.ts
  • test/state-backend.test.ts
  • test/tools.test.ts

⚠️ Critical Files

  • packages/squad-sdk/src/tools/index.ts

This report is generated automatically for every PR. See #733 for details.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 Medium severity · 1 Low severity

Open (2)
What changed in this PR

Centralizes linked-team path resolution so both supported teamRoot forms consistently locate team state.

Changes:

  • Adds teamSquadDir to 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.

@cherie-q
cherie-q force-pushed the users/cherieq/squad-2107-team-root branch from 28f2ea5 to 839efe6 Compare September 30, 2026 17:53
@tamirdresher

Copy link
Copy Markdown
Collaborator

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>
@cherie-q

cherie-q commented Oct 1, 2026

Copy link
Copy Markdown
Contributor Author

@tamirdresher Thanks, fixed in a231e2f (squashed and rebased onto dev).

  • One source for the state paths. The SDK now exports MUTABLE_STATE_PATHS and isMutableStateKey from @bradygaster/squad-sdk/tools. The state tools and doctor use the same list, so doctor cannot drift from what the tools can write. The 9 paths are decisions.md, decisions/inbox/, casting/policy.json, agents/*/history.md, log/, orchestration-log/, sessions/, .scratch/ and identity/.
  • Move or merge, never overwrite. For a linked team repo, doctor lists each stray .md/.json file at one of those paths under the team repo root. If the same relative path already exists under .squad/, doctor lists the file as "merge by hand, do not overwrite the newer state". Otherwise it lists it as "move". It reports only and does not move anything. The list shows 10 files, then "and N more".
  • Filter. Doctor flags only .md and .json files, so unrelated files in a team repo (for example log/development.log or an agents/README.md) do not cause a warning.
  • Skills. squad_skill also wrote to .github/skills/ in the folder that contains the team repo, not in the team repo. Doctor does not scan that parent folder. The changeset tells users to move those skills to <team>/.github/skills/.
  • Tests. test/cli/doctor.test.ts has a table with one row for each of the 9 paths (including your identity/now.md case and a merge case), plus rows that must not warn. test/tools.test.ts checks isMutableStateKey on 9 accepted and 15 rejected keys. Against the previous code, 35 of the new tests fail. With the fix, the 7 related test files pass (177/177).

@cherie-q
cherie-q force-pushed the users/cherieq/squad-2107-team-root branch from 839efe6 to a231e2f Compare October 1, 2026 12:42
@tamirdresher
tamirdresher merged commit 059b31e into github:dev Oct 2, 2026
17 checks passed
tamirdresher pushed a commit to ashtewari/squad that referenced this pull request Oct 2, 2026
…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.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Linked-team teamRoot: the FAQ's ../team-repo/.squad form makes squad loop report "No squad found"

4 participants