substrate-ai 0.19.9 → 0.19.11

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.
package/dist/cli/index.js CHANGED
@@ -4,7 +4,7 @@ import { createLogger } from "../logger-KeHncl-f.js";
4
4
  import { createEventBus } from "../helpers-CElYrONe.js";
5
5
  import { AdapterRegistry, BudgetConfigSchema, CURRENT_CONFIG_FORMAT_VERSION, CURRENT_TASK_GRAPH_VERSION, ConfigError, CostTrackerConfigSchema, DEFAULT_CONFIG, DoltClient, DoltNotInstalled, GlobalSettingsSchema, IngestionServer, MonitorDatabaseImpl, OPERATIONAL_FINDING, PartialGlobalSettingsSchema, PartialProviderConfigSchema, ProvidersSchema, RoutingRecommender, STORY_METRICS, TelemetryConfigSchema, addTokenUsage, aggregateTokenUsageForRun, checkDoltInstalled, compareRunMetrics, createAmendmentRun, createConfigSystem, createDecision, createDoltClient, createPipelineRun, getActiveDecisions, getAllCostEntriesFiltered, getBaselineRunMetrics, getDecisionsByCategory, getDecisionsByPhaseForRun, getLatestCompletedRun, getLatestRun, getPipelineRunById, getPlanningCostTotal, getRetryableEscalations, getRunMetrics, getSessionCostSummary, getSessionCostSummaryFiltered, getStoryMetricsForRun, getTokenUsageSummary, incrementRunRestarts, initSchema, initializeDolt, listRunMetrics, loadParentRunDecisions, supersedeDecision, tagRunAsBaseline, updatePipelineRun } from "../dist-adzGUKPc.js";
6
6
  import "../adapter-registry-DXLMTmfD.js";
7
- import { AdapterTelemetryPersistence, AppError, DoltRepoMapMetaRepository, DoltSymbolRepository, ERR_REPO_MAP_STORAGE_WRITE, EpicIngester, GitClient, GrammarLoader, RepoMapInjector, RepoMapModule, RepoMapQueryEngine, RepoMapStorage, SymbolParser, createContextCompiler, createDispatcher, createEventEmitter, createImplementationOrchestrator, createPackLoader, createPhaseOrchestrator, createStopAfterGate, createTelemetryAdvisor, formatPhaseCompletionSummary, getFactoryRunSummaries, getScenarioResultsForRun, getTwinRunsForRun, listGraphRuns, registerExportCommand, registerFactoryCommand, registerRunCommand, registerScenariosCommand, resolveStoryKeys, runAnalysisPhase, runPlanningPhase, runSolutioningPhase, validateStopAfterFromConflict } from "../run-Dg_BEJB6.js";
7
+ import { AdapterTelemetryPersistence, AppError, DoltRepoMapMetaRepository, DoltSymbolRepository, ERR_REPO_MAP_STORAGE_WRITE, EpicIngester, GitClient, GrammarLoader, RepoMapInjector, RepoMapModule, RepoMapQueryEngine, RepoMapStorage, SymbolParser, createContextCompiler, createDispatcher, createEventEmitter, createImplementationOrchestrator, createPackLoader, createPhaseOrchestrator, createStopAfterGate, createTelemetryAdvisor, formatPhaseCompletionSummary, getFactoryRunSummaries, getScenarioResultsForRun, getTwinRunsForRun, listGraphRuns, registerExportCommand, registerFactoryCommand, registerRunCommand, registerScenariosCommand, resolveStoryKeys, runAnalysisPhase, runPlanningPhase, runSolutioningPhase, validateStopAfterFromConflict } from "../run-BLxcApKZ.js";
8
8
  import "../errors-CZdr5Wqb.js";
9
9
  import "../routing-CcBOCuC9.js";
10
10
  import "../decisions-C0pz9Clx.js";
@@ -1155,7 +1155,8 @@ const SubstrateConfigSchema = z.object({
1155
1155
  budget: BudgetConfigSchema.optional(),
1156
1156
  token_ceilings: TokenCeilingsSchema.optional(),
1157
1157
  dispatch_timeouts: DispatchTimeoutsSchema.optional(),
1158
- telemetry: TelemetryConfigSchema.optional()
1158
+ telemetry: TelemetryConfigSchema.optional(),
1159
+ default_agent: z.string().optional()
1159
1160
  }).strict();
