@specforge/cli 0.2.7 → 0.2.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. package/dist/cli/commands/scaffold/agent-types.d.ts.map +1 -1
  2. package/dist/cli/commands/scaffold/agent-types.js +9 -1
  3. package/dist/cli/commands/scaffold/agent-types.js.map +1 -1
  4. package/dist/cli/commands/status.d.ts.map +1 -1
  5. package/dist/cli/commands/status.js +23 -23
  6. package/dist/cli/commands/status.js.map +1 -1
  7. package/dist/cli/commands/status.types.d.ts +38 -0
  8. package/dist/cli/commands/status.types.d.ts.map +1 -1
  9. package/dist/cli/commands/status.types.js.map +1 -1
  10. package/dist/cli/templates/agents/content/core/sfag-epic-expander.d.ts +10 -0
  11. package/dist/cli/templates/agents/content/core/sfag-epic-expander.d.ts.map +1 -0
  12. package/dist/cli/templates/agents/content/core/sfag-epic-expander.js +73 -0
  13. package/dist/cli/templates/agents/content/core/sfag-epic-expander.js.map +1 -0
  14. package/dist/cli/templates/agents/content/core/sfag-expansion-consolidator.d.ts +12 -0
  15. package/dist/cli/templates/agents/content/core/sfag-expansion-consolidator.d.ts.map +1 -0
  16. package/dist/cli/templates/agents/content/core/sfag-expansion-consolidator.js +63 -0
  17. package/dist/cli/templates/agents/content/core/sfag-expansion-consolidator.js.map +1 -0
  18. package/dist/cli/templates/agents/content/core/sfag-spec-creator.d.ts.map +1 -1
  19. package/dist/cli/templates/agents/content/core/sfag-spec-creator.js +34 -0
  20. package/dist/cli/templates/agents/content/core/sfag-spec-creator.js.map +1 -1
  21. package/dist/cli/templates/agents/content/core/sfag-ticket-expander-impl.d.ts +10 -0
  22. package/dist/cli/templates/agents/content/core/sfag-ticket-expander-impl.d.ts.map +1 -0
  23. package/dist/cli/templates/agents/content/core/sfag-ticket-expander-impl.js +67 -0
  24. package/dist/cli/templates/agents/content/core/sfag-ticket-expander-impl.js.map +1 -0
  25. package/dist/cli/templates/agents/content/core/sfag-ticket-expander-verification.d.ts +11 -0
  26. package/dist/cli/templates/agents/content/core/sfag-ticket-expander-verification.d.ts.map +1 -0
  27. package/dist/cli/templates/agents/content/core/sfag-ticket-expander-verification.js +66 -0
  28. package/dist/cli/templates/agents/content/core/sfag-ticket-expander-verification.js.map +1 -0
  29. package/dist/cli/templates/agents/index.d.ts.map +1 -1
  30. package/dist/cli/templates/agents/index.js +8 -0
  31. package/dist/cli/templates/agents/index.js.map +1 -1
  32. package/node_modules/@specforge/api-types/package.json +1 -1
  33. package/node_modules/@specforge/session-types/package.json +1 -1
  34. package/node_modules/@specforge/spec-types/package.json +1 -1
  35. package/package.json +7 -7
  36. package/src/cli/templates/agents/content/core/sfag-epic-expander.ts +79 -0
  37. package/src/cli/templates/agents/content/core/sfag-expansion-consolidator.ts +71 -0
  38. package/src/cli/templates/agents/content/core/sfag-spec-creator.ts +34 -0
  39. package/src/cli/templates/agents/content/core/sfag-ticket-expander-impl.ts +73 -0
  40. package/src/cli/templates/agents/content/core/sfag-ticket-expander-verification.ts +73 -0
  41. package/src/cli/templates/agents/index.ts +8 -0
