@nathapp/nax 0.40.0 → 0.41.0

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 (49) hide show
  1. package/dist/nax.js +1166 -277
  2. package/package.json +2 -2
  3. package/src/acceptance/fix-generator.ts +4 -35
  4. package/src/acceptance/generator.ts +27 -28
  5. package/src/acceptance/refinement.ts +72 -5
  6. package/src/acceptance/templates/cli.ts +47 -0
  7. package/src/acceptance/templates/component.ts +78 -0
  8. package/src/acceptance/templates/e2e.ts +43 -0
  9. package/src/acceptance/templates/index.ts +21 -0
  10. package/src/acceptance/templates/snapshot.ts +50 -0
  11. package/src/acceptance/templates/unit.ts +48 -0
  12. package/src/acceptance/types.ts +9 -1
  13. package/src/agents/acp/adapter.ts +644 -0
  14. package/src/agents/acp/cost.ts +79 -0
  15. package/src/agents/acp/index.ts +9 -0
  16. package/src/agents/acp/interaction-bridge.ts +126 -0
  17. package/src/agents/acp/parser.ts +166 -0
  18. package/src/agents/acp/spawn-client.ts +309 -0
  19. package/src/agents/acp/types.ts +22 -0
  20. package/src/agents/claude-complete.ts +3 -3
  21. package/src/agents/registry.ts +83 -0
  22. package/src/agents/types-extended.ts +23 -0
  23. package/src/agents/types.ts +17 -0
  24. package/src/cli/analyze.ts +6 -2
  25. package/src/cli/init-detect.ts +94 -8
  26. package/src/cli/init.ts +2 -2
  27. package/src/cli/plan.ts +23 -0
  28. package/src/config/defaults.ts +1 -0
  29. package/src/config/index.ts +1 -1
  30. package/src/config/runtime-types.ts +17 -0
  31. package/src/config/schema.ts +3 -1
  32. package/src/config/schemas.ts +9 -1
  33. package/src/config/types.ts +2 -0
  34. package/src/execution/executor-types.ts +6 -0
  35. package/src/execution/iteration-runner.ts +2 -0
  36. package/src/execution/lifecycle/acceptance-loop.ts +5 -2
  37. package/src/execution/lifecycle/run-initialization.ts +16 -4
  38. package/src/execution/lifecycle/run-setup.ts +4 -0
  39. package/src/execution/runner-completion.ts +11 -1
  40. package/src/execution/runner-execution.ts +8 -0
  41. package/src/execution/runner-setup.ts +4 -0
  42. package/src/execution/runner.ts +10 -0
  43. package/src/pipeline/stages/acceptance-setup.ts +4 -0
  44. package/src/pipeline/stages/execution.ts +33 -1
  45. package/src/pipeline/stages/routing.ts +18 -7
  46. package/src/pipeline/types.ts +10 -0
  47. package/src/tdd/orchestrator.ts +7 -0
  48. package/src/tdd/rectification-gate.ts +6 -0
  49. package/src/tdd/session-runner.ts +4 -0
@@ -22,6 +22,7 @@ import type { InteractionChain } from "../../interaction";
22
22
  import { initInteractionChain } from "../../interaction";
23
23
  import { getSafeLogger } from "../../logger";
24
24
  import { pipelineEventBus } from "../../pipeline/event-bus";
25
+ import type { AgentGetFn } from "../../pipeline/types";
25
26
  import { loadPlugins } from "../../plugins/loader";
26
27
  import type { PluginRegistry } from "../../plugins/registry";
27
28
  import type { PRD } from "../../prd";
@@ -53,6 +54,8 @@ export interface RunSetupOptions {
53
54
  // BUG-017: Additional getters for run.complete event on SIGTERM
54
55
  getStoriesCompleted: () => number;
55
56
  getTotalStories: () => number;
57
+ /** Protocol-aware agent resolver — passed from runner.ts registry */
58
+ agentGetFn?: AgentGetFn;
56
59
  }
57
60
 
58
61
  export interface RunSetupResult {
@@ -206,6 +209,7 @@ export async function setupRun(options: RunSetupOptions): Promise<RunSetupResult
206
209
  prdPath,
207
210
  workdir,
208
211
  dryRun,
212
+ agentGetFn: options.agentGetFn,
209
213
  });
210
214
  prd = initResult.prd;
211
215
  const counts = initResult.storyCounts;
