feat: Coordinator-as-Agent export — compile .squad/ into .github/agents/squad.md#1180
feat: Coordinator-as-Agent export — compile .squad/ into .github/agents/squad.md#1180bradygaster wants to merge 4 commits into
Conversation
There was a problem hiding this comment.
Pull request overview
Note
Copilot was unable to run its full agentic suite in this review.
Adds a new “Coordinator-as-Agent” export mode that compiles .squad/ state into a repo-native Copilot custom agent at .github/agents/squad.md, including watch/check/dry-run safety features.
Changes:
- Introduces a repo-native export pipeline in
squad-sdk(IR loader, prompt compiler with token budget, YAML frontmatter renderer, file writer, watch mode). - Adds
squad export agentCLI subcommand and routessquad exportbetween snapshot (JSON) and coordinator (agent) exports. - Adds Vitest coverage for the coordinator export pipeline (context → prompt → frontmatter → write/check/legacy collision).
Show a summary per file
| File | Description |
|---|---|
| test/repo-native/coordinator-export.test.ts | New end-to-end-ish tests for loading context, compiling prompts, rendering frontmatter, and writing/checking output. |
| packages/squad-sdk/src/repo-native/load-export-context.ts | Loads .squad/ files into a typed IR, resolves skills/model/description, and summarizes charters. |
| packages/squad-sdk/src/repo-native/compile-coordinator-prompt.ts | Builds coordinator prompt markdown with soft/hard token budgets and compaction modes. |
| packages/squad-sdk/src/repo-native/render-frontmatter.ts | Renders Copilot custom-agent YAML frontmatter from coordinator metadata. |
| packages/squad-sdk/src/repo-native/write-coordinator-agent.ts | Writes/checks the generated agent file with safety and legacy collision handling. |
| packages/squad-sdk/src/repo-native/watch-export.ts | Implements --watch rebuild triggers for .squad/ (and skills) file changes. |
| packages/squad-sdk/src/repo-native/types.ts | Adds repo-native export types and public API contracts. |
| packages/squad-sdk/src/repo-native/index.ts | Exposes the repo-native export API. |
| packages/squad-sdk/src/index.ts | Re-exports the repo-native module from the SDK top-level entry. |
| packages/squad-sdk/package.json | Adds ./repo-native subpath export and updates package version. |
| packages/squad-cli/src/cli/commands/export.ts | Routes squad export to either snapshot export or coordinator agent export. |
| packages/squad-cli/src/cli/commands/export-coordinator.ts | New CLI entry point for generating .github/agents/squad.md (check/watch/dry-run/force/etc.). |
| packages/squad-cli/src/cli-entry.ts | Updates help text and forwards args to the new export router. |
Copilot's findings
- Files reviewed: 13/13 changed files
- Comments generated: 9
| const slug = row.name.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-|-$/g, ''); | ||
| const charterPath = row.charterPath || `.squad/agents/${slug}/charter.md`; | ||
| const charterFullPath = path.join(root, charterPath); | ||
| const charterContent = readIfExists(charterFullPath); |
| lines.push(`name: ${coordinator.displayName}`); | ||
| lines.push(`description: "${escapeYamlString(coordinator.description)}"`); |
| if (coordinator.model) { | ||
| lines.push(`model: ${coordinator.model}`); | ||
| } |
| function renderDispatchRules(context: SquadExportContext): string { | ||
| const lines: string[] = []; | ||
|
|
||
| for (const rule of context.routing.rules) { | ||
| lines.push(`- If the request is mainly about ${rule.workType.toLowerCase()}, route to \`${rule.routeTo.toLowerCase()}\`.`); |
| // Watch top-level squad files | ||
| for (const file of WATCH_PATTERNS) { | ||
| const filePath = path.join(squadRoot, file); | ||
| try { | ||
| const watcher = fs.watch(filePath, { persistent: true }, () => { | ||
| scheduleRebuild(); | ||
| }); | ||
| watchers.push(watcher); | ||
| } catch { | ||
| // File may not exist; that's fine | ||
| } |
| const watcher = fs.watch(agentsDir, { recursive: true, persistent: true }, () => { | ||
| scheduleRebuild(); | ||
| }); |
| if (cleanLegacyAgent) { | ||
| const backupPath = legacyPath + '.bak'; | ||
| fs.renameSync(legacyPath, backupPath); | ||
| result.legacyCleaned = true; | ||
| } |
| // Check mode: compare without writing | ||
| if (check) { | ||
| try { | ||
| const existing = fs.readFileSync(fullOutputPath, 'utf-8'); | ||
| result.driftDetected = existing !== output; | ||
| } catch { | ||
| // File doesn't exist = drift | ||
| result.driftDetected = true; | ||
| } | ||
| return result; | ||
| } |
| * Start watching .squad/ source files for changes. | ||
| * Returns a cleanup function to stop watching. | ||
| */ | ||
| export function startWatchExport(options: WatchExportOptions): () => void { |
- Path traversal: validate charter paths stay within repo root - YAML safety: quote all frontmatter scalar values to prevent injection - Route resolution: resolve routeTo against team members by slug/name - Watch mode: watch directories (not files) for creation/rename detection - Cross-platform: replace recursive fs.watch with manual subdirectory enumeration - Backup safety: use timestamped backup names to prevent data loss - Check mode: distinguish user-owned files from drift with actionable errors - Tests: add coverage for path safety, YAML escaping, watch behavior Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
|
@copilot resolve the merge conflicts in this pull request |
Resolved. I merged |
|
@copilot you should have resolved the conflicts from dev, not from main |
Addressed in |
Extends the existing `squad export` command with a new `agent` branch that compiles .squad/ state into a repository-native Copilot custom agent at .github/agents/squad.md. The export family now supports two modes: - `squad export` / `squad export snapshot` — existing JSON snapshot (unchanged) - `squad export agent` — new coordinator agent export The coordinator export pipeline: 1. Reads .squad/team.md, routing.md, ceremonies.md, config.json, and agent charters 2. Parses into a typed intermediate representation (IR) 3. Compiles a coordinator prompt with token budget management 4. Renders YAML frontmatter + markdown body 5. Writes .github/agents/squad.md with safety checks Features: - Token budget with soft (14k) and hard (20k) limits - Automatic compaction passes for large teams - Lazy-load mode for teams > 8 members - --check mode for CI drift detection - --watch mode for live re-export on file changes - --dry-run for preview without writing - Legacy squad.agent.md collision detection - Deterministic, regenerable output New CLI flags for agent export: --out, --model, --description, --skills, --check, --watch, --dry-run, --force, --clean-legacy-agent, --max-prompt-tokens, --compact Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Path traversal: validate charter paths stay within repo root - YAML safety: quote all frontmatter scalar values to prevent injection - Route resolution: resolve routeTo against team members by slug/name - Watch mode: watch directories (not files) for creation/rename detection - Cross-platform: replace recursive fs.watch with manual subdirectory enumeration - Backup safety: use timestamped backup names to prevent data loss - Check mode: distinguish user-owned files from drift with actionable errors - Tests: add coverage for path safety, YAML escaping, watch behavior Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
54349e4 to
c1bcadf
Compare
- Add changeset for coordinator-as-agent-export (minor for SDK + CLI) - Remove 'coordinator' from user-facing help text (repl-ux-e2e test) - Condense export help to stay within 130-line limit (speed-gates test) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
All code paths that write squad.agent.md now check for an existing exported coordinator (squad.md with generated marker) first. If one exists, squad.agent.md is skipped — the exported file supersedes it. Previously only the export direction was guarded (export renames legacy to .bak). This closes the reverse vectors: init, upgrade, and consult could blindly write squad.agent.md alongside an already-exported squad.md, causing a collision. Vectors fixed: - squad init: skips squad.agent.md when exported coordinator present - squad upgrade: skips refresh when exported coordinator present - squad consult: skips agent file creation when exported coordinator present Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
|
👋 Friendly nudge — this PR has had no activity for 11 days. What needs attention:
If this PR is abandoned, please close it. If it's blocked on something external, leave a comment so the team knows. |
tamirdresher
left a comment
There was a problem hiding this comment.
Review summary
Read this PR end-to-end on a local checkout — architecture, file writer, prompt compiler, tests, end-to-end build. Code quality is strong but there are two concerns that need attention before merge.
✅ What's solid
- Clean architecture. Typed IR (
SquadExportContext) → loader → prompt compiler → frontmatter renderer → file writer. Each stage testable in isolation. - Token budget management. Three-tier degradation works correctly: full → compact → lazy-load → hard-fail with diagnostics. The diagnostics include top-5 largest sections and applied compactions, which is exactly what an author needs when hitting the cap.
- Test coverage. 30 tests pass on the new code (25 for the export pipeline + 5 for agent-collision). All paths exercised: IR parsing, frontmatter, drift detection, collision handling, watch debouncing, large-team lazy-load.
- Build clean.
npm run buildfrom this branch produces no errors, no new warnings. - Generated-file safety.
<!-- generated by squad export: do not edit by hand -->marker + check for it before overwrite is the right pattern — won't clobber user-owned files unless--force. - Collision detection (#1212 followup).
agent-collision.tsis exactly the right shape — checks forsquad.md(exported) vssquad.agent.md(canonical) and prevents both from coexisting silently.
⚠️ Blocker 1 — Branch is stale; would revert recently-merged code
The branch was last updated 2026-05-27 and is now 13 commits behind dev. Three of those merged-after-this-PR commits change the same files this PR touches:
| Change on dev | Files | Risk |
|---|---|---|
| #1251 (memory MCP fix, merged 2026-06-11) | init.ts, upgrade.ts |
Removes ensureSquadStateMcpInUserConfig imports/calls. Without those, agents won't get governed memory tools at user-MCP scope — defeats the whole MCP-fix work. |
| #1262 (@copilot init prompt, merged 2026-06-11) | init.ts |
Removes readTeamMd/writeTeamMd/hasCopilot/insertCopilotSection imports + the @copilot prompt code. UX regression. |
| #1222 (Rai + Fact Checker auto-scaffold, merged earlier) | init.ts |
Removes the fact-checker roster entry that squad upgrade auto-scaffolds. |
If merged as-is via squash or 3-way merge, GitHub's auto-resolver will likely produce a working tree that's missing those imports → build will fail OR ship a regression silently.
Action: rebase onto current dev (or merge dev in), resolve the conflicts (keep the dev side — those PRs are now canonical), re-run tests, force-push.
⚠️ Blocker 2 — --clean-legacy-agent flag renames .github/agents/squad.agent.md
The flag, when invoked, renames .github/agents/squad.agent.md to .bak.{timestamp} (write-coordinator-agent.ts:79-89).
This file is the canonical Coordinator source. It's the authoritative governance file that defines coordinator behavior, ships in the repo as documented at the top of squad.agent.md itself ("Source of Truth Hierarchy"), and is committed to every Squad-enabled repo. Renaming it removes the source of truth for the entire coordinator system.
The intent of the flag — "you exported the new squad.md and you don't need the legacy one anymore" — is reasonable, but the implementation is dangerous:
- The flag treats the legacy file as obsolete, but the new
squad.mdis a compiled artifact of the legacysquad.agent.md(plus team config). The source can't be removed once the artifact exists. - If a user runs
--clean-legacy-agent, then later runssquad upgrade(which re-writessquad.agent.md), they'd hit the collision detection fromagent-collision.tsand be confused why two files now exist. - Cleanly removing the legacy file should be a separate, explicit migration command (e.g.,
squad migrate-to-exported-coordinator) with strong warnings, not a flag on the export command.
Action: Recommend removing --clean-legacy-agent from this PR. Track it as a separate "migration to exported coordinator" concern that needs its own design + warnings + tests.
💡 Minor (non-blocking) observations
-
Doc gap.
squad.mdvssquad.agent.mdwill confuse users. Worth a short doc explaining:squad.agent.mdis the runtime source of truth,squad.mdis an exported compiled artifact for cross-surface compatibility. -
buildMcpServerSpecsintegration question. The exportedsquad.mddoesn't mention MCP tools — but a coordinator on Copilot CLI without the user-MCP setup (#1251) would silently degrade. Should the export include a "you needsquad initfirst" guardrail in the generated agent's frontmatter or preamble? -
renderFrontmatterskill list —--skills baselineis the default but I didn't see what "baseline" resolves to. A quick comment in the docstring would help. -
Watch mode is debounced (good!) but
startWatchExportdoesn't expose a stop mechanism —await new Promise(() => {})keeps the process alive forever. Fine for CLI usage but if SDK consumers wrap this, they'd be stuck. Minor — file an issue for follow-up if needed.
Recommendation
Not approving yet — please rebase to resolve Blocker 1, and either remove --clean-legacy-agent or add explicit guarantees that it cannot touch .github/agents/squad.agent.md.
After both are addressed, the architecture and test coverage are strong enough for a quick re-review and approve.
Summary
Extends the
squad exportcommand family with a new coordinator agent export that compiles.squad/state into a repository-native Copilot custom agent at.github/agents/squad.md.This lets Squad teams export their roster, routing rules, ceremony triggers, and dispatch protocol into a format that works across all Copilot surfaces (CLI, VS Code, GitHub Desktop) without requiring runtime changes.
What's new
CLI
Architecture
The export pipeline:
.squad/team.md,routing.md,ceremonies.md,config.json, and agent charters into a typed IR.github/agents/squad.mdwith safety checks (collision detection, generated-file markers, legacy cleanup)Token budget & compaction
Safety
--forcesquad.agent.mdcollisions--checkmode enables CI enforcement without mutationFiles
New
packages/squad-sdk/src/repo-native/— types, IR loader, prompt compiler, frontmatter renderer, file writer, watch modepackages/squad-cli/src/cli/commands/export-coordinator.ts— CLI entry point for agent exporttest/repo-native/coordinator-export.test.ts— 15 tests covering the full pipelineModified
packages/squad-cli/src/cli/commands/export.ts— forked into a snapshot/agent routerpackages/squad-cli/src/cli-entry.ts— updated help text and arg passingpackages/squad-sdk/src/index.ts— re-exports repo-native modulepackages/squad-sdk/package.json— added./repo-nativesubpath exportTesting
.squad/directory — generates a valid coordinator for a 22-member team