feat(export): v008 quality-first viability gate#1187
Closed
bradygaster wants to merge 1 commit into
Closed
Conversation
Add coordinator-as-agent export with viability pre-flight check: - Viability gate assesses squad complexity before attempting export - Configurable character limit (default 30K) via --char-limit flag - Honest refusal messaging with --force escape hatch - Thresholds scale with configured limit (agents, rules, coverage) - Fix roster parsing: filter category headers and placeholder rows - Dispatch rules distillation for large rule sets (>20 rules) - Decision distillation with progressive compaction passes - Self-containment enforcement (strip external file references) - Protected file guard (squad.agent.md immutable) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Contributor
There was a problem hiding this comment.
Pull request overview
Adds a "v008 quality-first" viability gate to the squad export agent flow: a pre-flight check that refuses to produce a coordinator file when the source squad is too complex to fit usefully into the 30K-char CCA budget. Adds a configurable --char-limit, a --force escape hatch, hardens roster table parsing against category/placeholder rows, and introduces dispatch-rule grouping plus iterative decision distillation in the prompt compiler.
Changes:
- New
viability-checkmodule + CLI wiring that scales agent/rule/coverage thresholds with the configured char limit, and refuses (or warns) accordingly. - Roster parser in
load-export-contextfilters non-member rows (bold-only headers, dash placeholders, N/A). - Prompt compiler groups dispatch rules above 20 entries, caps principles, and progressively re-distills decisions when over budget.
Show a summary per file
| File | Description |
|---|---|
| packages/squad-sdk/src/repo-native/viability-check.ts | New viability gate with scaling thresholds and complexity score. |
| packages/squad-sdk/src/repo-native/load-export-context.ts | Adds filtering for category/placeholder rows in team.md and path-traversal check on charter paths. |
| packages/squad-sdk/src/repo-native/compile-coordinator-prompt.ts | Adds dispatch-rule grouping, progressive decision compaction, character-budget tightening. |
| packages/squad-sdk/src/repo-native/distill-decisions.ts | Algorithmic decision distiller (categorize → prioritize → merge → render compact). |
| packages/squad-sdk/src/repo-native/write-coordinator-agent.ts | File writer with protected-file/legacy/drift handling. |
| packages/squad-sdk/src/repo-native/watch-export.ts | Cross-platform recursive-ish watcher for .squad/ and skills. |
| packages/squad-sdk/src/repo-native/render-frontmatter.ts | YAML frontmatter renderer with quoted scalars. |
| packages/squad-sdk/src/repo-native/types.ts | IR types incl. new charLimit/charHardLimit/charTarget. |
| packages/squad-sdk/src/repo-native/index.ts | Public re-exports for the new module. |
| packages/squad-sdk/src/index.ts | Re-exports new option types from the root entry. |
| packages/squad-sdk/package.json | Adds ./repo-native export; description em-dash replaced with \u2014 escape. |
| packages/squad-cli/src/cli/commands/export-coordinator.ts | New export agent subcommand wiring (parses flags, runs viability gate, writes/watches). |
| packages/squad-cli/src/cli-entry.ts | Dispatches squad export agent to the new subcommand. |
| .changeset/v008-viability-gate.md | Minor bump changeset for SDK + CLI. |
| test/repo-native/viability-check.test.ts | Unit tests for the viability thresholds and --force. |
| test/repo-native/roster-parsing.test.ts | Integration tests for roster filtering of headers/placeholders. |
Copilot's findings
- Files reviewed: 16/16 changed files
- Comments generated: 6
Comment on lines
+228
to
+263
| console.log('Watching .squad/ for changes... (Ctrl+C to stop)'); | ||
| startWatchExport({ | ||
| root: dest, | ||
| squadRoot: squadInfo.path, | ||
| onRebuild: async () => { | ||
| try { | ||
| const { result: r, prompt: p } = await doExport(); | ||
| if (r.written) { | ||
| const ts = new Date().toLocaleTimeString(); | ||
| success(`[${ts}] Re-exported (~${p.estimatedTokens} tokens, mode: ${p.mode})`); | ||
| } | ||
| } catch (err) { | ||
| console.error(`Rebuild failed: ${(err as Error).message}`); | ||
| } | ||
| }, | ||
| }); | ||
|
|
||
| // Keep process alive | ||
| await new Promise(() => {}); | ||
| return; | ||
| } | ||
|
|
||
| // Standard export | ||
| try { | ||
| const { result, prompt } = await doExport(); | ||
|
|
||
| if (result.written) { | ||
| const displayPath = path.relative(dest, result.outputPath) || path.basename(result.outputPath); | ||
| success(`Exported coordinator to ${displayPath}`); | ||
| console.log(` Characters: ${prompt.charCount}/${options.charLimit} (mode: ${prompt.mode})`); | ||
| if (prompt.appliedCompactions.length > 0) { | ||
| console.log(` Compactions: ${prompt.appliedCompactions.join(', ')}`); | ||
| } | ||
| if (viability.complexityScore > 50) { | ||
| console.log(` Complexity: ${viability.complexityScore}/100 — output quality may be reduced`); | ||
| } |
| "name": "@bradygaster/squad-sdk", | ||
| "version": "0.9.6", | ||
| "description": "Squad SDK — Programmable multi-agent runtime for GitHub Copilot", | ||
| "description": "Squad SDK \u2014 Programmable multi-agent runtime for GitHub Copilot", |
Comment on lines
+94
to
+96
| case '--char-limit': | ||
| options.charLimit = parseInt(args[++i] ?? '', 10) || 30_000; | ||
| break; |
Comment on lines
+474
to
+499
| const decisionsMatch = draft.match(/## Decisions\n\n([\s\S]*?)(?=\n## |\n*$)/); | ||
| if (decisionsMatch?.[1]) { | ||
| const currentDecisionsLen = decisionsMatch[1].length; | ||
| const newBudget = Math.max(1000, currentDecisionsLen - overage - 500); | ||
|
|
||
| const tighterDistill = distillDecisions(context.decisions, { | ||
| charBudget: newBudget, | ||
| }); | ||
|
|
||
| draft = draft.replace(decisionsMatch[1], tighterDistill.markdown); | ||
| appliedCompactions.push(`decisions-retightened(${newBudget})`); | ||
| charCount = draft.length; | ||
| } | ||
| } | ||
|
|
||
| // Compaction pass 4: if STILL over, aggressively trim decisions to minimum | ||
| if (charCount > charTarget && context.decisions) { | ||
| const decisionsMatch = draft.match(/## Decisions\n\n([\s\S]*?)(?=\n## |\n*$)/); | ||
| if (decisionsMatch?.[1]) { | ||
| const minDistill = distillDecisions(context.decisions, { | ||
| charBudget: 800, | ||
| }); | ||
| draft = draft.replace(decisionsMatch[1], minDistill.markdown); | ||
| appliedCompactions.push('decisions-minimized'); | ||
| charCount = draft.length; | ||
| } |
Comment on lines
+75
to
+76
| const summary = m.charterSummary ? ` ${m.charterSummary}` : ''; | ||
| return `- **${m.displayName}** (\`${m.slug}\`) — ${m.role}.${summary ? ` Use for${summary.startsWith(' ') ? summary : ' ' + summary}.` : ''}`; |
Comment on lines
+319
to
+323
| return { | ||
| markdown: cleaned, | ||
| charCount: cleaned.length, | ||
| sourceCount: parseExportDecisions(raw).length, | ||
| retainedCount: parseExportDecisions(raw).length, |
Owner
Author
|
Consolidating into #1180 — all v008-v011 work lands there. |
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
Implements the v008 quality-first philosophy for coordinator-as-agent export: we'd rather refuse with honest advice than produce a compressed artifact that lacks enough context to be useful.
What's New
--char-limit <n>flag (default 30000)--forceescape hatch — Override viability refusal with honest disclaimerViability Gate UX
When a squad is too complex:
Test Results
--forcecorrectly overrides rejectionCLI Usage