1160
1161
  const PartialSubstrateConfigSchema = z.object({
1161
1162
  config_format_version: z.enum(["1"]).optional(),
@@ -1170,7 +1171,8 @@ const PartialSubstrateConfigSchema = z.object({
1170
1171
  budget: BudgetConfigSchema.partial().optional(),
1171
1172
  token_ceilings: TokenCeilingsSchema.optional(),
1172
1173
  dispatch_timeouts: DispatchTimeoutsSchema.optional(),
1173
- telemetry: TelemetryConfigSchema.partial().optional()
1174
+ telemetry: TelemetryConfigSchema.partial().optional(),
1175
+ default_agent: z.string().optional()
1174
1176
  }).strict();
1175
1177
 
1176
1178
  //#endregion
@@ -2676,7 +2678,7 @@ function mapInternalPhaseToEventPhase(internalPhase) {
2676
2678
  }
2677
2679
  }
2678
2680
  async function runResumeAction(options) {
2679
- const { runId: specifiedRunId, stopAfter, outputFormat, projectRoot, concurrency, pack: packName, events: eventsFlag, registry, maxReviewCycles = 2 } = options;
2681
+ const { runId: specifiedRunId, stopAfter, outputFormat, projectRoot, concurrency, pack: packName, events: eventsFlag, registry, maxReviewCycles = 2, agent: agentId$1 } = options;
2680
2682
  if (stopAfter !== void 0 && !VALID_PHASES.includes(stopAfter)) {
2681
2683
  const errorMsg = `Invalid phase: "${stopAfter}". Valid phases: ${VALID_PHASES.join(", ")}`;
2682
2684
  if (outputFormat === "json") process.stdout.write(formatOutput(null, "json", false, errorMsg) + "\n");
@@ -2836,7 +2838,7 @@ async function runFullPipelineFromPhase(options) {
2836
2838
  const costUsd = (result.tokenUsage.input * 3 + result.tokenUsage.output * 15) / 1e6;
2837
2839
  await addTokenUsage(adapter, runId, {
2838
2840
  phase: "analysis",
2839
- agent: "claude-code",
2841
+ agent: agentId ?? "claude-code",
2840
2842
  input_tokens: result.tokenUsage.input,
2841
2843
  output_tokens: result.tokenUsage.output,
2842
2844
  cost_usd: costUsd
@@ -2856,7 +2858,7 @@ async function runFullPipelineFromPhase(options) {
2856
2858
  const costUsd = (result.tokenUsage.input * 3 + result.tokenUsage.output * 15) / 1e6;
2857
2859
  await addTokenUsage(adapter, runId, {
2858
2860
  phase: "planning",
2859
- agent: "claude-code",
2861
+ agent: agentId ?? "claude-code",
2860
2862
  input_tokens: result.tokenUsage.input,
2861
2863
  output_tokens: result.tokenUsage.output,
2862
2864
  cost_usd: costUsd
@@ -2876,7 +2878,7 @@ async function runFullPipelineFromPhase(options) {
2876
2878
  const costUsd = (result.tokenUsage.input * 3 + result.tokenUsage.output * 15) / 1e6;
2877
2879
  await addTokenUsage(adapter, runId, {
2878
2880
  phase: "solutioning",
2879
- agent: "claude-code",
2881
+ agent: agentId ?? "claude-code",
2880
2882
  input_tokens: result.tokenUsage.input,
2881
2883
  output_tokens: result.tokenUsage.output,
2882
2884
  cost_usd: costUsd
@@ -2917,6 +2919,7 @@ async function runFullPipelineFromPhase(options) {
2917
2919
  enableHeartbeat: eventsFlag === true
2918
2920
  },
2919
2921
  projectRoot,
2922
+ agentId,
2920
2923
  ...ingestionServer !== void 0 ? { ingestionServer } : {},
2921
2924
  ...telemetryPersistence !== void 0 ? { telemetryPersistence } : {}
2922
2925
  });
@@ -3005,7 +3008,7 @@ async function runFullPipelineFromPhase(options) {
3005
3008
  const costUsd = (input * 3 + output * 15) / 1e6;
3006
3009
  addTokenUsage(adapter, runId, {
3007
3010
  phase: payload.phase,
3008
- agent: "claude-code",
3011
+ agent: agentId ?? "claude-code",
3009
3012
  input_tokens: input,
3010
3013
  output_tokens: output,
3011
3014
  cost_usd: costUsd
@@ -3092,7 +3095,7 @@ async function runFullPipelineFromPhase(options) {
3092
3095
  }
3093
3096
  }
3094
3097
  function registerResumeCommand(program, _version = "0.0.0", projectRoot = process.cwd(), registry) {
3095
- program.command("resume").description("Resume a previously interrupted pipeline run").option("--run-id <id>", "Pipeline run ID to resume (defaults to latest)").option("--pack <name>", "Methodology pack name", "bmad").option("--stop-after <phase>", "Stop pipeline after this phase completes (overrides saved state)").option("--concurrency <n>", "Maximum parallel conflict groups", (v) => parseInt(v, 10), 3).option("--project-root <path>", "Project root directory", projectRoot).option("--output-format <format>", "Output format: human (default) or json", "human").option("--events", "Emit structured NDJSON events on stdout for programmatic consumption").option("--max-review-cycles <n>", "Maximum review cycles per story (default: 2)", (v) => parseInt(v, 10), 2).action(async (opts) => {
3098
+ program.command("resume").description("Resume a previously interrupted pipeline run").option("--run-id <id>", "Pipeline run ID to resume (defaults to latest)").option("--pack <name>", "Methodology pack name", "bmad").option("--stop-after <phase>", "Stop pipeline after this phase completes (overrides saved state)").option("--concurrency <n>", "Maximum parallel conflict groups", (v) => parseInt(v, 10), 3).option("--project-root <path>", "Project root directory", projectRoot).option("--output-format <format>", "Output format: human (default) or json", "human").option("--events", "Emit structured NDJSON events on stdout for programmatic consumption").option("--max-review-cycles <n>", "Maximum review cycles per story (default: 2)", (v) => parseInt(v, 10), 2).option("--agent <id>", "Agent backend: claude-code (default), codex, or gemini").action(async (opts) => {
3096
3099
  const outputFormat = opts.outputFormat === "json" ? "json" : "human";
3097
3100
  const exitCode = await runResumeAction({
3098
3101
  runId: opts.runId,
@@ -3103,6 +3106,7 @@ function registerResumeCommand(program, _version = "0.0.0", projectRoot = proces
3103
3106
  pack: opts.pack,
3104
3107
  events: opts.events,
3105
3108
  maxReviewCycles: opts.maxReviewCycles,
3109
+ agent: opts.agent,
3106
3110
  registry
3107
3111
  });
3108
3112
  process.exitCode = exitCode;
@@ -3746,7 +3750,7 @@ async function runPostPhaseSupersessionDetection(adapter, amendmentRunId, curren
3746
3750
  }
3747
3751
  }
3748
3752
  async function runAmendAction(options) {
3749
- const { concept: conceptArg, conceptFile, runId: specifiedRunId, stopAfter, from: startPhase, projectRoot, pack: packName, registry: injectedRegistry } = options;
3753
+ const { concept: conceptArg, conceptFile, runId: specifiedRunId, stopAfter, from: startPhase, projectRoot, pack: packName, registry: injectedRegistry, agent: agentId$1 } = options;
3750
3754
  let concept;
3751
3755
  if (conceptFile !== void 0 && conceptFile !== "") try {
3752
3756
  concept = await readFile(conceptFile, "utf-8");
@@ -3844,7 +3848,8 @@ async function runAmendAction(options) {
3844
3848
  db: adapter,
3845
3849
  pack,
3846
3850
  contextCompiler,
3847
- dispatcher
3851
+ dispatcher,
3852
+ agentId: agentId$1
3848
3853
  };
3849
3854
  const phaseOrder = [
3850
3855
  "analysis",
@@ -3888,7 +3893,7 @@ async function runAmendAction(options) {
3888
3893
  const costUsd = (result.tokenUsage.input * 3 + result.tokenUsage.output * 15) / 1e6;
3889
3894
  await addTokenUsage(adapter, amendmentRunId, {
3890
3895
  phase: "analysis",
3891
- agent: "claude-code",
3896
+ agent: agentId$1 ?? "claude-code",
3892
3897
  input_tokens: result.tokenUsage.input,
3893
3898
  output_tokens: result.tokenUsage.output,
3894
3899
  cost_usd: costUsd
@@ -3910,7 +3915,7 @@ async function runAmendAction(options) {
3910
3915
  const costUsd = (result.tokenUsage.input * 3 + result.tokenUsage.output * 15) / 1e6;
3911
3916
  await addTokenUsage(adapter, amendmentRunId, {
3912
3917
  phase: "planning",
3913
- agent: "claude-code",
3918
+ agent: agentId$1 ?? "claude-code",
3914
3919
  input_tokens: result.tokenUsage.input,
3915
3920
  output_tokens: result.tokenUsage.output,
3916
3921
  cost_usd: costUsd
@@ -3932,7 +3937,7 @@ async function runAmendAction(options) {
3932
3937
  const costUsd = (result.tokenUsage.input * 3 + result.tokenUsage.output * 15) / 1e6;
3933
3938
  await addTokenUsage(adapter, amendmentRunId, {
3934
3939
  phase: "solutioning",
3935
- agent: "claude-code",
3940
+ agent: agentId$1 ?? "claude-code",
3936
3941
  input_tokens: result.tokenUsage.input,
3937
3942
  output_tokens: result.tokenUsage.output,
3938
3943
  cost_usd: costUsd
@@ -4003,7 +4008,7 @@ async function runAmendAction(options) {
4003
4008
  }
4004
4009
  }
4005
4010
  function registerAmendCommand(program, _version = "0.0.0", projectRoot = process.cwd(), registry) {
4006
- program.command("amend").description("Run an amendment pipeline against a completed run and an existing run").option("--concept <text>", "Amendment concept description (inline)").option("--concept-file <path>", "Path to concept file").option("--run-id <id>", "Parent run ID (defaults to latest completed run)").option("--stop-after <phase>", "Stop pipeline after this phase completes").option("--from <phase>", "Start pipeline from this phase").option("--pack <name>", "Methodology pack name", "bmad").option("--project-root <path>", "Project root directory", projectRoot).option("--output-format <format>", "Output format: human (default) or json", "human").action(async (opts) => {
4011
+ program.command("amend").description("Run an amendment pipeline against a completed run and an existing run").option("--concept <text>", "Amendment concept description (inline)").option("--concept-file <path>", "Path to concept file").option("--run-id <id>", "Parent run ID (defaults to latest completed run)").option("--stop-after <phase>", "Stop pipeline after this phase completes").option("--from <phase>", "Start pipeline from this phase").option("--pack <name>", "Methodology pack name", "bmad").option("--project-root <path>", "Project root directory", projectRoot).option("--output-format <format>", "Output format: human (default) or json", "human").option("--agent <id>", "Agent backend: claude-code (default), codex, or gemini").action(async (opts) => {
4007
4012
  const exitCode = await runAmendAction({
4008
4013
  concept: opts.concept,
4009
4014
  conceptFile: opts.conceptFile,
@@ -4012,6 +4017,7 @@ function registerAmendCommand(program, _version = "0.0.0", projectRoot = process
4012
4017
  from: opts.from,
4013
4018
  projectRoot: opts.projectRoot,
4014
4019
  pack: opts.pack,
4020
+ agent: opts.agent,
4015
4021
  registry
4016
4022
  });
4017
4023
  process.exitCode = exitCode;
@@ -4547,7 +4553,7 @@ async function runSupervisorAction(options, deps = {}) {
4547
4553
  await initSchema(expAdapter);
4548
4554
  const { runRunAction: runPipeline } = await import(
4549
4555
  /* @vite-ignore */
4550
- "../run-0y5KOffG.js"
4556
+ "../run-Ds3ucwad.js"
4551
4557
  );
4552
4558
  const runStoryFn = async (opts) => {
4553
4559
  const exitCode = await runPipeline({
@@ -7159,7 +7165,7 @@ function registerBrainstormCommand(program, _version = "0.0.0", projectRoot = pr
7159
7165
  //#region src/cli/commands/retry-escalated.ts
7160
7166
  const logger$3 = createLogger("retry-escalated-cmd");
7161
7167
  async function runRetryEscalatedAction(options) {
7162
- const { runId, dryRun, force, outputFormat, projectRoot, concurrency, pack: packName, registry: injectedRegistry } = options;
7168
+ const { runId, dryRun, force, outputFormat, projectRoot, concurrency, pack: packName, registry: injectedRegistry, agent: agentId$1 } = options;
7163
7169
  const dbRoot = await resolveMainRepoRoot(projectRoot);
7164
7170
  const dbPath = join(dbRoot, ".substrate", "substrate.db");
7165
7171
  const doltDir = join(dbRoot, ".substrate", "state", ".dolt");
@@ -7262,7 +7268,8 @@ async function runRetryEscalatedAction(options) {
7262
7268
  pipelineRunId: pipelineRun.id,
7263
7269
  ...Object.keys(perStoryContextCeilings).length > 0 ? { perStoryContextCeilings } : {}
7264
7270
  },
7265
- projectRoot
7271
+ projectRoot,
7272
+ agentId: agentId$1
7266
7273
  });
7267
7274
  eventBus.on("orchestrator:story-phase-complete", (payload) => {
7268
7275
  try {
@@ -7272,7 +7279,7 @@ async function runRetryEscalatedAction(options) {
7272
7279
  const costUsd = (input * 3 + output * 15) / 1e6;
7273
7280
  addTokenUsage(adapter, pipelineRun.id, {
7274
7281
  phase: payload.phase,
7275
- agent: "claude-code",
7282
+ agent: agentId$1 ?? "claude-code",
7276
7283
  input_tokens: input,
7277
7284
  output_tokens: output,
7278
7285
  cost_usd: costUsd
@@ -7316,7 +7323,7 @@ function registerRetryEscalatedCommand(program, _version = "0.0.0", projectRoot
7316
7323
  const n = parseInt(v, 10);
7317
7324
  if (isNaN(n) || n < 1) throw new Error(`--concurrency must be a positive integer, got: ${v}`);
7318
7325
  return n;
7319
- }, 3).option("--pack <name>", "Methodology pack name", "bmad").option("--project-root <path>", "Project root directory", projectRoot).option("--output-format <format>", "Output format: human (default) or json", "human").action(async (opts) => {
7326
+ }, 3).option("--pack <name>", "Methodology pack name", "bmad").option("--project-root <path>", "Project root directory", projectRoot).option("--output-format <format>", "Output format: human (default) or json", "human").option("--agent <id>", "Agent backend: claude-code (default), codex, or gemini").action(async (opts) => {
7320
7327
  const outputFormat = opts.outputFormat === "json" ? "json" : "human";
7321
7328
  const exitCode = await runRetryEscalatedAction({
7322
7329
  runId: opts.runId,
@@ -7326,6 +7333,7 @@ function registerRetryEscalatedCommand(program, _version = "0.0.0", projectRoot
7326
7333
  projectRoot: opts.projectRoot,
7327
7334
  concurrency: opts.concurrency,
7328
7335
  pack: opts.pack,
7336
+ agent: opts.agent,
7329
7337
  registry
7330
7338
  });
7331
7339
  process.exitCode = exitCode;
package/dist/index.d.ts CHANGED
@@ -94,6 +94,8 @@ interface PipelineStartEvent {
94
94
  stories: string[];
95
95
  /** Maximum parallel conflict groups */
96
96
  concurrency: number;
97
+ /** Execution engine: 'linear' or 'graph' */
98
+ engine?: string;
97
99
  }
98
100
  /**
99
101
  * Emitted as the last event when the pipeline finishes.
@@ -1526,6 +1528,7 @@ declare const SubstrateConfigSchema: z.ZodObject<{
1526
1528
  enabled: z.ZodDefault<z.ZodBoolean>;
1527
1529
  port: z.ZodDefault<z.ZodNumber>;
1528
1530
  }, z.core.$strict>>;
1531
+ default_agent: z.ZodOptional<z.ZodString>;
1529
1532
  }, z.core.$strict>;
1530
1533
  type SubstrateConfig = z.infer<typeof SubstrateConfigSchema>;
1531
1534
 
@@ -5284,6 +5284,15 @@ async function runCreateStory(deps, params) {
5284
5284
  const implementationDecisions = await getImplementationDecisions(deps);
5285
5285
  const epicShardContent = getEpicShard(implementationDecisions, epicId, deps.projectRoot, storyKey);
5286
5286
  const prevDevNotesContent = getPrevDevNotes(implementationDecisions, epicId);
5287
+ let storyDefinitionContent = "";
5288
+ try {
5289
+ const storyDecisions = await getDecisionsByPhase(deps.db, "solutioning");
5290
+ const storyDef = storyDecisions.find((d) => d.category === "stories" && d.key === storyKey);
5291
+ if (storyDef) {
5292
+ storyDefinitionContent = storyDef.value;
5293
+ logger$18.debug({ storyKey }, "Injected story definition from solutioning decisions");
5294
+ }
5295
+ } catch {}
5287
5296
  const archConstraintsContent = await getArchConstraints$3(deps);
5288
5297
  const storyTemplateContent = await getStoryTemplate(deps);
5289
5298
  const { prompt, tokenCount, truncated } = assemblePrompt(template, [
@@ -5297,6 +5306,11 @@ async function runCreateStory(deps, params) {
5297
5306
  content: epicShardContent,
5298
5307
  priority: "required"
5299
5308
  },
5309
+ {
5310
+ name: "story_definition",
5311
+ content: storyDefinitionContent,
5312
+ priority: "required"
5313
+ },
5300
5314
  {
5301
5315
  name: "arch_constraints",
5302
5316
  content: archConstraintsContent,
@@ -5320,7 +5334,7 @@ async function runCreateStory(deps, params) {
5320
5334
  }, "Prompt assembled for create-story");
5321
5335
  const handle = deps.dispatcher.dispatch({
5322
5336
  prompt,
5323
- agent: "claude-code",
5337
+ agent: deps.agentId ?? "claude-code",
5324
5338
  taskType: "create-story",
5325
5339
  outputSchema: CreateStoryResultSchema,
5326
5340
  maxTurns: 50,
@@ -6542,7 +6556,7 @@ async function runDevStory(deps, params) {
6542
6556
  try {
6543
6557
  const handle = deps.dispatcher.dispatch({
6544
6558
  prompt,
6545
- agent: "claude-code",
6559
+ agent: deps.agentId ?? "claude-code",
6546
6560
  taskType: "dev-story",
6547
6561
  timeout: DEFAULT_TIMEOUT_MS$1,
6548
6562
  outputSchema: DevStoryResultSchema,
@@ -7011,7 +7025,7 @@ async function runCodeReview(deps, params) {
7011
7025
  const { prompt } = assembleResult;
7012
7026
  const handle = deps.dispatcher.dispatch({
7013
7027
  prompt,
7014
- agent: "claude-code",
7028
+ agent: deps.agentId ?? "claude-code",
7015
7029
  taskType: "code-review",
7016
7030
  outputSchema: CodeReviewResultSchema,
7017
7031
  workingDirectory: deps.projectRoot,
@@ -7226,7 +7240,7 @@ async function runTestPlan(deps, params) {
7226
7240
  try {
7227
7241
  const handle = deps.dispatcher.dispatch({
7228
7242
  prompt,
7229
- agent: "claude-code",
7243
+ agent: deps.agentId ?? "claude-code",
7230
7244
  taskType: "test-plan",
7231
7245
  timeout: DEFAULT_TIMEOUT_MS,
7232
7246
  outputSchema: TestPlanResultSchema,
@@ -7497,7 +7511,7 @@ async function runTestExpansion(deps, params) {
7497
7511
  const { prompt } = assembleResult;
7498
7512
  const handle = deps.dispatcher.dispatch({
7499
7513
  prompt,
7500
- agent: "claude-code",
7514
+ agent: deps.agentId ?? "claude-code",
7501
7515
  taskType: "test-expansion",
7502
7516
  outputSchema: TestExpansionResultSchema,
7503
7517
  workingDirectory: deps.projectRoot,
@@ -11617,7 +11631,7 @@ function createImplementationOrchestrator(deps) {
11617
11631
  startPhase(storyKey, "dev-story-retry");
11618
11632
  const checkpointRetryHandle = dispatcher.dispatch({
11619
11633
  prompt: checkpointRetryPrompt,
11620
- agent: "claude-code",
11634
+ agent: deps.agentId ?? "claude-code",
11621
11635
  taskType: "dev-story",
11622
11636
  outputSchema: DevStoryResultSchema,
11623
11637
  ...checkpointRetryMaxTurns !== void 0 ? { maxTurns: checkpointRetryMaxTurns } : {},
@@ -11888,7 +11902,7 @@ function createImplementationOrchestrator(deps) {
11888
11902
  incrementDispatches(storyKey);
11889
11903
  const fixHandle = dispatcher.dispatch({
11890
11904
  prompt: buildFixPrompt,
11891
- agent: "claude-code",
11905
+ agent: deps.agentId ?? "claude-code",
11892
11906
  taskType: "build-fix",
11893
11907
  maxTurns: 15,
11894
11908
  workingDirectory: projectRoot ?? process.cwd(),
@@ -12378,7 +12392,7 @@ function createImplementationOrchestrator(deps) {
12378
12392
  }
12379
12393
  const handle = dispatcher.dispatch({
12380
12394
  prompt: fixPrompt,
12381
- agent: "claude-code",
12395
+ agent: deps.agentId ?? "claude-code",
12382
12396
  taskType: "minor-fixes",
12383
12397
  workingDirectory: projectRoot,
12384
12398
  ...autoApproveMaxTurns !== void 0 ? { maxTurns: autoApproveMaxTurns } : {},
@@ -12537,7 +12551,7 @@ function createImplementationOrchestrator(deps) {
12537
12551
  incrementDispatches(storyKey);
12538
12552
  const handle = isMajorRework ? dispatcher.dispatch({
12539
12553
  prompt: fixPrompt,
12540
- agent: "claude-code",
12554
+ agent: deps.agentId ?? "claude-code",
12541
12555
  taskType,
12542
12556
  ...fixModel !== void 0 ? { model: fixModel } : {},
12543
12557
  outputSchema: DevStoryResultSchema,
@@ -12547,7 +12561,7 @@ function createImplementationOrchestrator(deps) {
12547
12561
  ..._otlpEndpoint !== void 0 ? { otlpEndpoint: _otlpEndpoint } : {}
12548
12562
  }) : dispatcher.dispatch({
12549
12563
  prompt: fixPrompt,
12550
- agent: "claude-code",
12564
+ agent: deps.agentId ?? "claude-code",
12551
12565
  taskType,
12552
12566
  ...fixModel !== void 0 ? { model: fixModel } : {},
12553
12567
  ...fixMaxTurns !== void 0 ? { maxTurns: fixMaxTurns } : {},
@@ -13757,7 +13771,7 @@ async function runCritiqueLoop(artifact, phaseId, runId, phase, deps, options =
13757
13771
  try {
13758
13772
  const handle = deps.dispatcher.dispatch({
13759
13773
  prompt: critiquePrompt,
13760
- agent: "claude-code",
13774
+ agent: deps.agentId ?? "claude-code",
13761
13775
  taskType: "critique",
13762
13776
  outputSchema: CritiqueOutputSchema
13763
13777
  });
@@ -13868,7 +13882,7 @@ async function runCritiqueLoop(artifact, phaseId, runId, phase, deps, options =
13868
13882
  try {
13869
13883
  const refineHandle = deps.dispatcher.dispatch({
13870
13884
  prompt: refinePrompt,
13871
- agent: "claude-code",
13885
+ agent: deps.agentId ?? "claude-code",
13872
13886
  taskType: "critique",
13873
13887
  outputSchema: void 0
13874
13888
  });
@@ -14546,7 +14560,7 @@ async function runSteps(steps, deps, runId, phase, params) {
14546
14560
  }
14547
14561
  const handle = deps.dispatcher.dispatch({
14548
14562
  prompt,
14549
- agent: "claude-code",
14563
+ agent: deps.agentId ?? "claude-code",
14550
14564
  taskType: step.taskType,
14551
14565
  outputSchema: step.outputSchema
14552
14566
  });
@@ -14725,7 +14739,7 @@ async function runSteps(steps, deps, runId, phase, params) {
14725
14739
  const elicitPrompt = elicitationTemplate.replace(/\{\{method_name\}\}/g, method.name).replace(/\{\{method_description\}\}/g, method.description).replace(/\{\{output_pattern\}\}/g, method.output_pattern).replace(/\{\{artifact_content\}\}/g, artifactContent);
14726
14740
  const elicitHandle = deps.dispatcher.dispatch({
14727
14741
  prompt: elicitPrompt,
14728
- agent: "claude-code",
14742
+ agent: deps.agentId ?? "claude-code",
14729
14743
  taskType: "elicitation",
14730
14744
  outputSchema: ElicitationOutputSchema
14731
14745
  });
@@ -15069,7 +15083,7 @@ async function runAnalysisPhase(deps, params) {
15069
15083
  };
15070
15084
  const handle = dispatcher.dispatch({
15071
15085
  prompt,
15072
- agent: "claude-code",
15086
+ agent: deps.agentId ?? "claude-code",
15073
15087
  taskType: "analysis",
15074
15088
  outputSchema: AnalysisOutputSchema
15075
15089
  });
@@ -15381,7 +15395,7 @@ async function runPlanningMultiStep(deps, params) {
15381
15395
  correctedPrompt = correctionPrefix + correctedPrompt;
15382
15396
  const retryHandle = deps.dispatcher.dispatch({
15383
15397
  prompt: correctedPrompt,
15384
- agent: "claude-code",
15398
+ agent: deps.agentId ?? "claude-code",
15385
15399
  taskType: "planning-nfrs",
15386
15400
  outputSchema: PlanningNFRsOutputSchema
15387
15401
  });
@@ -15501,7 +15515,7 @@ async function runPlanningPhase(deps, params) {
15501
15515
  };
15502
15516
  const handle = dispatcher.dispatch({
15503
15517
  prompt,
15504
- agent: "claude-code",
15518
+ agent: deps.agentId ?? "claude-code",
15505
15519
  taskType: "planning",
15506
15520
  outputSchema: PlanningOutputSchema
15507
15521
  });
@@ -15801,7 +15815,7 @@ async function runArchitectureGeneration(deps, params) {
15801
15815
  };
15802
15816
  const handle = dispatcher.dispatch({
15803
15817
  prompt,
15804
- agent: "claude-code",
15818
+ agent: deps.agentId ?? "claude-code",
15805
15819
  taskType: "architecture",
15806
15820
  outputSchema: ArchitectureOutputSchema
15807
15821
  });
@@ -15903,7 +15917,7 @@ async function runStoryGeneration(deps, params, gapAnalysis) {
15903
15917
  };
15904
15918
  const handle = dispatcher.dispatch({
15905
15919
  prompt,
15906
- agent: "claude-code",
15920
+ agent: deps.agentId ?? "claude-code",
15907
15921
  taskType: "story-generation",
15908
15922
  outputSchema: StoryGenerationOutputSchema
15909
15923
  });
@@ -16081,7 +16095,7 @@ async function runReadinessCheck(deps, runId) {
16081
16095
  else prompt = prompt.replace(READINESS_UX_PLACEHOLDER, "");
16082
16096
  const handle = dispatcher.dispatch({
16083
16097
  prompt,
16084
- agent: "claude-code",
16098
+ agent: deps.agentId ?? "claude-code",
16085
16099
  taskType: "readiness-check",
16086
16100
  outputSchema: ReadinessOutputSchema
16087
16101
  });
@@ -40360,7 +40374,7 @@ function wireNdjsonEmitter(eventBus, ndjsonEmitter) {
40360
40374
  });
40361
40375
  }
40362
40376
  async function runRunAction(options) {
40363
- const { pack: packName, from: startPhase, stopAfter, concept: conceptArg, conceptFile, stories: storiesArg, concurrency, outputFormat, projectRoot, events: eventsFlag, verbose: verboseFlag, tui: tuiFlag, skipUx, research: researchFlag, skipResearch: skipResearchFlag, skipPreflight, epic: epicNumber, dryRun, maxReviewCycles = 2, engine, registry: injectedRegistry } = options;
40377
+ const { pack: packName, from: startPhase, stopAfter, concept: conceptArg, conceptFile, stories: storiesArg, concurrency, outputFormat, projectRoot, events: eventsFlag, verbose: verboseFlag, tui: tuiFlag, skipUx, research: researchFlag, skipResearch: skipResearchFlag, skipPreflight, epic: epicNumber, dryRun, maxReviewCycles = 2, engine, agent: agentId, registry: injectedRegistry } = options;
40364
40378
  const resolvedEngine = engine ?? "linear";
40365
40379
  if (!VALID_ENGINES.includes(resolvedEngine)) {
40366
40380
  const errorMsg = `Invalid engine '${engine}'. Valid values: ${VALID_ENGINES.join(", ")}`;
@@ -40524,7 +40538,8 @@ async function runRunAction(options) {
40524
40538
  telemetryEnabled: true,
40525
40539
  telemetryPort
40526
40540
  } : {},
40527
- maxReviewCycles
40541
+ maxReviewCycles,
40542
+ agentId
40528
40543
  });
40529
40544
  let storyKeys = [...parsedStoryKeys];
40530
40545
  if (!existsSync$1(dbDir)) mkdirSync$1(dbDir, { recursive: true });
@@ -40742,7 +40757,7 @@ async function runRunAction(options) {
40742
40757
  const costUsd = (input * 3 + output * 15) / 1e6;
40743
40758
  addTokenUsage(adapter, pipelineRun.id, {
40744
40759
  phase: payload.phase,
40745
- agent: "claude-code",
40760
+ agent: agentId ?? "claude-code",
40746
40761
  input_tokens: input,
40747
40762
  output_tokens: output,
40748
40763
  cost_usd: costUsd,
@@ -40956,7 +40971,8 @@ async function runRunAction(options) {
40956
40971
  pack,
40957
40972
  contextCompiler,
40958
40973
  dispatcher,
40959
- projectRoot
40974
+ projectRoot,
40975
+ agentId
40960
40976
  };
40961
40977
  const sdlcEventBus = eventBus;
40962
40978
  const handlerRegistry = buildSdlcHandlerRegistry({
@@ -40966,7 +40982,8 @@ async function runRunAction(options) {
40966
40982
  db: adapter,
40967
40983
  pack,
40968
40984
  contextCompiler,
40969
- dispatcher
40985
+ dispatcher,
40986
+ agentId
40970
40987
  },
40971
40988
  phases: {
40972
40989
  analysis: runAnalysisPhase,
@@ -41034,6 +41051,7 @@ async function runRunAction(options) {
41034
41051
  },
41035
41052
  projectRoot,
41036
41053
  tokenCeilings,
41054
+ agentId,
41037
41055
  ...ingestionServer !== void 0 ? { ingestionServer } : {},
41038
41056
  ...telemetryPersistence !== void 0 ? { telemetryPersistence } : {},
41039
41057
  ...repoMapInjector !== void 0 ? {
@@ -41147,7 +41165,7 @@ async function runRunAction(options) {
41147
41165
  }
41148
41166
  }
41149
41167
  async function runFullPipeline(options) {
41150
- const { packName, packPath, dbDir, dbPath, startPhase, stopAfter, concept, concurrency, outputFormat, projectRoot, events: eventsFlag, skipUx, research: researchFlag, skipResearch: skipResearchFlag, skipPreflight, maxReviewCycles = 2, registry: injectedRegistry, tokenCeilings, stories: explicitStories, telemetryEnabled: fullTelemetryEnabled, telemetryPort: fullTelemetryPort } = options;
41168
+ const { packName, packPath, dbDir, dbPath, startPhase, stopAfter, concept, concurrency, outputFormat, projectRoot, events: eventsFlag, skipUx, research: researchFlag, skipResearch: skipResearchFlag, skipPreflight, maxReviewCycles = 2, registry: injectedRegistry, tokenCeilings, stories: explicitStories, telemetryEnabled: fullTelemetryEnabled, telemetryPort: fullTelemetryPort, agentId } = options;
41151
41169
  if (!existsSync$1(dbDir)) mkdirSync$1(dbDir, { recursive: true });
41152
41170
  const adapter = createDatabaseAdapter({
41153
41171
  backend: "auto",
@@ -41188,7 +41206,8 @@ async function runFullPipeline(options) {
41188
41206
  db: adapter,
41189
41207
  pack,
41190
41208
  contextCompiler,
41191
- dispatcher
41209
+ dispatcher,
41210
+ agentId
41192
41211
  };
41193
41212
  let effectiveResearch = pack.manifest.research === true;
41194
41213
  if (researchFlag === true) effectiveResearch = true;
@@ -41280,7 +41299,7 @@ async function runFullPipeline(options) {
41280
41299
  const costUsd = (result.tokenUsage.input * 3 + result.tokenUsage.output * 15) / 1e6;
41281
41300
  await addTokenUsage(adapter, runId, {
41282
41301
  phase: "analysis",
41283
- agent: "claude-code",
41302
+ agent: agentId ?? "claude-code",
41284
41303
  input_tokens: result.tokenUsage.input,
41285
41304
  output_tokens: result.tokenUsage.output,
41286
41305
  cost_usd: costUsd
@@ -41305,7 +41324,7 @@ async function runFullPipeline(options) {
41305
41324
  const costUsd = (result.tokenUsage.input * 3 + result.tokenUsage.output * 15) / 1e6;
41306
41325
  await addTokenUsage(adapter, runId, {
41307
41326
  phase: "planning",
41308
- agent: "claude-code",
41327
+ agent: agentId ?? "claude-code",
41309
41328
  input_tokens: result.tokenUsage.input,
41310
41329
  output_tokens: result.tokenUsage.output,
41311
41330
  cost_usd: costUsd
@@ -41333,7 +41352,7 @@ async function runFullPipeline(options) {
41333
41352
  const costUsd = (result.tokenUsage.input * 3 + result.tokenUsage.output * 15) / 1e6;
41334
41353
  await addTokenUsage(adapter, runId, {
41335
41354
  phase: "research",
41336
- agent: "claude-code",
41355
+ agent: agentId ?? "claude-code",
41337
41356
  input_tokens: result.tokenUsage.input,
41338
41357
  output_tokens: result.tokenUsage.output,
41339
41358
  cost_usd: costUsd
@@ -41358,7 +41377,7 @@ async function runFullPipeline(options) {
41358
41377
  const costUsd = (result.tokenUsage.input * 3 + result.tokenUsage.output * 15) / 1e6;
41359
41378
  await addTokenUsage(adapter, runId, {
41360
41379
  phase: "ux-design",
41361
- agent: "claude-code",
41380
+ agent: agentId ?? "claude-code",
41362
41381
  input_tokens: result.tokenUsage.input,
41363
41382
  output_tokens: result.tokenUsage.output,
41364
41383
  cost_usd: costUsd
@@ -41383,7 +41402,7 @@ async function runFullPipeline(options) {
41383
41402
  const costUsd = (result.tokenUsage.input * 3 + result.tokenUsage.output * 15) / 1e6;
41384
41403
  await addTokenUsage(adapter, runId, {
41385
41404
  phase: "solutioning",
41386
- agent: "claude-code",
41405
+ agent: agentId ?? "claude-code",
41387
41406
  input_tokens: result.tokenUsage.input,
41388
41407
  output_tokens: result.tokenUsage.output,
41389
41408
  cost_usd: costUsd
@@ -41442,6 +41461,7 @@ async function runFullPipeline(options) {
41442
41461
  },
41443
41462
  projectRoot,
41444
41463
  tokenCeilings,
41464
+ agentId,
41445
41465
  ...fpIngestionServer !== void 0 ? { ingestionServer: fpIngestionServer } : {},
41446
41466
  ...fpTelemetryPersistence !== void 0 ? { telemetryPersistence: fpTelemetryPersistence } : {}
41447
41467
  });
@@ -41453,7 +41473,7 @@ async function runFullPipeline(options) {
41453
41473
  const costUsd = (input * 3 + output * 15) / 1e6;
41454
41474
  addTokenUsage(adapter, runId, {
41455
41475
  phase: payload.phase,
41456
- agent: "claude-code",
41476
+ agent: agentId ?? "claude-code",
41457
41477
  input_tokens: input,
41458
41478
  output_tokens: output,
41459
41479
  cost_usd: costUsd
@@ -41574,7 +41594,7 @@ async function runFullPipeline(options) {
41574
41594
  }
41575
41595
  }
41576
41596
  function registerRunCommand(program, _version = "0.0.0", projectRoot = process.cwd(), registry) {
41577
- program.command("run").description("Run the autonomous pipeline (use --from to start from a specific phase)").option("--pack <name>", "Methodology pack name", "bmad").option("--from <phase>", "Start from this phase: analysis, planning, solutioning, implementation").option("--stop-after <phase>", "Stop pipeline after this phase completes").option("--concept <text>", "Inline concept text (required when --from analysis)").option("--concept-file <path>", "Path to a file containing the concept text").option("--stories <keys>", "Comma-separated story keys (e.g., 10-1,10-2)").option("--epic <n>", "Scope story discovery to a single epic number (e.g., 27)", (v) => parseInt(v, 10)).option("--concurrency <n>", "Maximum parallel conflict groups", (v) => parseInt(v, 10), 3).option("--project-root <path>", "Project root directory", projectRoot).option("--output-format <format>", "Output format: human (default) or json", "human").option("--events", "Emit structured NDJSON events on stdout for programmatic consumption").option("--verbose", "Show detailed pino log output").option("--help-agent", "Print a machine-optimized prompt fragment for AI agents and exit").option("--tui", "Show TUI dashboard").option("--skip-ux", "Skip the UX design phase even if enabled in the pack manifest").option("--research", "Enable the research phase even if not set in the pack manifest").option("--skip-research", "Skip the research phase even if enabled in the pack manifest").option("--skip-preflight", "Skip the pre-flight build check (escape hatch for known-broken projects)").option("--max-review-cycles <n>", "Maximum review cycles per story (default: 2)", (v) => parseInt(v, 10), 2).option("--dry-run", "Preview routing and repo-map injection without dispatching (Story 28-9)").option("--engine <type>", "Execution engine: linear (default) or graph").action(async (opts) => {
41597
+ program.command("run").description("Run the autonomous pipeline (use --from to start from a specific phase)").option("--pack <name>", "Methodology pack name", "bmad").option("--from <phase>", "Start from this phase: analysis, planning, solutioning, implementation").option("--stop-after <phase>", "Stop pipeline after this phase completes").option("--concept <text>", "Inline concept text (required when --from analysis)").option("--concept-file <path>", "Path to a file containing the concept text").option("--stories <keys>", "Comma-separated story keys (e.g., 10-1,10-2)").option("--epic <n>", "Scope story discovery to a single epic number (e.g., 27)", (v) => parseInt(v, 10)).option("--concurrency <n>", "Maximum parallel conflict groups", (v) => parseInt(v, 10), 3).option("--project-root <path>", "Project root directory", projectRoot).option("--output-format <format>", "Output format: human (default) or json", "human").option("--events", "Emit structured NDJSON events on stdout for programmatic consumption").option("--verbose", "Show detailed pino log output").option("--help-agent", "Print a machine-optimized prompt fragment for AI agents and exit").option("--tui", "Show TUI dashboard").option("--skip-ux", "Skip the UX design phase even if enabled in the pack manifest").option("--research", "Enable the research phase even if not set in the pack manifest").option("--skip-research", "Skip the research phase even if enabled in the pack manifest").option("--skip-preflight", "Skip the pre-flight build check (escape hatch for known-broken projects)").option("--max-review-cycles <n>", "Maximum review cycles per story (default: 2)", (v) => parseInt(v, 10), 2).option("--dry-run", "Preview routing and repo-map injection without dispatching (Story 28-9)").option("--engine <type>", "Execution engine: linear (default) or graph").option("--agent <id>", "Agent backend: claude-code (default), codex, or gemini").action(async (opts) => {
41578
41598
  if (opts.helpAgent) {
41579
41599
  process.exitCode = await runHelpAgent();
41580
41600
  return;
@@ -41612,6 +41632,7 @@ function registerRunCommand(program, _version = "0.0.0", projectRoot = process.c
41612
41632
  maxReviewCycles: opts.maxReviewCycles,
41613
41633
  dryRun: opts.dryRun,
41614
41634
  engine: opts.engine,
41635
+ agent: opts.agent,
41615
41636
  registry
41616
41637
  });
41617
41638
  process.exitCode = exitCode;
@@ -41620,4 +41641,4 @@ function registerRunCommand(program, _version = "0.0.0", projectRoot = process.c
41620
41641
 
41621
41642
  //#endregion
41622
41643
  export { AdapterTelemetryPersistence, AppError, DoltRepoMapMetaRepository, DoltSymbolRepository, ERR_REPO_MAP_STORAGE_WRITE, EpicIngester, GitClient, GrammarLoader, RepoMapInjector, RepoMapModule, RepoMapQueryEngine, RepoMapStorage, SymbolParser, createContextCompiler, createDispatcher, createEventEmitter, createImplementationOrchestrator, createPackLoader, createPhaseOrchestrator, createStopAfterGate, createTelemetryAdvisor, formatPhaseCompletionSummary, getFactoryRunSummaries, getScenarioResultsForRun, getTwinRunsForRun, listGraphRuns, normalizeGraphSummaryToStatus, registerExportCommand, registerFactoryCommand, registerRunCommand, registerScenariosCommand, resolveStoryKeys, runAnalysisPhase, runPlanningPhase, runRunAction, runSolutioningPhase, validateStopAfterFromConflict };
41623
- //# sourceMappingURL=run-Dg_BEJB6.js.map
41644
+ //# sourceMappingURL=run-BLxcApKZ.js.map
@@ -2,7 +2,7 @@ import "./health-DJgGZhW-.js";
2
2
  import "./logger-KeHncl-f.js";
3
3
  import "./helpers-CElYrONe.js";
4
4
  import "./dist-adzGUKPc.js";
5
- import { normalizeGraphSummaryToStatus, registerRunCommand, runRunAction } from "./run-Dg_BEJB6.js";
5
+ import { normalizeGraphSummaryToStatus, registerRunCommand, runRunAction } from "./run-BLxcApKZ.js";
6
6
  import "./routing-CcBOCuC9.js";
7
7
  import "./decisions-C0pz9Clx.js";
8
8
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "substrate-ai",
3
- "version": "0.19.9",
3
+ "version": "0.19.11",
4
4
  "description": "Substrate — multi-agent orchestration daemon for AI coding agents",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -16,13 +16,22 @@
16
16
 
17
17
  ---
18
18
 
19
+ ### Story Definition (from Solutioning Phase)
20
+ {{story_definition}}
21
+
22
+ ---
23
+
19
24
  ## Mission
20
25
 
21
26
  Using the context above, write a complete, implementation-ready story file for story **{{story_key}}**.
22
27
 
28
+ **CRITICAL**: The Story Definition above is the authoritative specification for this story's scope.
29
+ Use the title, description, and acceptance criteria from the Story Definition — do NOT substitute
30
+ a different story from the epic scope. The story key, title, and core scope are non-negotiable.
31
+
23
32
  ## Instructions
24
33
 
25
- 1. **Parse the epic scope** to understand what this epic is building and where this story fits
34
+ 1. **Use the Story Definition as your primary input** it specifies exactly what this story builds. The epic scope provides surrounding context only.
26
35
  2. **Apply architecture constraints** — every constraint listed above is mandatory (file paths, import style, test framework, etc.)
27
36
  3. **Use previous dev notes** as guardrails — don't repeat mistakes, build on patterns that worked
28
37
  4. **Fill out the story template** with:
@@ -88,7 +88,21 @@ For EVERY story with dependencies on other stories:
88
88
  3. Flag invalid references as **blocker** findings
89
89
  4. Flag potentially circular dependencies as **major** findings
90
90
 
91
- ### Step 7: Final Verdict
91
+ ### Step 7: Cross-Epic Story Duplication Check
92
+
93
+ For EVERY pair of stories from DIFFERENT epics:
94
+ 1. Do they implement the same core functionality? (e.g., both create a MatchRunner, both build a tournament CLI, both implement a logging module)
95
+ 2. Do they have overlapping acceptance criteria? (same testable condition appears in both)
96
+ 3. Does a later epic's story RE-IMPLEMENT what an earlier epic's story already built, rather than extending or depending on it?
97
+ 4. Flag duplicate stories as **major** findings with both story keys in affected_items
98
+ 5. Flag near-duplicates (>50% AC overlap) as **major** findings
99
+
100
+ Common duplication patterns:
101
+ - Infrastructure stories (logging, CLI, config) appearing in multiple epics
102
+ - Runner/harness classes duplicated across epics
103
+ - The same data model or service being created by stories in different epics
104
+
105
+ ### Step 8: Final Verdict
92
106
 
93
107
  Determine your verdict:
94
108
  - **NOT_READY**: Any of these conditions are true:
@@ -130,7 +144,7 @@ findings:
130
144
  ```
131
145
 
132
146
  Valid verdict values: READY, NEEDS_WORK, NOT_READY
133
- Valid category values: fr_coverage, architecture_compliance, story_quality, constraint_adherence, ux_alignment, dependency_validity
147
+ Valid category values: fr_coverage, architecture_compliance, story_quality, constraint_adherence, ux_alignment, dependency_validity, story_duplication
134
148
  Valid severity values: blocker, major, minor
135
149
 
136
150
  If you cannot perform the review:
@@ -42,12 +42,19 @@ Break down the requirements and architecture above into **epics and stories**
42
42
  - If architecture specifies a project structure, Epic 1 Story 1 should be project scaffolding
43
43
  - Database tables, API endpoints, and infrastructure are created in the epic where they are FIRST NEEDED
44
44
 
45
- 5. **Size stories appropriately:**
45
+ 5. **Eliminate cross-epic duplication (CRITICAL):**
46
+ - Before finalizing, compare EVERY story against stories in OTHER epics
47
+ - If two stories from different epics implement the same core functionality (e.g., both create a MatchRunner, both build a tournament CLI), consolidate into ONE story in the earliest epic that needs it
48
+ - The later epic should DEPEND ON the earlier epic's story, not re-implement it
49
+ - Common duplication patterns to watch for: logging infrastructure, CLI subcommands, runner/harness classes, configuration modules
50
+ - A story that "extends" functionality from an earlier epic is OK; a story that "re-implements" the same functionality is NOT
51
+
52
+ 6. **Size stories appropriately:**
46
53
  - Each story should be completable by one developer in 1-3 days
47
54
  - If a story feels too large, split it into multiple stories within the same epic
48
55
  - If an epic has more than 8 stories, consider splitting the epic
49
56
 
50
- 6. **Amendment awareness**: If amendment context from a parent run is provided below, generate stories for the NEW scope introduced by the amendment. Do not regenerate stories for unchanged requirements.
57
+ 7. **Amendment awareness**: If amendment context from a parent run is provided below, generate stories for the NEW scope introduced by the amendment. Do not regenerate stories for unchanged requirements.
51
58
 
52
59
  ## Output Contract
53
60