@@ -11,9 +11,11 @@ import { fireHook } from "../hooks";
11
11
  import { getSafeLogger } from "../logger";
12
12
  import type { StoryMetrics } from "../metrics";
13
13
  import type { PipelineEventEmitter } from "../pipeline/events";
14
+ import type { AgentGetFn } from "../pipeline/types";
14
15
  import type { PluginRegistry } from "../plugins/registry";
15
16
  import { isComplete } from "../prd";
16
17
  import type { PRD } from "../prd";
18
+ import { autoCommitIfDirty } from "../utils/git";
17
19
  import { stopHeartbeat, writeExitSummary } from "./crash-recovery";
18
20
  import { hookCtx } from "./story-context";
19
21
 
@@ -42,6 +44,10 @@ export interface RunnerCompletionOptions {
42
44
  statusWriter: any;
43
45
  pluginRegistry: PluginRegistry;
44
46
  eventEmitter?: PipelineEventEmitter;
47
+ /** Protocol-aware agent resolver */
48
+ agentGetFn?: AgentGetFn;
49
+ /** Path to prd.json — required for acceptance fix story writes */
50
+ prdPath: string;
45
51
  }
46
52
 
47
53
  /**
@@ -67,7 +73,7 @@ export async function runCompletionPhase(options: RunnerCompletionOptions): Prom
67
73
  const acceptanceResult = await runAcceptanceLoop({
68
74
  config: options.config,
69
75
  prd: options.prd,
70
- prdPath: "", // Not needed for this extraction
76
+ prdPath: options.prdPath,
71
77
  workdir: options.workdir,
72
78
  featureDir: options.featureDir,
73
79
  hooks: options.hooks,
@@ -79,6 +85,7 @@ export async function runCompletionPhase(options: RunnerCompletionOptions): Prom
79
85
  pluginRegistry: options.pluginRegistry,
80
86
  eventEmitter: options.eventEmitter,
81
87
  statusWriter: options.statusWriter,
88
+ agentGetFn: options.agentGetFn,
82
89
  });
83
90
 
84
91
  Object.assign(options, {
@@ -153,6 +160,9 @@ export async function runCompletionPhase(options: RunnerCompletionOptions): Prom
153
160
  durationMs,
154
161
  );
155
162
 
163
+ // Commit status.json and any other nax runtime files left dirty at run end
164
+ await autoCommitIfDirty(options.workdir, "run.complete", "run-summary", options.feature);
165
+
156
166
  return {
157
167
  durationMs,
158
168
  runCompletedAt,
@@ -10,6 +10,7 @@ import type { LoadedHooksConfig } from "../hooks";
10
10
  import { getSafeLogger } from "../logger";
11
11
  import type { StoryMetrics } from "../metrics";
12
12
  import type { PipelineEventEmitter } from "../pipeline/events";
13
+ import type { AgentGetFn } from "../pipeline/types";
13
14
  import type { PluginRegistry } from "../plugins/registry";
14
15
  import type { PRD } from "../prd";
15
16
  import { tryLlmBatchRoute } from "../routing/batch-route";
@@ -17,6 +18,7 @@ import { clearCache as clearLlmCache, routeBatch as llmRouteBatch } from "../rou
17
18
  import { precomputeBatchPlan } from "./batching";
18
19
  import { getAllReadyStories } from "./helpers";
19
20
  import type { ParallelExecutorOptions, ParallelExecutorResult } from "./parallel-executor";
21
+ import type { PidRegistry } from "./pid-registry";
20
22
 
21
23
  /**
22
24
  * Options for the execution phase.
@@ -42,6 +44,10 @@ export interface RunnerExecutionOptions {
42
44
  headless: boolean;
43
45
  parallel?: number;
44
46
  runParallelExecution?: (options: ParallelExecutorOptions, prd: PRD) => Promise<ParallelExecutorResult>;
47
+ /** Protocol-aware agent resolver — created once in runner.ts from createAgentRegistry(config) */
48
+ agentGetFn?: AgentGetFn;
49
+ /** PID registry for crash recovery — passed to agent.run() to register child processes. */
50
+ pidRegistry?: PidRegistry;
45
51
  }
46
52
 