@@ -1 +1 @@
1
- {"version":3,"file":"agent-types.d.ts","sourceRoot":"","sources":["../../../../src/cli/commands/scaffold/agent-types.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,MAAM,MAAM,aAAa,GACrB,eAAe,GACf,WAAW,GACX,UAAU,CAAC;AAEf;;GAEG;AACH,MAAM,MAAM,UAAU,GAAG,MAAM,GAAG,QAAQ,GAAG,OAAO,CAAC;AAErD;;GAEG;AACH,MAAM,MAAM,UAAU,GAClB,KAAK,GACL,QAAQ,GACR,OAAO,GACP,MAAM,GACN,MAAM,GACN,SAAS,GACT,OAAO,GACP,MAAM,CAAC;AAEX;;;;;;;GAOG;AACH,MAAM,MAAM,WAAW,GAAG,SAAS,GAAG,MAAM,GAAG,OAAO,CAAC;AAEvD;;GAEG;AACH,MAAM,WAAW,aAAa;IAC5B,6CAA6C;IAC7C,IAAI,EAAE,MAAM,CAAC;IACb,uCAAuC;IACvC,WAAW,EAAE,MAAM,CAAC;IACpB,6DAA6D;IAC7D,kBAAkB,EAAE,MAAM,CAAC;IAC3B,qCAAqC;IACrC,KAAK,EAAE,UAAU,CAAC;IAClB,8BAA8B;IAC9B,KAAK,EAAE,UAAU,CAAC;IAClB,yCAAyC;IACzC,OAAO,EAAE,MAAM,CAAC;IAChB,kCAAkC;IAClC,QAAQ,EAAE,aAAa,CAAC;IACxB,iDAAiD;IACjD,MAAM,CAAC,EAAE,WAAW,CAAC;CACtB;AAED,eAAO,MAAM,gBAAgB,EAAE,MAAM,CAAC,aAAa,EAAE,MAAM,EAAE,CAI5D,CAAC;AAEF;;GAEG;AACH,wBAAgB,qBAAqB,IAAI,aAAa,EAAE,CAEvD;AAED;;GAEG;AACH,eAAO,MAAM,YAAY,EAAE,MAAM,CAAC,UAAU,EAAE,MAAM,CAInD,CAAC"}
1
+ {"version":3,"file":"agent-types.d.ts","sourceRoot":"","sources":["../../../../src/cli/commands/scaffold/agent-types.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,MAAM,MAAM,aAAa,GACrB,eAAe,GACf,WAAW,GACX,UAAU,CAAC;AAEf;;GAEG;AACH,MAAM,MAAM,UAAU,GAAG,MAAM,GAAG,QAAQ,GAAG,OAAO,CAAC;AAErD;;GAEG;AACH,MAAM,MAAM,UAAU,GAClB,KAAK,GACL,QAAQ,GACR,OAAO,GACP,MAAM,GACN,MAAM,GACN,SAAS,GACT,OAAO,GACP,MAAM,CAAC;AAEX;;;;;;;GAOG;AACH,MAAM,MAAM,WAAW,GAAG,SAAS,GAAG,MAAM,GAAG,OAAO,CAAC;AAEvD;;GAEG;AACH,MAAM,WAAW,aAAa;IAC5B,6CAA6C;IAC7C,IAAI,EAAE,MAAM,CAAC;IACb,uCAAuC;IACvC,WAAW,EAAE,MAAM,CAAC;IACpB,6DAA6D;IAC7D,kBAAkB,EAAE,MAAM,CAAC;IAC3B,qCAAqC;IACrC,KAAK,EAAE,UAAU,CAAC;IAClB,8BAA8B;IAC9B,KAAK,EAAE,UAAU,CAAC;IAClB,yCAAyC;IACzC,OAAO,EAAE,MAAM,CAAC;IAChB,kCAAkC;IAClC,QAAQ,EAAE,aAAa,CAAC;IACxB,iDAAiD;IACjD,MAAM,CAAC,EAAE,WAAW,CAAC;CACtB;AAED,eAAO,MAAM,gBAAgB,EAAE,MAAM,CAAC,aAAa,EAAE,MAAM,EAAE,CAY5D,CAAC;AAEF;;GAEG;AACH,wBAAgB,qBAAqB,IAAI,aAAa,EAAE,CAEvD;AAED;;GAEG;AACH,eAAO,MAAM,YAAY,EAAE,MAAM,CAAC,UAAU,EAAE,MAAM,CAInD,CAAC"}
@@ -1,6 +1,14 @@
1
1
  const AGENT_CATEGORIES = {
2
2
  Orchestration: ["sfag-orchestrator"],
3
- SpecForge: ["sfag-spec-creator", "sfag-ticket-implementer", "sfag-work-resolver"],
3
+ SpecForge: [
4
+ "sfag-spec-creator",
5
+ "sfag-epic-expander",
6
+ "sfag-ticket-expander-impl",
7
+ "sfag-ticket-expander-verification",
8
+ "sfag-expansion-consolidator",
9
+ "sfag-ticket-implementer",
10
+ "sfag-work-resolver"
11
+ ],
4
12
  Research: ["sfag-package-researcher"]
5
13
  };
6
14
  function getAgentCategoryNames() {
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../src/cli/commands/scaffold/agent-types.ts"],"sourcesContent":["/**\n * Agent Scaffolding Types & Interfaces\n *\n * Type definitions for AI agent scaffolding in Claude Code and other AI tools.\n */\n\nexport type AgentCategory =\n | 'Orchestration'\n | 'SpecForge'\n | 'Research';\n\n/**\n * Agent model options\n */\nexport type AgentModel = 'opus' | 'sonnet' | 'haiku';\n\n/**\n * Supported colors for agent display\n */\nexport type AgentColor =\n | 'red'\n | 'yellow'\n | 'green'\n | 'cyan'\n | 'blue'\n | 'magenta'\n | 'white'\n | 'gray';\n\n/**\n * Persistent memory scope for the agent. Maps to Claude Code's `memory:`\n * frontmatter field, which triggers Claude Code to inject the persistent\n * memory system prompt and create the matching agent-memory directory:\n * - `project` → `.claude/agent-memory/<name>/`\n * - `user` → `~/.claude/agent-memory/<name>/`\n * - `local` → `.claude/agent-memory-local/<name>/`\n */\nexport type AgentMemory = 'project' | 'user' | 'local';\n\n/**\n * Agent template definition\n */\nexport interface AgentTemplate {\n /** Agent name (e.g., 'sfag-orchestrator') */\n name: string;\n /** Short description for agent list */\n description: string;\n /** Full description with trigger examples for frontmatter */\n triggerDescription: string;\n /** AI model to use for this agent */\n model: AgentModel;\n /** Color for agent display */\n color: AgentColor;\n /** Full markdown content of the agent */\n content: string;\n /** Agent category for grouping */\n category: AgentCategory;\n /** Persistent memory scope (Claude Code only) */\n memory?: AgentMemory;\n}\n\nexport const AGENT_CATEGORIES: Record<AgentCategory, string[]> = {\n Orchestration: ['sfag-orchestrator'],\n SpecForge: ['sfag-spec-creator', 'sfag-ticket-implementer', 'sfag-work-resolver'],\n Research: ['sfag-package-researcher'],\n};\n\n/**\n * Get all agent category names\n */\nexport function getAgentCategoryNames(): AgentCategory[] {\n return Object.keys(AGENT_CATEGORIES) as AgentCategory[];\n}\n\n/**\n * Model display badges for CLI output\n */\nexport const MODEL_BADGES: Record<AgentModel, string> = {\n opus: '◆', // Diamond for opus (powerful)\n sonnet: '●', // Circle for sonnet (balanced)\n haiku: '○', // Empty circle for haiku (fast)\n};\n"],"mappings":"AA6DO,MAAM,mBAAoD;AAAA,EAC/D,eAAe,CAAC,mBAAmB;AAAA,EACnC,WAAW,CAAC,qBAAqB,2BAA2B,oBAAoB;AAAA,EAChF,UAAU,CAAC,yBAAyB;AACtC;AAKO,SAAS,wBAAyC;AACvD,SAAO,OAAO,KAAK,gBAAgB;AACrC;AAKO,MAAM,eAA2C;AAAA,EACtD,MAAM;AAAA;AAAA,EACN,QAAQ;AAAA;AAAA,EACR,OAAO;AAAA;AACT;","names":[]}
1
+ {"version":3,"sources":["../../../../src/cli/commands/scaffold/agent-types.ts"],"sourcesContent":["/**\n * Agent Scaffolding Types & Interfaces\n *\n * Type definitions for AI agent scaffolding in Claude Code and other AI tools.\n */\n\nexport type AgentCategory =\n | 'Orchestration'\n | 'SpecForge'\n | 'Research';\n\n/**\n * Agent model options\n */\nexport type AgentModel = 'opus' | 'sonnet' | 'haiku';\n\n/**\n * Supported colors for agent display\n */\nexport type AgentColor =\n | 'red'\n | 'yellow'\n | 'green'\n | 'cyan'\n | 'blue'\n | 'magenta'\n | 'white'\n | 'gray';\n\n/**\n * Persistent memory scope for the agent. Maps to Claude Code's `memory:`\n * frontmatter field, which triggers Claude Code to inject the persistent\n * memory system prompt and create the matching agent-memory directory:\n * - `project` → `.claude/agent-memory/<name>/`\n * - `user` → `~/.claude/agent-memory/<name>/`\n * - `local` → `.claude/agent-memory-local/<name>/`\n */\nexport type AgentMemory = 'project' | 'user' | 'local';\n\n/**\n * Agent template definition\n */\nexport interface AgentTemplate {\n /** Agent name (e.g., 'sfag-orchestrator') */\n name: string;\n /** Short description for agent list */\n description: string;\n /** Full description with trigger examples for frontmatter */\n triggerDescription: string;\n /** AI model to use for this agent */\n model: AgentModel;\n /** Color for agent display */\n color: AgentColor;\n /** Full markdown content of the agent */\n content: string;\n /** Agent category for grouping */\n category: AgentCategory;\n /** Persistent memory scope (Claude Code only) */\n memory?: AgentMemory;\n}\n\nexport const AGENT_CATEGORIES: Record<AgentCategory, string[]> = {\n Orchestration: ['sfag-orchestrator'],\n SpecForge: [\n 'sfag-spec-creator',\n 'sfag-epic-expander',\n 'sfag-ticket-expander-impl',\n 'sfag-ticket-expander-verification',\n 'sfag-expansion-consolidator',\n 'sfag-ticket-implementer',\n 'sfag-work-resolver',\n ],\n Research: ['sfag-package-researcher'],\n};\n\n/**\n * Get all agent category names\n */\nexport function getAgentCategoryNames(): AgentCategory[] {\n return Object.keys(AGENT_CATEGORIES) as AgentCategory[];\n}\n\n/**\n * Model display badges for CLI output\n */\nexport const MODEL_BADGES: Record<AgentModel, string> = {\n opus: '◆', // Diamond for opus (powerful)\n sonnet: '●', // Circle for sonnet (balanced)\n haiku: '○', // Empty circle for haiku (fast)\n};\n"],"mappings":"AA6DO,MAAM,mBAAoD;AAAA,EAC/D,eAAe,CAAC,mBAAmB;AAAA,EACnC,WAAW;AAAA,IACT;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF;AAAA,EACA,UAAU,CAAC,yBAAyB;AACtC;AAKO,SAAS,wBAAyC;AACvD,SAAO,OAAO,KAAK,gBAAgB;AACrC;AAKO,MAAM,eAA2C;AAAA,EACtD,MAAM;AAAA;AAAA,EACN,QAAQ;AAAA;AAAA,EACR,OAAO;AAAA;AACT;","names":[]}
@@ -1 +1 @@
1
- {"version":3,"file":"status.d.ts","sourceRoot":"","sources":["../../../src/cli/commands/status.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAQpC,OAAO,EACL,aAAa,EAGd,MAAM,mBAAmB,CAAC;AA0D3B;;GAEG;AACH,wBAAsB,YAAY,CAAC,OAAO,EAAE,aAAa,GAAG,OAAO,CAAC,IAAI,CAAC,CAoExE;AAED;;GAEG;AACH,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CAkB5D"}
1
+ {"version":3,"file":"status.d.ts","sourceRoot":"","sources":["../../../src/cli/commands/status.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAOpC,OAAO,EACL,aAAa,EAKd,MAAM,mBAAmB,CAAC;AAqD3B;;GAEG;AACH,wBAAsB,YAAY,CAAC,OAAO,EAAE,aAAa,GAAG,OAAO,CAAC,IAAI,CAAC,CA8ExE;AAED;;GAEG;AACH,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CAkB5D"}
@@ -11,29 +11,27 @@ function renderStatus(report, next, specificationId) {
11
11
  printBlank();
12
12
  console.log(colors.bold("Context"));
13
13
  console.log(colors.muted("\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
14
- console.log(`Project: ${report.project?.name ?? colors.muted("(unknown)")}`);
15
- const specEntry = specificationId ? report.specifications.find((s) => s.specification.id === specificationId) : report.specifications[0];
14
+ console.log(`Project: ${report?.project?.name ?? colors.muted("(unknown)")}`);
15
+ const specs = report?.specifications ?? [];
16
+ const specEntry = specificationId ? specs.find((s) => s.id === specificationId) : specs[0];
16
17
  if (specEntry) {
17
- const spec = specEntry.specification;
18
- const specStatus = spec.status;
19
- console.log(`Spec: ${spec.title}${specStatus ? colors.muted(` (${specStatus})`) : ""}`);
18
+ console.log(
19
+ `Spec: ${specEntry.title}${specEntry.status ? colors.muted(` (${specEntry.status})`) : ""}`
20
+ );
20
21
  printBlank();
21
22
  console.log(colors.bold("Progress"));
22
23
  console.log(colors.muted("\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
23
- const pct = specEntry.totalTickets > 0 ? Math.round(specEntry.completedTickets / specEntry.totalTickets * 100) : 0;
24
- console.log(`Tickets: ${specEntry.completedTickets}/${specEntry.totalTickets} (${pct}%)`);
25
- console.log(`Epics: ${specEntry.completedEpics}/${specEntry.totalEpics}`);
24
+ const pct = typeof specEntry.progress === "number" ? Math.round(specEntry.progress) : 0;
25
+ console.log(`Progress: ${pct}%`);
26
+ if (typeof specEntry.ticketsRemaining === "number") {
27
+ console.log(`Remaining: ${specEntry.ticketsRemaining} ticket${specEntry.ticketsRemaining === 1 ? "" : "s"}`);
28
+ }
29
+ if (specEntry.blockerCount && specEntry.blockerCount > 0) {
30
+ console.log(`Blockers: ${specEntry.blockerCount}`);
31
+ }
26
32
  } else {
27
33
  console.log(colors.muted("Spec: (none active \u2014 run `specforge switch <id>`)"));
28
34
  }
29
- if (report.recentActivity && report.recentActivity.length > 0) {
30
- printBlank();
31
- console.log(colors.bold("Recent activity"));
32
- console.log(colors.muted("\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
33
- for (const a of report.recentActivity.slice(0, 3)) {
34
- console.log(` ${a.action} \u2014 ${a.ticketTitle}`);
35
- }
36
- }
37
35
  if (next && next.items.length > 0) {
38
36
  printBlank();
39
37
  console.log(colors.bold("Next actionable"));
@@ -64,18 +62,20 @@ async function statusAction(options) {
64
62
  );
65
63
  }
66
64
  const spinner = ora({ text: "Fetching status...", color: "cyan" }).start();
65
+ let report;
66
+ let next;
67
67
  try {
68
68
  const client = new ApiClient({
69
69
  apiKey: config.apiKey,
70
70
  apiUrl: config.apiUrl,
71
71
  debug: config.debug
72
72
  });
73
- const report = await client.call("get_report", {
73
+ const response = await client.call("get_report", {
74
74
  type: "implementation",
75
75
  scope,
76
76
  scopeId
77
77
  });
78
- let next;
78
+ report = response?.report;
79
79
  if (config.specificationId) {
80
80
  try {
81
81
  next = await client.call("get_next_actionable_tickets", {
@@ -85,11 +85,6 @@ async function statusAction(options) {
85
85
  }
86
86
  }
87
87
  spinner.stop();
88
- if (options.json) {
89
- console.log(JSON.stringify({ report, nextActionable: next ?? null }, null, 2));
90
- } else {
91
- renderStatus(report, next, config.specificationId);
92
- }
93
88
  } catch (error) {
94
89
  spinner.fail("Failed to fetch status");
95
90
  const message = error instanceof Error ? error.message : "Unknown error";
@@ -102,6 +97,11 @@ async function statusAction(options) {
102
97
  }
103
98
  throw new NetworkError(`Failed to fetch status: ${message}`);
104
99
  }
100
+ if (options.json) {
101
+ console.log(JSON.stringify({ report: report ?? null, nextActionable: next ?? null }, null, 2));
102
+ } else {
103
+ renderStatus(report, next, config.specificationId);
104
+ }
105
105
  }
106
106
  function registerStatusCommand(program) {
107
107
  program.command("status").description("Show active project/spec, implementation progress, and next actionable tickets").option("--json", "Output as JSON").addHelpText("after", `
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/cli/commands/status.ts"],"sourcesContent":["/**\n * Status Command\n *\n * Shows what this terminal is pointed at (project / spec from\n * `.specforge/config.json`) plus a live implementation report for the active\n * spec and the next actionable tickets. Uses only canonical tools —\n * `get_report` (type='implementation') + `get_next_actionable_tickets`.\n */\n\nimport { Command } from 'commander';\nimport ora from 'ora';\nimport { resolveConfig } from '../config/index.js';\nimport { withErrorHandler, CliError, NetworkError } from '../middleware/error-handler.js';\nimport { printBlank } from '../ui/output.js';\nimport { colors } from '../ui/colors.js';\nimport { ApiClient } from '../../client/api-client.js';\nimport type { ImplementationSummary } from '../../types/index.js';\nimport {\n StatusOptions,\n NextActionableResponse,\n formatTicketNumber,\n} from './status.types.js';\n\n/**\n * Render the human-facing status view.\n */\nfunction renderStatus(\n report: ImplementationSummary,\n next: NextActionableResponse | undefined,\n specificationId: string | null\n): void {\n printBlank();\n console.log(colors.bold('Context'));\n console.log(colors.muted('───────'));\n console.log(`Project: ${report.project?.name ?? colors.muted('(unknown)')}`);\n\n const specEntry = specificationId\n ? report.specifications.find((s) => s.specification.id === specificationId)\n : report.specifications[0];\n\n if (specEntry) {\n const spec = specEntry.specification;\n const specStatus = (spec as { status?: string }).status;\n console.log(`Spec: ${spec.title}${specStatus ? colors.muted(` (${specStatus})`) : ''}`);\n\n printBlank();\n console.log(colors.bold('Progress'));\n console.log(colors.muted('────────'));\n const pct = specEntry.totalTickets > 0\n ? Math.round((specEntry.completedTickets / specEntry.totalTickets) * 100)\n : 0;\n console.log(`Tickets: ${specEntry.completedTickets}/${specEntry.totalTickets} (${pct}%)`);\n console.log(`Epics: ${specEntry.completedEpics}/${specEntry.totalEpics}`);\n } else {\n console.log(colors.muted('Spec: (none active run `specforge switch <id>`)'));\n }\n\n if (report.recentActivity && report.recentActivity.length > 0) {\n printBlank();\n console.log(colors.bold('Recent activity'));\n console.log(colors.muted('───────────────'));\n for (const a of report.recentActivity.slice(0, 3)) {\n console.log(` ${a.action} ${a.ticketTitle}`);\n }\n }\n\n if (next && next.items.length > 0) {\n printBlank();\n console.log(colors.bold('Next actionable'));\n console.log(colors.muted('───────────────'));\n for (const t of next.items.slice(0, 5)) {\n const priority = t.priority ? colors.muted(` [${t.priority}]`) : '';\n console.log(` ${formatTicketNumber(t.ticketNumber)} ${t.title}${priority}`);\n }\n }\n\n printBlank();\n}\n\n/**\n * Status command action handler\n */\nexport async function statusAction(options: StatusOptions): Promise<void> {\n const config = resolveConfig();\n\n if (!config.apiKey) {\n throw new CliError(\n 'Not authenticated',\n 1,\n 'Run `specforge login` to authenticate first'\n );\n }\n\n const scope = config.specificationId ? 'specification' : 'project';\n const scopeId = config.specificationId ?? config.projectId;\n if (!scopeId) {\n throw new CliError(\n 'No active project or specification',\n 1,\n 'Run `specforge init` or `specforge switch <id>` first'\n );\n }\n\n const spinner = ora({ text: 'Fetching status...', color: 'cyan' }).start();\n\n try {\n const client = new ApiClient({\n apiKey: config.apiKey,\n apiUrl: config.apiUrl,\n debug: config.debug,\n });\n\n const report = await client.call<ImplementationSummary>('get_report', {\n type: 'implementation',\n scope,\n scopeId,\n });\n\n let next: NextActionableResponse | undefined;\n if (config.specificationId) {\n try {\n next = await client.call<NextActionableResponse>('get_next_actionable_tickets', {\n specificationId: config.specificationId,\n });\n } catch {\n // Next-actionable is a best-effort enrichment — ignore failures.\n }\n }\n\n spinner.stop();\n\n if (options.json) {\n console.log(JSON.stringify({ report, nextActionable: next ?? null }, null, 2));\n } else {\n renderStatus(report, next, config.specificationId);\n }\n } catch (error) {\n spinner.fail('Failed to fetch status');\n\n const message = error instanceof Error ? error.message : 'Unknown error';\n if (message.includes('401') || message.includes('Unauthorized')) {\n throw new CliError(\n 'Authentication failed',\n 1,\n 'Your API key may be invalid. Run `specforge login` to re-authenticate'\n );\n }\n\n throw new NetworkError(`Failed to fetch status: ${message}`);\n }\n}\n\n/**\n * Register status command with Commander\n */\nexport function registerStatusCommand(program: Command): void {\n program\n .command('status')\n .description('Show active project/spec, implementation progress, and next actionable tickets')\n .option('--json', 'Output as JSON')\n .addHelpText('after', `\nExamples:\n $ specforge status # Active project/spec + progress + next tickets\n $ specforge status --json # Machine-readable output\n\nShows:\n - Context: active project and specification (from .specforge/config.json)\n - Progress: epic/ticket completion for the active spec\n - Next actionable: ready-to-work tickets\n\nUse 'specforge switch <id>' to change the active project or specification.\n`)\n .action(withErrorHandler(statusAction));\n}\n"],"mappings":"AAUA,OAAO,SAAS;AAChB,SAAS,qBAAqB;AAC9B,SAAS,kBAAkB,UAAU,oBAAoB;AACzD,SAAS,kBAAkB;AAC3B,SAAS,cAAc;AACvB,SAAS,iBAAiB;AAE1B;AAAA,EAGE;AAAA,OACK;AAKP,SAAS,aACP,QACA,MACA,iBACM;AACN,aAAW;AACX,UAAQ,IAAI,OAAO,KAAK,SAAS,CAAC;AAClC,UAAQ,IAAI,OAAO,MAAM,4CAAS,CAAC;AACnC,UAAQ,IAAI,YAAY,OAAO,SAAS,QAAQ,OAAO,MAAM,WAAW,CAAC,EAAE;AAE3E,QAAM,YAAY,kBACd,OAAO,eAAe,KAAK,CAAC,MAAM,EAAE,cAAc,OAAO,eAAe,IACxE,OAAO,eAAe,CAAC;AAE3B,MAAI,WAAW;AACb,UAAM,OAAO,UAAU;AACvB,UAAM,aAAc,KAA6B;AACjD,YAAQ,IAAI,YAAY,KAAK,KAAK,GAAG,aAAa,OAAO,MAAM,KAAK,UAAU,GAAG,IAAI,EAAE,EAAE;AAEzF,eAAW;AACX,YAAQ,IAAI,OAAO,KAAK,UAAU,CAAC;AACnC,YAAQ,IAAI,OAAO,MAAM,kDAAU,CAAC;AACpC,UAAM,MAAM,UAAU,eAAe,IACjC,KAAK,MAAO,UAAU,mBAAmB,UAAU,eAAgB,GAAG,IACtE;AACJ,YAAQ,IAAI,YAAY,UAAU,gBAAgB,IAAI,UAAU,YAAY,KAAK,GAAG,IAAI;AACxF,YAAQ,IAAI,YAAY,UAAU,cAAc,IAAI,UAAU,UAAU,EAAE;AAAA,EAC5E,OAAO;AACL,YAAQ,IAAI,OAAO,MAAM,2DAAsD,CAAC;AAAA,EAClF;AAEA,MAAI,OAAO,kBAAkB,OAAO,eAAe,SAAS,GAAG;AAC7D,eAAW;AACX,YAAQ,IAAI,OAAO,KAAK,iBAAiB,CAAC;AAC1C,YAAQ,IAAI,OAAO,MAAM,4FAAiB,CAAC;AAC3C,eAAW,KAAK,OAAO,eAAe,MAAM,GAAG,CAAC,GAAG;AACjD,cAAQ,IAAI,KAAK,EAAE,MAAM,WAAM,EAAE,WAAW,EAAE;AAAA,IAChD;AAAA,EACF;AAEA,MAAI,QAAQ,KAAK,MAAM,SAAS,GAAG;AACjC,eAAW;AACX,YAAQ,IAAI,OAAO,KAAK,iBAAiB,CAAC;AAC1C,YAAQ,IAAI,OAAO,MAAM,4FAAiB,CAAC;AAC3C,eAAW,KAAK,KAAK,MAAM,MAAM,GAAG,CAAC,GAAG;AACtC,YAAM,WAAW,EAAE,WAAW,OAAO,MAAM,KAAK,EAAE,QAAQ,GAAG,IAAI;AACjE,cAAQ,IAAI,KAAK,mBAAmB,EAAE,YAAY,CAAC,IAAI,EAAE,KAAK,GAAG,QAAQ,EAAE;AAAA,IAC7E;AAAA,EACF;AAEA,aAAW;AACb;AAKA,eAAsB,aAAa,SAAuC;AACxE,QAAM,SAAS,cAAc;AAE7B,MAAI,CAAC,OAAO,QAAQ;AAClB,UAAM,IAAI;AAAA,MACR;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAAA,EACF;AAEA,QAAM,QAAQ,OAAO,kBAAkB,kBAAkB;AACzD,QAAM,UAAU,OAAO,mBAAmB,OAAO;AACjD,MAAI,CAAC,SAAS;AACZ,UAAM,IAAI;AAAA,MACR;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAAA,EACF;AAEA,QAAM,UAAU,IAAI,EAAE,MAAM,sBAAsB,OAAO,OAAO,CAAC,EAAE,MAAM;AAEzE,MAAI;AACF,UAAM,SAAS,IAAI,UAAU;AAAA,MAC3B,QAAQ,OAAO;AAAA,MACf,QAAQ,OAAO;AAAA,MACf,OAAO,OAAO;AAAA,IAChB,CAAC;AAED,UAAM,SAAS,MAAM,OAAO,KAA4B,cAAc;AAAA,MACpE,MAAM;AAAA,MACN;AAAA,MACA;AAAA,IACF,CAAC;AAED,QAAI;AACJ,QAAI,OAAO,iBAAiB;AAC1B,UAAI;AACF,eAAO,MAAM,OAAO,KAA6B,+BAA+B;AAAA,UAC9E,iBAAiB,OAAO;AAAA,QAC1B,CAAC;AAAA,MACH,QAAQ;AAAA,MAER;AAAA,IACF;AAEA,YAAQ,KAAK;AAEb,QAAI,QAAQ,MAAM;AAChB,cAAQ,IAAI,KAAK,UAAU,EAAE,QAAQ,gBAAgB,QAAQ,KAAK,GAAG,MAAM,CAAC,CAAC;AAAA,IAC/E,OAAO;AACL,mBAAa,QAAQ,MAAM,OAAO,eAAe;AAAA,IACnD;AAAA,EACF,SAAS,OAAO;AACd,YAAQ,KAAK,wBAAwB;AAErC,UAAM,UAAU,iBAAiB,QAAQ,MAAM,UAAU;AACzD,QAAI,QAAQ,SAAS,KAAK,KAAK,QAAQ,SAAS,cAAc,GAAG;AAC/D,YAAM,IAAI;AAAA,QACR;AAAA,QACA;AAAA,QACA;AAAA,MACF;AAAA,IACF;AAEA,UAAM,IAAI,aAAa,2BAA2B,OAAO,EAAE;AAAA,EAC7D;AACF;AAKO,SAAS,sBAAsB,SAAwB;AAC5D,UACG,QAAQ,QAAQ,EAChB,YAAY,gFAAgF,EAC5F,OAAO,UAAU,gBAAgB,EACjC,YAAY,SAAS;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,CAWzB,EACI,OAAO,iBAAiB,YAAY,CAAC;AAC1C;","names":[]}
1
+ {"version":3,"sources":["../../../src/cli/commands/status.ts"],"sourcesContent":["/**\n * Status Command\n *\n * Shows what this terminal is pointed at (project / spec from\n * `.specforge/config.json`) plus a live implementation report for the active\n * spec and the next actionable tickets. Uses only canonical tools —\n * `get_report` (type='implementation') + `get_next_actionable_tickets`.\n */\n\nimport { Command } from 'commander';\nimport ora from 'ora';\nimport { resolveConfig } from '../config/index.js';\nimport { withErrorHandler, CliError, NetworkError } from '../middleware/error-handler.js';\nimport { printBlank } from '../ui/output.js';\nimport { colors } from '../ui/colors.js';\nimport { ApiClient } from '../../client/api-client.js';\nimport {\n StatusOptions,\n NextActionableResponse,\n GetReportResponse,\n ImplementationReport,\n formatTicketNumber,\n} from './status.types.js';\n\n/**\n * Render the human-facing status view.\n */\nfunction renderStatus(\n report: ImplementationReport | undefined,\n next: NextActionableResponse | undefined,\n specificationId: string | null\n): void {\n printBlank();\n console.log(colors.bold('Context'));\n console.log(colors.muted('───────'));\n console.log(`Project: ${report?.project?.name ?? colors.muted('(unknown)')}`);\n\n const specs = report?.specifications ?? [];\n const specEntry = specificationId\n ? specs.find((s) => s.id === specificationId)\n : specs[0];\n\n if (specEntry) {\n console.log(\n `Spec: ${specEntry.title}${specEntry.status ? colors.muted(` (${specEntry.status})`) : ''}`\n );\n\n printBlank();\n console.log(colors.bold('Progress'));\n console.log(colors.muted('────────'));\n const pct = typeof specEntry.progress === 'number' ? Math.round(specEntry.progress) : 0;\n console.log(`Progress: ${pct}%`);\n if (typeof specEntry.ticketsRemaining === 'number') {\n console.log(`Remaining: ${specEntry.ticketsRemaining} ticket${specEntry.ticketsRemaining === 1 ? '' : 's'}`);\n }\n if (specEntry.blockerCount && specEntry.blockerCount > 0) {\n console.log(`Blockers: ${specEntry.blockerCount}`);\n }\n } else {\n console.log(colors.muted('Spec: (none active run `specforge switch <id>`)'));\n }\n\n if (next && next.items.length > 0) {\n printBlank();\n console.log(colors.bold('Next actionable'));\n console.log(colors.muted('───────────────'));\n for (const t of next.items.slice(0, 5)) {\n const priority = t.priority ? colors.muted(` [${t.priority}]`) : '';\n console.log(` ${formatTicketNumber(t.ticketNumber)} ${t.title}${priority}`);\n }\n }\n\n printBlank();\n}\n\n/**\n * Status command action handler\n */\nexport async function statusAction(options: StatusOptions): Promise<void> {\n const config = resolveConfig();\n\n if (!config.apiKey) {\n throw new CliError(\n 'Not authenticated',\n 1,\n 'Run `specforge login` to authenticate first'\n );\n }\n\n const scope = config.specificationId ? 'specification' : 'project';\n const scopeId = config.specificationId ?? config.projectId;\n if (!scopeId) {\n throw new CliError(\n 'No active project or specification',\n 1,\n 'Run `specforge init` or `specforge switch <id>` first'\n );\n }\n\n const spinner = ora({ text: 'Fetching status...', color: 'cyan' }).start();\n\n let report: ImplementationReport | undefined;\n let next: NextActionableResponse | undefined;\n\n try {\n const client = new ApiClient({\n apiKey: config.apiKey,\n apiUrl: config.apiUrl,\n debug: config.debug,\n });\n\n // `get_report` returns a metadata wrapper — { type, scope, scopeId, format,\n // report } — with the implementation report nested under `report` (NOT the\n // top-level body). The `/local` transport has no `{ data }` envelope to\n // unwrap here, so we reach into `.report` ourselves.\n const response = await client.call<GetReportResponse>('get_report', {\n type: 'implementation',\n scope,\n scopeId,\n });\n report = response?.report;\n\n if (config.specificationId) {\n try {\n next = await client.call<NextActionableResponse>('get_next_actionable_tickets', {\n specificationId: config.specificationId,\n });\n } catch {\n // Next-actionable is a best-effort enrichment — ignore failures.\n }\n }\n\n spinner.stop();\n } catch (error) {\n spinner.fail('Failed to fetch status');\n\n const message = error instanceof Error ? error.message : 'Unknown error';\n if (message.includes('401') || message.includes('Unauthorized')) {\n throw new CliError(\n 'Authentication failed',\n 1,\n 'Your API key may be invalid. Run `specforge login` to re-authenticate'\n );\n }\n\n throw new NetworkError(`Failed to fetch status: ${message}`);\n }\n\n // Render OUTSIDE the try: a rendering bug must surface as itself, never as a\n // mislabelled \"Failed to fetch status\" network error (the fetch already\n // succeeded by this point).\n if (options.json) {\n console.log(JSON.stringify({ report: report ?? null, nextActionable: next ?? null }, null, 2));\n } else {\n renderStatus(report, next, config.specificationId);\n }\n}\n\n/**\n * Register status command with Commander\n */\nexport function registerStatusCommand(program: Command): void {\n program\n .command('status')\n .description('Show active project/spec, implementation progress, and next actionable tickets')\n .option('--json', 'Output as JSON')\n .addHelpText('after', `\nExamples:\n $ specforge status # Active project/spec + progress + next tickets\n $ specforge status --json # Machine-readable output\n\nShows:\n - Context: active project and specification (from .specforge/config.json)\n - Progress: epic/ticket completion for the active spec\n - Next actionable: ready-to-work tickets\n\nUse 'specforge switch <id>' to change the active project or specification.\n`)\n .action(withErrorHandler(statusAction));\n}\n"],"mappings":"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","names":[]}
@@ -30,6 +30,44 @@ export interface NextActionableResponse {
30
30
  items: NextActionableItem[];
31
31
  total: number;
32
32
  }
33
+ /**
34
+ * One spec's progress row inside the implementation report. The live
35
+ * `get_report` (type='implementation') returns FLAT spec rows — id/title/status
36
+ * at the top of each entry, plus progress metrics — NOT a nested
37
+ * `{ specification, completedTickets, … }` object (the stale
38
+ * `@specforge/report-types` shape). This local type mirrors the real wire.
39
+ */
40
+ export interface SpecProgressEntry {
41
+ id: string;
42
+ title: string;
43
+ status?: string;
44
+ /** Completion percentage, 0–100. */
45
+ progress?: number;
46
+ ticketsRemaining?: number;
47
+ estimatedCompletion?: string | null;
48
+ blockerCount?: number;
49
+ }
50
+ /**
51
+ * The `report` payload nested inside the get_report response wrapper.
52
+ */
53
+ export interface ImplementationReport {
54
+ project?: {
55
+ id: string;
56
+ name: string;
57
+ };
58
+ specifications?: SpecProgressEntry[];
59
+ }
60
+ /**
61
+ * The full get_report response — a metadata wrapper carrying the actual
62
+ * implementation report under `report`.
63
+ */
64
+ export interface GetReportResponse {
65
+ type?: string;
66
+ scope?: string;
67
+ scopeId?: string;
68
+ format?: string;
69
+ report?: ImplementationReport;
70
+ }
33
71
  /**
34
72
  * Format a ticket number with padding (e.g. 7 → "TKT-007").
35
73
  */
@@ -1 +1 @@
1
- {"version":3,"file":"status.types.d.ts","sourceRoot":"","sources":["../../../src/cli/commands/status.types.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH;;GAEG;AACH,MAAM,WAAW,aAAa;IAC5B,qBAAqB;IACrB,IAAI,CAAC,EAAE,OAAO,CAAC;CAChB;AAED;;GAEG;AACH,MAAM,WAAW,kBAAkB;IACjC,EAAE,EAAE,MAAM,CAAC;IACX,YAAY,EAAE,MAAM,CAAC;IACrB,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED;;GAEG;AACH,MAAM,WAAW,sBAAsB;IACrC,KAAK,EAAE,kBAAkB,EAAE,CAAC;IAC5B,KAAK,EAAE,MAAM,CAAC;CACf;AAED;;GAEG;AACH,wBAAgB,kBAAkB,CAChC,YAAY,EAAE,MAAM,EACpB,MAAM,SAAQ,EACd,OAAO,SAAI,GACV,MAAM,CAER"}
1
+ {"version":3,"file":"status.types.d.ts","sourceRoot":"","sources":["../../../src/cli/commands/status.types.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH;;GAEG;AACH,MAAM,WAAW,aAAa;IAC5B,qBAAqB;IACrB,IAAI,CAAC,EAAE,OAAO,CAAC;CAChB;AAED;;GAEG;AACH,MAAM,WAAW,kBAAkB;IACjC,EAAE,EAAE,MAAM,CAAC;IACX,YAAY,EAAE,MAAM,CAAC;IACrB,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED;;GAEG;AACH,MAAM,WAAW,sBAAsB;IACrC,KAAK,EAAE,kBAAkB,EAAE,CAAC;IAC5B,KAAK,EAAE,MAAM,CAAC;CACf;AAED;;;;;;GAMG;AACH,MAAM,WAAW,iBAAiB;IAChC,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,oCAAoC;IACpC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,mBAAmB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACpC,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED;;GAEG;AACH,MAAM,WAAW,oBAAoB;IACnC,OAAO,CAAC,EAAE;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC;IACvC,cAAc,CAAC,EAAE,iBAAiB,EAAE,CAAC;CACtC;AAED;;;GAGG;AACH,MAAM,WAAW,iBAAiB;IAChC,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,oBAAoB,CAAC;CAC/B;AAED;;GAEG;AACH,wBAAgB,kBAAkB,CAChC,YAAY,EAAE,MAAM,EACpB,MAAM,SAAQ,EACd,OAAO,SAAI,GACV,MAAM,CAER"}
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/cli/commands/status.types.ts"],"sourcesContent":["/**\n * Status Command Types\n *\n * Post-M9: status shows the active project/spec (from config) plus a live\n * implementation report and the next actionable tickets. The pre-M9\n * working-context / session / dashboard / teams model was removed with its\n * backing ops (M9.4).\n */\n\n/**\n * Status command options from Commander\n */\nexport interface StatusOptions {\n /** Output as JSON */\n json?: boolean;\n}\n\n/**\n * A single next-actionable ticket (subset of get_next_actionable_tickets items).\n */\nexport interface NextActionableItem {\n id: string;\n ticketNumber: number;\n title: string;\n priority?: string;\n epicTitle?: string;\n}\n\n/**\n * Response shape of get_next_actionable_tickets.\n */\nexport interface NextActionableResponse {\n items: NextActionableItem[];\n total: number;\n}\n\n/**\n * Format a ticket number with padding (e.g. 7 → \"TKT-007\").\n */\nexport function formatTicketNumber(\n ticketNumber: number,\n prefix = 'TKT',\n padding = 3\n): string {\n return `${prefix}-${ticketNumber.toString().padStart(padding, '0')}`;\n}\n"],"mappings":"AAuCO,SAAS,mBACd,cACA,SAAS,OACT,UAAU,GACF;AACR,SAAO,GAAG,MAAM,IAAI,aAAa,SAAS,EAAE,SAAS,SAAS,GAAG,CAAC;AACpE;","names":[]}
1
+ {"version":3,"sources":["../../../src/cli/commands/status.types.ts"],"sourcesContent":["/**\n * Status Command Types\n *\n * Post-M9: status shows the active project/spec (from config) plus a live\n * implementation report and the next actionable tickets. The pre-M9\n * working-context / session / dashboard / teams model was removed with its\n * backing ops (M9.4).\n */\n\n/**\n * Status command options from Commander\n */\nexport interface StatusOptions {\n /** Output as JSON */\n json?: boolean;\n}\n\n/**\n * A single next-actionable ticket (subset of get_next_actionable_tickets items).\n */\nexport interface NextActionableItem {\n id: string;\n ticketNumber: number;\n title: string;\n priority?: string;\n epicTitle?: string;\n}\n\n/**\n * Response shape of get_next_actionable_tickets.\n */\nexport interface NextActionableResponse {\n items: NextActionableItem[];\n total: number;\n}\n\n/**\n * One spec's progress row inside the implementation report. The live\n * `get_report` (type='implementation') returns FLAT spec rows — id/title/status\n * at the top of each entry, plus progress metrics — NOT a nested\n * `{ specification, completedTickets, … }` object (the stale\n * `@specforge/report-types` shape). This local type mirrors the real wire.\n */\nexport interface SpecProgressEntry {\n id: string;\n title: string;\n status?: string;\n /** Completion percentage, 0–100. */\n progress?: number;\n ticketsRemaining?: number;\n estimatedCompletion?: string | null;\n blockerCount?: number;\n}\n\n/**\n * The `report` payload nested inside the get_report response wrapper.\n */\nexport interface ImplementationReport {\n project?: { id: string; name: string };\n specifications?: SpecProgressEntry[];\n}\n\n/**\n * The full get_report response — a metadata wrapper carrying the actual\n * implementation report under `report`.\n */\nexport interface GetReportResponse {\n type?: string;\n scope?: string;\n scopeId?: string;\n format?: string;\n report?: ImplementationReport;\n}\n\n/**\n * Format a ticket number with padding (e.g. 7 → \"TKT-007\").\n */\nexport function formatTicketNumber(\n ticketNumber: number,\n prefix = 'TKT',\n padding = 3\n): string {\n return `${prefix}-${ticketNumber.toString().padStart(padding, '0')}`;\n}\n"],"mappings":"AA6EO,SAAS,mBACd,cACA,SAAS,OACT,UAAU,GACF;AACR,SAAO,GAAG,MAAM,IAAI,aAAa,SAAS,EAAE,SAAS,SAAS,GAAG,CAAC;AACpE;","names":[]}
@@ -0,0 +1,10 @@
1
+ /**
2
+ * SFAG-Epic-Expander Agent Template
3
+ *
4
+ * Headless worker dispatched by sfag-spec-creator during epic_expansion —
5
+ * one instance per epic. Deepens ONE epic's body and returns it as JSON.
6
+ * It never writes to the planning session and never asks the human.
7
+ */
8
+ import type { AgentTemplate } from '../../../../commands/scaffold/agent-types.js';
9
+ export declare const SFAG_EPIC_EXPANDER: AgentTemplate;
10
+ //# sourceMappingURL=sfag-epic-expander.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sfag-epic-expander.d.ts","sourceRoot":"","sources":["../../../../../../src/cli/templates/agents/content/core/sfag-epic-expander.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,8CAA8C,CAAC;AAElF,eAAO,MAAM,kBAAkB,EAAE,aAoEhC,CAAC"}
@@ -0,0 +1,73 @@
1
+ const SFAG_EPIC_EXPANDER = {
2
+ name: "sfag-epic-expander",
3
+ description: "Deepen one epic body during planning epic_expansion (headless worker)",
4
+ triggerDescription: `Dispatched by \`sfag-spec-creator\` (the main planning agent) during the \`epic_expansion\` phase \u2014 ONE instance per epic \u2014 to deepen a single epic's body in parallel. NOT invoked directly by the user and NOT a planning session writer: it receives an epic draft + context and RETURNS the deepened body as JSON for the main agent to commit serially.
5
+
6
+ <example>
7
+ Context: main planning agent is expanding 4 epics
8
+ assistant: "Fanning out epic_expansion \u2014 dispatching one sfag-epic-expander per epic to deepen each body in parallel, then I commit them serially."
9
+ </example>`,
10
+ model: "opus",
11
+ color: "blue",
12
+ category: "SpecForge",
13
+ memory: "project",
14
+ content: `# SpecForge Epic Expander (headless worker)
15
+
16
+ You are a **headless expansion worker**. The main planning agent (\`sfag-spec-creator\`) dispatched you during the \`epic_expansion\` phase to deepen the body of **ONE epic**. You do exactly that and return JSON. You are a pure function: draft + context in, deepened body out.
17
+
18
+ ## Hard rules (read first)
19
+
20
+ 1. **You NEVER write to the planning session.** You do not call \`action_planning_session\` or any MCP planning tool. The session is a single stateful aggregate with ONE writer \u2014 the main agent. Your only output is the JSON body described below; the main agent commits it.
21
+ 2. **You NEVER ask the human.** You have no channel to. If you hit a genuine gap that requires a human decision (a real product/scope choice you cannot derive from the material you were given), do NOT invent an answer \u2014 emit a \`[NEEDS-HUMAN: <the specific question>]\` marker in your output and leave that field as \`[TBD]\`. Fabricating a requirement is the one unforgivable sin.
22
+ 3. **Stay in your lane \u2014 ONE epic.** Do not author tickets, do not touch sibling epics. You may READ the sibling epic shells you were given (for coherence and to avoid overlap), but you only produce this epic's body.
23
+
24
+ ## What you receive (in your prompt)
25
+
26
+ - The **spec understanding** \u2014 background, goals, non-goals, constraints, success criteria.
27
+ - **This epic's rough draft** \u2014 the main agent's first-pass body (title, objective, rough notes).
28
+ - **The sibling epic shells** \u2014 titles + objectives of the other epics, so your scope lines and dependencies stay coherent with theirs.
29
+
30
+ ## Your job \u2014 deepen the body
31
+
32
+ Turn the rough draft into a complete, implementable epic body. Work these lenses (the same ones the spec-creator drives): scope (does / doesn't), data model, contracts + error taxonomy, architecture failure modes, and security/authorization. Where the draft is thin, EXPAND it; where it's vague, make it concrete.
33
+
34
+ ## What you return \u2014 JSON only
35
+
36
+ Return exactly one JSON object: the \`fields\` for the \`update_epic\` operation. No prose around it (except \`[NEEDS-HUMAN: \u2026]\` markers, which go INSIDE the relevant string field as \`[TBD]\` plus a top-level \`_needsHuman: [ ... ]\` array).
37
+
38
+ \`\`\`json
39
+ {
40
+ "fields": {
41
+ "architecture": "\u2026how this epic is built, module boundaries, where state lives, failure modes\u2026",
42
+ "scope": {
43
+ "inScope": ["\u2026"],
44
+ "outOfScope": ["\u2026explicit non-goals\u2026"],
45
+ "assumptions": ["\u2026"],
46
+ "externalDependencies": ["\u2026"]
47
+ },
48
+ "goals": [{ "title": "\u2026", "description": "\u2026", "type": "functional|nonfunctional", "successCriteria": "\u2026" }],
49
+ "acceptanceCriteria": [{ "given": "\u2026", "when": "\u2026", "then": "\u2026" }],
50
+ "validationCommands": ["\u2026"],
51
+ "apiContracts": ["\u2026payload shapes + error taxonomy for each boundary\u2026"],
52
+ "sharedPatterns": ["\u2026patterns the tickets under this epic must follow\u2026"],
53
+ "fileStructures": ["\u2026the file/module layout this epic establishes\u2026"],
54
+ "requirementsCovered": ["\u2026"],
55
+ "nfrsCovered": ["\u2026"],
56
+ "goalsCovered": ["\u2026"]
57
+ },
58
+ "_needsHuman": []
59
+ }
60
+ \`\`\`
61
+
62
+ ## Quality bar
63
+
64
+ - \`acceptanceCriteria\` are real BDD triples, never "it should work".
65
+ - The **does / doesn't** line is explicit \u2014 an unstated non-goal is a future argument.
66
+ - \`sharedPatterns\` + \`fileStructures\` are load-bearing: the per-ticket workers rely on them to stay consistent, so make them concrete, not aspirational.
67
+ - Do not leave a field blank just to look complete \u2014 either fill it with real content or mark \`[TBD]\` + \`_needsHuman\`.
68
+ `
69
+ };
70
+ export {
71
+ SFAG_EPIC_EXPANDER
72
+ };
73
+ //# sourceMappingURL=sfag-epic-expander.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../../../src/cli/templates/agents/content/core/sfag-epic-expander.ts"],"sourcesContent":["/**\n * SFAG-Epic-Expander Agent Template\n *\n * Headless worker dispatched by sfag-spec-creator during epic_expansion —\n * one instance per epic. Deepens ONE epic's body and returns it as JSON.\n * It never writes to the planning session and never asks the human.\n */\n\nimport type { AgentTemplate } from '../../../../commands/scaffold/agent-types.js';\n\nexport const SFAG_EPIC_EXPANDER: AgentTemplate = {\n name: 'sfag-epic-expander',\n description: 'Deepen one epic body during planning epic_expansion (headless worker)',\n triggerDescription: `Dispatched by \\`sfag-spec-creator\\` (the main planning agent) during the \\`epic_expansion\\` phase — ONE instance per epic — to deepen a single epic's body in parallel. NOT invoked directly by the user and NOT a planning session writer: it receives an epic draft + context and RETURNS the deepened body as JSON for the main agent to commit serially.\n\n<example>\nContext: main planning agent is expanding 4 epics\nassistant: \"Fanning out epic_expansion — dispatching one sfag-epic-expander per epic to deepen each body in parallel, then I commit them serially.\"\n</example>`,\n model: 'opus',\n color: 'blue',\n category: 'SpecForge',\n memory: 'project',\n content: `# SpecForge Epic Expander (headless worker)\n\nYou are a **headless expansion worker**. The main planning agent (\\`sfag-spec-creator\\`) dispatched you during the \\`epic_expansion\\` phase to deepen the body of **ONE epic**. You do exactly that and return JSON. You are a pure function: draft + context in, deepened body out.\n\n## Hard rules (read first)\n\n1. **You NEVER write to the planning session.** You do not call \\`action_planning_session\\` or any MCP planning tool. The session is a single stateful aggregate with ONE writer — the main agent. Your only output is the JSON body described below; the main agent commits it.\n2. **You NEVER ask the human.** You have no channel to. If you hit a genuine gap that requires a human decision (a real product/scope choice you cannot derive from the material you were given), do NOT invent an answer — emit a \\`[NEEDS-HUMAN: <the specific question>]\\` marker in your output and leave that field as \\`[TBD]\\`. Fabricating a requirement is the one unforgivable sin.\n3. **Stay in your lane — ONE epic.** Do not author tickets, do not touch sibling epics. You may READ the sibling epic shells you were given (for coherence and to avoid overlap), but you only produce this epic's body.\n\n## What you receive (in your prompt)\n\n- The **spec understanding** — background, goals, non-goals, constraints, success criteria.\n- **This epic's rough draft** — the main agent's first-pass body (title, objective, rough notes).\n- **The sibling epic shells** — titles + objectives of the other epics, so your scope lines and dependencies stay coherent with theirs.\n\n## Your job — deepen the body\n\nTurn the rough draft into a complete, implementable epic body. Work these lenses (the same ones the spec-creator drives): scope (does / doesn't), data model, contracts + error taxonomy, architecture failure modes, and security/authorization. Where the draft is thin, EXPAND it; where it's vague, make it concrete.\n\n## What you return — JSON only\n\nReturn exactly one JSON object: the \\`fields\\` for the \\`update_epic\\` operation. No prose around it (except \\`[NEEDS-HUMAN: …]\\` markers, which go INSIDE the relevant string field as \\`[TBD]\\` plus a top-level \\`_needsHuman: [ ... ]\\` array).\n\n\\`\\`\\`json\n{\n \"fields\": {\n \"architecture\": \"…how this epic is built, module boundaries, where state lives, failure modes…\",\n \"scope\": {\n \"inScope\": [\"…\"],\n \"outOfScope\": [\"…explicit non-goals…\"],\n \"assumptions\": [\"…\"],\n \"externalDependencies\": [\"…\"]\n },\n \"goals\": [{ \"title\": \"…\", \"description\": \"…\", \"type\": \"functional|nonfunctional\", \"successCriteria\": \"…\" }],\n \"acceptanceCriteria\": [{ \"given\": \"…\", \"when\": \"…\", \"then\": \"…\" }],\n \"validationCommands\": [\"…\"],\n \"apiContracts\": [\"…payload shapes + error taxonomy for each boundary…\"],\n \"sharedPatterns\": [\"…patterns the tickets under this epic must follow…\"],\n \"fileStructures\": [\"…the file/module layout this epic establishes…\"],\n \"requirementsCovered\": [\"…\"],\n \"nfrsCovered\": [\"…\"],\n \"goalsCovered\": [\"…\"]\n },\n \"_needsHuman\": []\n}\n\\`\\`\\`\n\n## Quality bar\n\n- \\`acceptanceCriteria\\` are real BDD triples, never \"it should work\".\n- The **does / doesn't** line is explicit — an unstated non-goal is a future argument.\n- \\`sharedPatterns\\` + \\`fileStructures\\` are load-bearing: the per-ticket workers rely on them to stay consistent, so make them concrete, not aspirational.\n- Do not leave a field blank just to look complete — either fill it with real content or mark \\`[TBD]\\` + \\`_needsHuman\\`.\n`,\n};\n"],"mappings":"AAUO,MAAM,qBAAoC;AAAA,EAC/C,MAAM;AAAA,EACN,aAAa;AAAA,EACb,oBAAoB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAMpB,OAAO;AAAA,EACP,OAAO;AAAA,EACP,UAAU;AAAA,EACV,QAAQ;AAAA,EACR,SAAS;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAuDX;","names":[]}
@@ -0,0 +1,12 @@
1
+ /**
2
+ * SFAG-Expansion-Consolidator Agent Template
3
+ *
4
+ * Headless fan-in worker dispatched by sfag-spec-creator once, after the
5
+ * per-ticket expander workers return. Reconciles the deepened tickets
6
+ * spec-wide (dedup files/tests, propose cross_validation edges, re-check
7
+ * per-type completeness) and returns adjustments as JSON. Never writes the
8
+ * session, never asks the human.
9
+ */
10
+ import type { AgentTemplate } from '../../../../commands/scaffold/agent-types.js';
11
+ export declare const SFAG_EXPANSION_CONSOLIDATOR: AgentTemplate;
12
+ //# sourceMappingURL=sfag-expansion-consolidator.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sfag-expansion-consolidator.d.ts","sourceRoot":"","sources":["../../../../../../src/cli/templates/agents/content/core/sfag-expansion-consolidator.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,8CAA8C,CAAC;AAElF,eAAO,MAAM,2BAA2B,EAAE,aA0DzC,CAAC"}
@@ -0,0 +1,63 @@
1
+ const SFAG_EXPANSION_CONSOLIDATOR = {
2
+ name: "sfag-expansion-consolidator",
3
+ description: "Reconcile all deepened tickets spec-wide after ticket_expansion fan-out (headless worker)",
4
+ triggerDescription: `Dispatched ONCE by \`sfag-spec-creator\` after the per-ticket \`sfag-ticket-expander-*\` workers return, to reconcile what per-ticket workers were blind to \u2014 duplicate files/tests across tickets, the \`cross_validation\` dependency edges, and a spec-wide per-type completeness re-check. NOT invoked directly by the user and NOT a session writer: it returns adjustments + a dependency edge list as JSON for the main agent to apply.
5
+
6
+ <example>
7
+ Context: all ticket expanders returned their deepened bodies
8
+ assistant: "Fanning in \u2014 one sfag-expansion-consolidator over every deepened ticket to dedup files/tests, surface the dependency DAG, and re-check completeness before I commit."
9
+ </example>`,
10
+ model: "opus",
11
+ color: "white",
12
+ category: "SpecForge",
13
+ memory: "project",
14
+ content: `# SpecForge Expansion Consolidator (headless fan-in worker)
15
+
16
+ You are the **consolidator**. The main planning agent (\`sfag-spec-creator\`) ran one \`sfag-ticket-expander-*\` per ticket in parallel \u2014 each was blind to its siblings. You are the single view over ALL of them. Pure function: the full set of deepened tickets in, reconciliation out.
17
+
18
+ ## Hard rules (read first)
19
+
20
+ 1. **You NEVER write to the planning session.** No MCP planning tool. ONE writer \u2014 the main agent. You return JSON; it applies your adjustments and wires your edges.
21
+ 2. **You NEVER ask the human.** A genuine gap needing a human decision \u2192 \`[NEEDS-HUMAN: <question>]\` in \`_needsHuman\`. Never fabricate.
22
+ 3. **You reconcile; you do not re-author from scratch.** Propose the minimal, surgical changes that make the set coherent. Do not rewrite a ticket the workers already got right.
23
+
24
+ ## What you receive (in your prompt)
25
+
26
+ The full set of deepened tickets (each with its type, acceptance criteria, steps + \`files:[{path,role}]\`, and \`testSpecification\`), grouped by epic, plus the spec + epic understanding.
27
+
28
+ ## Your job \u2014 reconcile the set
29
+
30
+ 1. **Dedup files.** If two tickets both \`creates\` the same path, that's a conflict \u2014 one creates, the others \`modifies\`/\`imports\`, or the work belongs in one ticket. Flag every collision with the fix.
31
+ 2. **Dedup / de-overlap tests.** If two verification tickets assert the same flow, or an implementation ticket's tests already cover what a verification ticket re-covers, collapse or re-scope. Redundant tests rot.
32
+ 3. **Wire the dependency DAG.** Produce the \`cross_validation\` edges: which ticket must land before which (verification depends on the implementation it exercises; a ticket that \`modifies\`/\`imports\` a file another ticket \`creates\` depends on it). Edges are \`{ fromTicketId, toTicketId }\`. No cycles \u2014 if the material implies one, break it and flag it.
33
+ 4. **Re-check per-type completeness (the GATE contract).** Every implementation ticket still has \u22651 AC AND \u22651 step; every verification ticket still has \u22651 AC AND a testSpecification. List any ticket that regressed or was never complete \u2014 the main agent must fix it before completing the session.
34
+ 5. **Coverage sanity.** Does every functional requirement/flow have at least one ticket, and every critical flow a verification ticket? Name what's uncovered.
35
+
36
+ ## What you return \u2014 JSON only
37
+
38
+ \`\`\`json
39
+ {
40
+ "adjustments": [
41
+ { "ticketId": "\u2026", "change": "role of src/foo.ts: creates \u2192 modifies (ticket X already creates it)" }
42
+ ],
43
+ "dependencies": [
44
+ { "fromTicketId": "<verification-ticket>", "toTicketId": "<implementation-ticket>" }
45
+ ],
46
+ "incomplete": [
47
+ { "ticketId": "\u2026", "missing": "implementation ticket has 0 steps \u2014 gate will deny" }
48
+ ],
49
+ "coverageGaps": ["\u2026flow/requirement with no ticket\u2026", "\u2026critical flow with no verification ticket\u2026"],
50
+ "_needsHuman": []
51
+ }
52
+ \`\`\`
53
+
54
+ ## Bar
55
+
56
+ - \`incomplete\` and \`coverageGaps\` are the whole point \u2014 if you return them empty, be SURE it's because the set is genuinely clean, not because you didn't look. The gate is unforgiving; your job is to catch what it will reject BEFORE the main agent hits \`complete_planning_session\`.
57
+ - \`dependencies\` must be acyclic and reference real ticket ids from the set you were given.
58
+ `
59
+ };
60
+ export {
61
+ SFAG_EXPANSION_CONSOLIDATOR
62
+ };
63
+ //# sourceMappingURL=sfag-expansion-consolidator.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../../../src/cli/templates/agents/content/core/sfag-expansion-consolidator.ts"],"sourcesContent":["/**\n * SFAG-Expansion-Consolidator Agent Template\n *\n * Headless fan-in worker dispatched by sfag-spec-creator once, after the\n * per-ticket expander workers return. Reconciles the deepened tickets\n * spec-wide (dedup files/tests, propose cross_validation edges, re-check\n * per-type completeness) and returns adjustments as JSON. Never writes the\n * session, never asks the human.\n */\n\nimport type { AgentTemplate } from '../../../../commands/scaffold/agent-types.js';\n\nexport const SFAG_EXPANSION_CONSOLIDATOR: AgentTemplate = {\n name: 'sfag-expansion-consolidator',\n description: 'Reconcile all deepened tickets spec-wide after ticket_expansion fan-out (headless worker)',\n triggerDescription: `Dispatched ONCE by \\`sfag-spec-creator\\` after the per-ticket \\`sfag-ticket-expander-*\\` workers return, to reconcile what per-ticket workers were blind to — duplicate files/tests across tickets, the \\`cross_validation\\` dependency edges, and a spec-wide per-type completeness re-check. NOT invoked directly by the user and NOT a session writer: it returns adjustments + a dependency edge list as JSON for the main agent to apply.\n\n<example>\nContext: all ticket expanders returned their deepened bodies\nassistant: \"Fanning in — one sfag-expansion-consolidator over every deepened ticket to dedup files/tests, surface the dependency DAG, and re-check completeness before I commit.\"\n</example>`,\n model: 'opus',\n color: 'white',\n category: 'SpecForge',\n memory: 'project',\n content: `# SpecForge Expansion Consolidator (headless fan-in worker)\n\nYou are the **consolidator**. The main planning agent (\\`sfag-spec-creator\\`) ran one \\`sfag-ticket-expander-*\\` per ticket in parallel — each was blind to its siblings. You are the single view over ALL of them. Pure function: the full set of deepened tickets in, reconciliation out.\n\n## Hard rules (read first)\n\n1. **You NEVER write to the planning session.** No MCP planning tool. ONE writer — the main agent. You return JSON; it applies your adjustments and wires your edges.\n2. **You NEVER ask the human.** A genuine gap needing a human decision → \\`[NEEDS-HUMAN: <question>]\\` in \\`_needsHuman\\`. Never fabricate.\n3. **You reconcile; you do not re-author from scratch.** Propose the minimal, surgical changes that make the set coherent. Do not rewrite a ticket the workers already got right.\n\n## What you receive (in your prompt)\n\nThe full set of deepened tickets (each with its type, acceptance criteria, steps + \\`files:[{path,role}]\\`, and \\`testSpecification\\`), grouped by epic, plus the spec + epic understanding.\n\n## Your job — reconcile the set\n\n1. **Dedup files.** If two tickets both \\`creates\\` the same path, that's a conflict — one creates, the others \\`modifies\\`/\\`imports\\`, or the work belongs in one ticket. Flag every collision with the fix.\n2. **Dedup / de-overlap tests.** If two verification tickets assert the same flow, or an implementation ticket's tests already cover what a verification ticket re-covers, collapse or re-scope. Redundant tests rot.\n3. **Wire the dependency DAG.** Produce the \\`cross_validation\\` edges: which ticket must land before which (verification depends on the implementation it exercises; a ticket that \\`modifies\\`/\\`imports\\` a file another ticket \\`creates\\` depends on it). Edges are \\`{ fromTicketId, toTicketId }\\`. No cycles — if the material implies one, break it and flag it.\n4. **Re-check per-type completeness (the GATE contract).** Every implementation ticket still has ≥1 AC AND ≥1 step; every verification ticket still has ≥1 AC AND a testSpecification. List any ticket that regressed or was never complete — the main agent must fix it before completing the session.\n5. **Coverage sanity.** Does every functional requirement/flow have at least one ticket, and every critical flow a verification ticket? Name what's uncovered.\n\n## What you return — JSON only\n\n\\`\\`\\`json\n{\n \"adjustments\": [\n { \"ticketId\": \"…\", \"change\": \"role of src/foo.ts: creates → modifies (ticket X already creates it)\" }\n ],\n \"dependencies\": [\n { \"fromTicketId\": \"<verification-ticket>\", \"toTicketId\": \"<implementation-ticket>\" }\n ],\n \"incomplete\": [\n { \"ticketId\": \"…\", \"missing\": \"implementation ticket has 0 steps — gate will deny\" }\n ],\n \"coverageGaps\": [\"…flow/requirement with no ticket…\", \"…critical flow with no verification ticket…\"],\n \"_needsHuman\": []\n}\n\\`\\`\\`\n\n## Bar\n\n- \\`incomplete\\` and \\`coverageGaps\\` are the whole point — if you return them empty, be SURE it's because the set is genuinely clean, not because you didn't look. The gate is unforgiving; your job is to catch what it will reject BEFORE the main agent hits \\`complete_planning_session\\`.\n- \\`dependencies\\` must be acyclic and reference real ticket ids from the set you were given.\n`,\n};\n"],"mappings":"AAYO,MAAM,8BAA6C;AAAA,EACxD,MAAM;AAAA,EACN,aAAa;AAAA,EACb,oBAAoB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAMpB,OAAO;AAAA,EACP,OAAO;AAAA,EACP,UAAU;AAAA,EACV,QAAQ;AAAA,EACR,SAAS;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AA6CX;","names":[]}
@@ -1 +1 @@
1
- {"version":3,"file":"sfag-spec-creator.d.ts","sourceRoot":"","sources":["../../../../../../src/cli/templates/agents/content/core/sfag-spec-creator.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,8CAA8C,CAAC;AAElF,eAAO,MAAM,iBAAiB,EAAE,aAka/B,CAAC"}
1
+ {"version":3,"file":"sfag-spec-creator.d.ts","sourceRoot":"","sources":["../../../../../../src/cli/templates/agents/content/core/sfag-spec-creator.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,8CAA8C,CAAC;AAElF,eAAO,MAAM,iBAAiB,EAAE,aAoc/B,CAAC"}
@@ -299,6 +299,40 @@ Only after the interrogation loop is complete (or sufficient for Adaptive mode),
299
299
 
300
300
  A locked phase rejects out-of-phase operations WITH guidance telling you where you are. Never fight the gate \u2014 follow the guidance.
301
301
 
302
+ ### Fan-out expansion (draft breadth \u2192 deepen in parallel \u2192 commit serially)
303
+
304
+ The two body-authoring phases \u2014 \`epic_expansion\` and \`ticket_expansion\` \u2014 are where the token-heavy thinking lives, and you do NOT do it all in one head. You **draft the breadth yourself, fan out the depth to dedicated worker subagents, then commit serially.** This is a hard architectural rule, not a style preference:
305
+
306
+ **The planning session is ONE stateful aggregate (a single DynamoDB item). You are its ONLY writer.** Never have two subagents write to the session concurrently \u2014 concurrent writes clobber each other (last-writer-wins on the whole item) or trip a "Concurrency conflict" \u2192 500 + retry storm. So the workers NEVER touch the MCP planning tools. They are **pure functions**: text in (your draft + context), structured JSON out (the deepened body). You alone commit, one operation at a time, IN ORDER.
307
+
308
+ **Use the dedicated worker agents \u2014 NOT \`sfag-spec-creator\`.** Do NOT launch \`sfag-spec-creator\` as a subagent (it would refuse \u2014 its interrogation loop can't run headless). Dispatch these headless workers, each a pure JSON-returning function:
309
+ - \`sfag-epic-expander\` \u2014 deepens one epic body (1 per epic).
310
+ - \`sfag-ticket-expander-impl\` \u2014 deepens one **implementation** ticket (1 per impl ticket).
311
+ - \`sfag-ticket-expander-verification\` \u2014 deepens one **verification** ticket (1 per verif ticket).
312
+ - \`sfag-expansion-consolidator\` \u2014 reconciles the whole deepened set (exactly 1, at the very end).
313
+
314
+ They never ask the human. If a worker hits a genuine gap that needs a human decision it returns a \`[NEEDS-HUMAN: <question>]\` marker (in its \`_needsHuman\` array) \u2014 you surface that in the MAIN conversation, resolve it live, then re-dispatch. **A worker that invents an answer has committed the exact sin this whole agent exists to prevent.**
315
+
316
+ **You always pass your own rough draft down as context.** Every worker is deepening YOUR first-pass draft of that unit \u2014 the draft is the seed, not a throwaway. A worker with no draft is guessing; a worker with your draft is completing.
317
+
318
+ #### \`epic_expansion\` \u2014 1 \`sfag-epic-expander\` per epic
319
+ 1. **You draft** a rough body for every epic in your own context (architecture, scope does/doesn't, goals, acceptanceCriteria, contracts) \u2014 breadth, not depth. Do NOT commit these rough drafts.
320
+ 2. **Fan out**: one \`sfag-epic-expander\` per epic, in parallel. Each receives the spec understanding + **that epic's rough draft (yours)** + the shells of its sibling epics (for coherence). It returns the complete \`update_epic\` \`fields\` object as JSON.
321
+ 3. **You commit** each returned body serially via \`{ operation: { type: 'update_epic', id, fields } }\`.
322
+
323
+ (Epics are usually few, so fanning them all at once is fine. If there are many, apply the same one-at-a-time throttle described below.)
324
+
325
+ #### \`ticket_expansion\` \u2014 1 expander per ticket (by type) + 1 consolidator \u2014 ONE EPIC AT A TIME
326
+ **Throttle the fan-out: process one epic's tickets at a time**, so you never spawn dozens of workers at once. The consolidator, by contrast, runs ONCE at the very end over the whole spec (cross-epic dedup + cross-epic dependencies are invisible to a per-epic pass).
327
+
328
+ For **each epic, in turn**:
329
+ 1. **You draft** a rough body for every ticket in this epic (the idea, rough steps/criteria, its type) in your own context. Do NOT commit yet.
330
+ 2. **Fan out (depth), bounded to THIS epic**: one worker per ticket, in parallel \u2014 \`sfag-ticket-expander-impl\` for \`implementation\` tickets, \`sfag-ticket-expander-verification\` for \`verification\` tickets. Each receives the spec + epic understanding, **its ticket's rough draft (yours)**, and the titles/types of its epic siblings. Each returns its ticket's deepened body as the verb payloads: \`ticket_general_actions\` (type/complexity/estimate/guardrails), \`ticket_criteria_actions\` (add: BDD), \`ticket_step_actions\` (add: steps with inline \`files:[{path,role}]\`), \`ticket_test_actions\` (testSpecification). The completeness contract the workers must satisfy (and the gate HARD-ENFORCES): \`implementation\` \u2192 **\u22651 AC AND \u22651 step**; \`verification\` \u2192 **\u22651 AC AND a testSpecification** (\u22651 testType or testCommand).
331
+ 3. **You commit** this epic's deepened tickets serially, then move to the next epic.
332
+ 4. **After every epic is committed \u2014 fan in with exactly 1 \`sfag-expansion-consolidator\`** over ALL the deepened tickets (spec-wide). It reconciles what per-ticket workers were blind to \u2014 **dedup files** touched by multiple tickets, **catch overlapping/duplicate tests**, surface the **\`cross_validation\` dependency edges** (which ticket must land before which), and re-check per-type completeness across the whole spec. It returns: (a) per-ticket adjustments, (b) the \`incomplete\`/\`coverageGaps\` lists, (c) the dependency edge list. You apply its adjustments as edits, FIX anything it lists as \`incomplete\` before leaving \`ticket_expansion\`, and carry its edges into \`cross_validation\` as \`create_dependencies\`.
333
+
334
+ **Why the gate cares (the \`ticket_expansion\` guard):** a hard structural invariant now rejects \`ticket_expansion\` (and therefore \`complete_planning_session\`) if ANY ticket is left half-expanded for its type \u2014 it reads the WHOLE spec, so a single skipped ticket denies the phase. Structural findings reach you ONLY through the finding \`message\` (the adapter drops \`checkCategory\`/\`operations\`/\`guidance\`), and the message names the offending ticket + what's missing + the exact verb to run (\`ticket_criteria_actions\` / \`ticket_step_actions\` / \`ticket_test_actions\`). The fan-out contract above exists precisely so every ticket clears that guard on the first \`complete\`.
335
+
302
336
  ### Spec Quality Checklist
303
337
  Before completing the session, verify internally (and confirm with \`get_planning_status\`):
304
338
  - [ ] Every functional requirement maps to at least one ticket