Skip to content

feat(export): v008 quality-first viability gate#1187

Closed
bradygaster wants to merge 1 commit into
devfrom
squad/v008-quality-viability-gate
Closed

feat(export): v008 quality-first viability gate#1187
bradygaster wants to merge 1 commit into
devfrom
squad/v008-quality-viability-gate

Conversation

@bradygaster

Copy link
Copy Markdown
Owner

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

  • Viability gate — Pre-flight check assessing squad complexity before attempting export
    • Agent count thresholds (>25 warn, >50 error)
    • Routing rule density thresholds (>60 warn, >100 error)
    • Coverage analysis (structural content vs budget ratio)
    • All thresholds scale with configured character limit
  • Configurable output limit--char-limit <n> flag (default 30000)
    • Future-proofs for CCA limit increases
    • Safety margin of 1000 chars below the limit
  • --force escape hatch — Override viability refusal with honest disclaimer
  • Roster parsing fix — Filters out category headers, placeholder rows, and non-member entries from team.md tables
  • Dispatch rules distillation — Groups routes to same target when >20 rules, caps principles at 5
  • Decision distillation — Progressive compaction from full → compact → lazy-load → minimize → drop
  • Self-containment enforcement — Strips all external file references CCA cannot access

Viability Gate UX

When a squad is too complex:

We're pretty sure this squad is too complex to produce a valuable export within the 30000-char limit.

• This squad has 55 specialists — even in compact mode, the roster alone may consume most of the 30000-char budget.
• 120 routing rules is very dense. Even distilled, this many rules may produce a dispatch section that dominates the budget.

Use --force if you want to attempt it anyway — we'll do our best, but the output may lack enough context to be useful for an AI agent.

Test Results

  • 10 unit tests passing (viability check + roster parsing)
  • Local squad repo: 24 members, 39 rules → viable, 5K/30K chars
  • Simulated 55-agent ModSquad → correctly rejected (complexity: 97/100)
  • --force correctly overrides rejection
  • Configurable limit correctly scales thresholds (60K limit → 55 agents passes)

CLI Usage

squad export agent                    # Standard export
squad export agent --char-limit 50000 # Custom limit
squad export agent --force            # Override viability refusal
squad export agent --compact          # Force compact mode
squad export agent --dry-run          # Preview without writing
squad export agent --check            # CI drift detection

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>
Copilot AI review requested due to automatic review settings May 28, 2026 19:03

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.

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-check module + CLI wiring that scales agent/rule/coverage thresholds with the configured char limit, and refuses (or warns) accordingly.
  • Roster parser in load-export-context filters 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,
@bradygaster

Copy link
Copy Markdown
Owner Author

Consolidating into #1180 — all v008-v011 work lands there.

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.

2 participants