47
53
  /**
@@ -198,6 +204,8 @@ export async function runExecutionPhase(
198
204
  runId: options.runId,
199
205
  startTime: options.startTime,
200
206
  batchPlan,
207
+ agentGetFn: options.agentGetFn,
208
+ pidRegistry: options.pidRegistry,
201
209
  },
202
210
  prd,
203
211
  );
@@ -7,6 +7,7 @@
7
7
 
8
8
  import type { NaxConfig } from "../config";
9
9
  import type { LoadedHooksConfig } from "../hooks";
10
+ import type { AgentGetFn } from "../pipeline/types";
10
11
 
11
12
  /**
12
13
  * Options for the setup phase.
@@ -31,6 +32,8 @@ export interface RunnerSetupOptions {
31
32
  getIterations: () => number;
32
33
  getStoriesCompleted: () => number;
33
34
  getTotalStories: () => number;
35
+ /** Protocol-aware agent resolver — created from createAgentRegistry(config) in runner.ts */
36
+ agentGetFn?: AgentGetFn;
34
37
  }
35
38
 
36
39
  /**
@@ -76,6 +79,7 @@ export async function runSetupPhase(options: RunnerSetupOptions): Promise<Runner
76
79
  // BUG-017: Pass getters for run.complete event on SIGTERM
77
80
  getStoriesCompleted: options.getStoriesCompleted,
78
81
  getTotalStories: options.getTotalStories,
82
+ agentGetFn: options.agentGetFn,
79
83
  });
80
84
 
81
85
  return setupResult;
@@ -13,6 +13,7 @@
13
13
  * - runner-completion.ts: Acceptance loop, hooks, metrics
14
14
  */
15
15
 
16
+ import { createAgentRegistry } from "../agents/registry";
16
17
  import type { NaxConfig } from "../config";
17
18
  import type { LoadedHooksConfig } from "../hooks";
18
19
  import { fireHook } from "../hooks";
@@ -116,6 +117,10 @@ export async function run(options: RunOptions): Promise<RunResult> {
116
117
 
117
118
  const logger = getSafeLogger();
118
119
 
120
+ // Create protocol-aware agent registry (ACP wiring — ACP-003/registry-wiring)
121
+ const registry = createAgentRegistry(config);
122
+ const agentGetFn = registry.getAgent.bind(registry);
123
+
119
124
  // Declare prd before crash handler setup to avoid TDZ if SIGTERM arrives during setup
120
125
  // biome-ignore lint/suspicious/noExplicitAny: PRD type initialized during setup
121
126
  let prd: any | undefined;
@@ -137,6 +142,7 @@ export async function run(options: RunOptions): Promise<RunResult> {
137
142
  skipPrecheck,
138
143
  headless,
139
144
  formatterMode,
145
+ agentGetFn,
140
146
  getTotalCost: () => totalCost,
141
147
  getIterations: () => iterations,
142
148
  // BUG-017: Pass getters for run.complete event on SIGTERM
@@ -170,6 +176,8 @@ export async function run(options: RunOptions): Promise<RunResult> {
170
176
  headless,
171
177
  parallel,
172
178
  runParallelExecution: _runnerDeps.runParallelExecution ?? undefined,
179
+ agentGetFn,
180
+ pidRegistry,
173
181
  },
174
182
  prd,
175
183
  pluginRegistry,
@@ -198,6 +206,7 @@ export async function run(options: RunOptions): Promise<RunResult> {
198
206
  hooks,
199
207
  feature,
200
208
  workdir,
209
+ prdPath,
201
210
  statusFile,
202
211
  logFilePath,
203
212
  runId,
@@ -214,6 +223,7 @@ export async function run(options: RunOptions): Promise<RunResult> {
214
223
  statusWriter,
215
224
  pluginRegistry,
216
225
  eventEmitter,
226
+ agentGetFn,
217
227
  });
218
228
 
219
229
  const { durationMs } = completionResult;
@@ -89,6 +89,8 @@ export const acceptanceSetupStage: PipelineStage = {
89
89
  storyId: ctx.prd.userStories[0]?.id ?? "US-001",
90
90
  codebaseContext: "",
91
91
  config: ctx.config,
92
+ testStrategy: ctx.config.acceptance.testStrategy,
93
+ testFramework: ctx.config.acceptance.testFramework,
92
94
  });
93
95
  } else {
94
96
  refinedCriteria = allCriteria.map((c) => ({
@@ -108,6 +110,8 @@ export const acceptanceSetupStage: PipelineStage = {
108
110
  modelTier: ctx.config.acceptance.model ?? "fast",
109
111
  modelDef: resolveModel(ctx.config.models[ctx.config.acceptance.model ?? "fast"]),
110
112
  config: ctx.config,
113
+ testStrategy: ctx.config.acceptance.testStrategy,
114
+ testFramework: ctx.config.acceptance.testFramework,
111
115
  });
112
116
 
113
117
  await _acceptanceSetupDeps.writeFile(testPath, result.testCode);
@@ -107,7 +107,7 @@ export const executionStage: PipelineStage = {
107
107
  const logger = getLogger();
108
108
 
109
109
  // HARD FAILURE: No agent available — cannot proceed without an agent
110
- const agent = _executionDeps.getAgent(ctx.config.autoMode.defaultAgent);
110
+ const agent = (ctx.agentGetFn ?? _executionDeps.getAgent)(ctx.config.autoMode.defaultAgent);
111
111
  if (!agent) {
112
112
  return {
113
113
  action: "fail",
@@ -133,6 +133,7 @@ export const executionStage: PipelineStage = {
133
133
  config: ctx.config,
134
134
  workdir: ctx.workdir,
135
135
  modelTier: ctx.routing.modelTier,
136
+ featureName: ctx.prd.feature,
136
137
  contextMarkdown: ctx.contextMarkdown,
137
138
  constitution: ctx.constitution?.content,
138
139
  dryRun: false,
@@ -217,6 +218,37 @@ export const executionStage: PipelineStage = {
217
218
  modelDef: resolveModel(ctx.config.models[ctx.routing.modelTier]),
218
219
  timeoutSeconds: ctx.config.execution.sessionTimeoutSeconds,
219
220
  dangerouslySkipPermissions: ctx.config.execution.dangerouslySkipPermissions,
221
+ pidRegistry: ctx.pidRegistry,
222
+ featureName: ctx.prd.feature,
223
+ storyId: ctx.story.id,
224
+ // No sessionRole for single-session strategies (no role suffix in session name)
225
+ interactionBridge: (() => {
226
+ const plugin = ctx.interaction?.getPrimary();
227
+ if (!plugin) return undefined;
228
+ const QUESTION_PATTERNS = [/\?/, /\bwhich\b/i, /\bshould i\b/i, /\bunclear\b/i, /\bplease clarify\b/i];
229
+ return {
230
+ detectQuestion: async (text: string) => QUESTION_PATTERNS.some((p) => p.test(text)),
231
+ onQuestionDetected: async (text: string) => {
232
+ const requestId = `ix-acp-${Date.now()}-${Math.random().toString(36).slice(2, 9)}`;
233
+ await plugin.send({
234
+ id: requestId,
235
+ type: "input",
236
+ featureName: ctx.prd.feature,
237
+ storyId: ctx.story.id,
238
+ stage: "execution",
239
+ summary: text,
240
+ fallback: "continue",
241
+ createdAt: Date.now(),
242
+ });
243
+ try {
244
+ const response = await plugin.receive(requestId, 120_000);
245
+ return response.value ?? "continue";
246
+ } catch {
247
+ return "continue";
248
+ }
249
+ },
250
+ };
251
+ })(),
220
252
  });
221
253
 
222
254
  ctx.agentResult = result;
@@ -44,7 +44,13 @@ import type { PipelineContext, PipelineStage, RoutingResult, StageResult } from
44
44
  * Used as the default implementation in _routingDeps.runDecompose.
45
45
  * In production, replace with an LLM-backed adapter.
46
46
  */
47
- async function runDecompose(story: UserStory, prd: PRD, config: NaxConfig, _workdir: string): Promise<DecomposeResult> {
47
+ async function runDecompose(
48
+ story: UserStory,
49
+ prd: PRD,
50
+ config: NaxConfig,
51
+ _workdir: string,
52
+ agentGetFn?: import("../types").AgentGetFn,
53
+ ): Promise<DecomposeResult> {
48
54
  const naxDecompose = config.decompose;
49
55
  const builderConfig: BuilderDecomposeConfig = {
50
56
  maxSubStories: naxDecompose?.maxSubstories ?? 5,
@@ -52,10 +58,15 @@ async function runDecompose(story: UserStory, prd: PRD, config: NaxConfig, _work
52
58
  maxRetries: naxDecompose?.maxRetries ?? 2,
53
59
  };
54
60
 
55
- // Stub adapter replaced in tests via _routingDeps injection.
61
+ // Resolve the default agent adapter for LLM-backed decompose.
62
+ // Falls back to agent.complete() with JSON mode — works with both CLI and ACP adapters.
63
+ const agent = (agentGetFn ?? getAgent)(config.autoMode.defaultAgent);
64
+ if (!agent) {
65
+ throw new Error(`[decompose] Agent "${config.autoMode.defaultAgent}" not found — cannot decompose`);
66
+ }
56
67
  const adapter = {
57
- async decompose(_prompt: string): Promise<string> {
58
- throw new Error("[decompose] No LLM adapter configured for story decomposition");
68
+ async decompose(prompt: string): Promise<string> {
69
+ return agent.complete(prompt, { jsonMode: true });
59
70
  },
60
71
  };
61
72
 
@@ -71,7 +82,7 @@ export const routingStage: PipelineStage = {
71
82
 
72
83
  // Resolve agent adapter for LLM routing (shared with execution)
73
84
  const agentName = ctx.config.execution?.agent ?? "claude";
74
- const adapter = _routingDeps.getAgent(agentName);
85
+ const adapter = (ctx.agentGetFn ?? _routingDeps.getAgent)(agentName);
75
86
 
76
87
  // Staleness detection (RRP-003):
77
88
  // - story.routing absent → cache miss (no prior routing)
@@ -173,7 +184,7 @@ export const routingStage: PipelineStage = {
173
184
  `Story ${ctx.story.id} is oversized (${acCount} ACs) but decompose is disabled — continuing with original`,
174
185
  );
175
186
  } else if (decomposeConfig.trigger === "auto") {
176
- const result = await _routingDeps.runDecompose(ctx.story, ctx.prd, ctx.config, ctx.workdir);
187
+ const result = await _routingDeps.runDecompose(ctx.story, ctx.prd, ctx.config, ctx.workdir, ctx.agentGetFn);
177
188
  if (result.validation.valid) {
178
189
  _routingDeps.applyDecomposition(ctx.prd, result);
179
190
  if (ctx.prdPath) {
@@ -193,7 +204,7 @@ export const routingStage: PipelineStage = {
193
204
  ctx.interaction!,
194
205
  );
195
206
  if (action === "decompose") {
196
- const result = await _routingDeps.runDecompose(ctx.story, ctx.prd, ctx.config, ctx.workdir);
207
+ const result = await _routingDeps.runDecompose(ctx.story, ctx.prd, ctx.config, ctx.workdir, ctx.agentGetFn);
197
208
  if (result.validation.valid) {
198
209
  _routingDeps.applyDecomposition(ctx.prd, result);
199
210
  if (ctx.prdPath) {
@@ -8,6 +8,7 @@ import type { AgentResult } from "../agents/types";
8
8
  import type { NaxConfig } from "../config/schema";
9
9
  import type { ConstitutionResult } from "../constitution/types";
10
10
  import type { BuiltContext } from "../context/types";
11
+ import type { PidRegistry } from "../execution/pid-registry";
11
12
  import type { HooksConfig } from "../hooks/types";
12
13
  import type { InteractionChain } from "../interaction/chain";
13
14
  import type { StoryMetrics } from "../metrics/types";
@@ -52,6 +53,8 @@ export interface RoutingResult {
52
53
  * };
53
54
  * ```
54
55
  */
56
+ export type AgentGetFn = (name: string) => import("../agents/types").AgentAdapter | undefined;
57
+
55
58
  export interface PipelineContext {
56
59
  /** Ngent configuration */
57
60
  config: NaxConfig;
@@ -73,6 +76,13 @@ export interface PipelineContext {
73
76
  hooks: HooksConfig;
74
77
  /** Plugin registry (optional, for plugin-provided extensions) */
75
78
  plugins?: PluginRegistry;
79
+ /**
80
+ * Protocol-aware agent resolver. When set (ACP mode), returns AcpAgentAdapter;
81
+ * falls back to standalone getAgent (CLI mode) when absent.
82
+ */
83
+ agentGetFn?: AgentGetFn;
84
+ /** PID registry for crash recovery — passed through to agent.run() for child process registration. */
85
+ pidRegistry?: PidRegistry;
76
86
  /** Interaction chain (optional, for human-in-the-loop triggers) */
77
87
  interaction?: InteractionChain;
78
88
  /** Constitution result (set by constitutionStage) */
@@ -28,6 +28,8 @@ export interface ThreeSessionTddOptions {
28
28
  config: NaxConfig;
29
29
  workdir: string;
30
30
  modelTier: ModelTier;
31
+ /** Feature name — used for ACP session naming (nax-<hash>-<feature>-<story>-<role>) */
32
+ featureName?: string;
31
33
  contextMarkdown?: string;
32
34
  constitution?: string;
33
35
  dryRun?: boolean;
@@ -45,6 +47,7 @@ export async function runThreeSessionTdd(options: ThreeSessionTddOptions): Promi
45
47
  config,
46
48
  workdir,
47
49
  modelTier,
50
+ featureName,
48
51
  contextMarkdown,
49
52
  constitution,
50
53
  dryRun = false,
@@ -135,6 +138,7 @@ export async function runThreeSessionTdd(options: ThreeSessionTddOptions): Promi
135
138
  lite,
136
139
  lite,
137
140
  constitution,
141
+ featureName,
138
142
  );
139
143
  sessions.push(session1);
140
144
  }
@@ -240,6 +244,7 @@ export async function runThreeSessionTdd(options: ThreeSessionTddOptions): Promi
240
244
  lite,
241
245
  lite,
242
246
  constitution,
247
+ featureName,
243
248
  );
244
249
  sessions.push(session2);
245
250
 
@@ -269,6 +274,7 @@ export async function runThreeSessionTdd(options: ThreeSessionTddOptions): Promi
269
274
  contextMarkdown,
270
275
  lite,
271
276
  logger,
277
+ featureName,
272
278
  );
273
279
 
274
280
  // Session 3: Verifier
@@ -286,6 +292,7 @@ export async function runThreeSessionTdd(options: ThreeSessionTddOptions): Promi
286
292
  false,
287
293
  false,
288
294
  constitution,
295
+ featureName,
289
296
  );
290
297
  sessions.push(session3);
291
298
 
@@ -34,6 +34,7 @@ export async function runFullSuiteGate(
34
34
  contextMarkdown: string | undefined,
35
35
  lite: boolean,
36
36
  logger: ReturnType<typeof getLogger>,
37
+ featureName?: string,
37
38
  ): Promise<boolean> {
38
39
  const rectificationEnabled = config.execution.rectification?.enabled ?? false;
39
40
  if (!rectificationEnabled) return false;
@@ -67,6 +68,7 @@ export async function runFullSuiteGate(
67
68
  rectificationConfig,
68
69
  testCmd,
69
70
  fullSuiteTimeout,
71
+ featureName,
70
72
  );
71
73
  }
72
74
 
@@ -118,6 +120,7 @@ async function runRectificationLoop(
118
120
  rectificationConfig: NonNullable<NaxConfig["execution"]["rectification"]>,
119
121
  testCmd: string,
120
122
  fullSuiteTimeout: number,
123
+ featureName?: string,
121
124
  ): Promise<boolean> {
122
125
  const rectificationState: RectificationState = {
123
126
  attempt: 0,
@@ -156,6 +159,9 @@ async function runRectificationLoop(
156
159
  modelDef: resolveModel(config.models[implementerTier]),
157
160
  timeoutSeconds: config.execution.sessionTimeoutSeconds,
158
161
  dangerouslySkipPermissions: config.execution.dangerouslySkipPermissions,
162
+ featureName,
163
+ storyId: story.id,
164
+ sessionRole: "implementer",
159
165
  });
160
166
 
161
167
  if (!rectifyResult.success && rectifyResult.pid) {
@@ -84,6 +84,7 @@ export async function runTddSession(
84
84
  lite = false,
85
85
  skipIsolation = false,
86
86
  constitution?: string,
87
+ featureName?: string,
87
88
  ): Promise<TddSessionResult> {
88
89
  const startTime = Date.now();
89
90
 
@@ -130,6 +131,9 @@ export async function runTddSession(
130
131
  modelDef: resolveModel(config.models[modelTier]),
131
132
  timeoutSeconds: config.execution.sessionTimeoutSeconds,
132
133
  dangerouslySkipPermissions: config.execution.dangerouslySkipPermissions,
134
+ featureName,
135
+ storyId: story.id,
136
+ sessionRole: role,
133
137
  });
134
138
 
135
139
  // BUG-21 Fix: Clean up orphaned child processes if agent failed