substrate-ai 0.2.27 → 0.2.29

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.
@@ -539,7 +539,7 @@ const migration010RunMetrics = {
539
539
 
540
540
  //#endregion
541
541
  //#region src/persistence/migrations/index.ts
542
- const logger$20 = createLogger("persistence:migrations");
542
+ const logger$21 = createLogger("persistence:migrations");
543
543
  const MIGRATIONS = [
544
544
  initialSchemaMigration,
545
545
  costTrackerSchemaMigration,
@@ -557,7 +557,7 @@ const MIGRATIONS = [
557
557
  * Safe to call multiple times — already-applied migrations are skipped.
558
558
  */
559
559
  function runMigrations(db) {
560
- logger$20.info("Starting migration runner");
560
+ logger$21.info("Starting migration runner");
561
561
  db.exec(`
562
562
  CREATE TABLE IF NOT EXISTS schema_migrations (
563
563
  version INTEGER PRIMARY KEY,
@@ -568,12 +568,12 @@ function runMigrations(db) {
568
568
  const appliedVersions = new Set(db.prepare("SELECT version FROM schema_migrations").all().map((row) => row.version));
569
569
  const pending = MIGRATIONS.filter((m) => !appliedVersions.has(m.version)).sort((a, b) => a.version - b.version);
570
570
  if (pending.length === 0) {
571
- logger$20.info("No pending migrations");
571
+ logger$21.info("No pending migrations");
572
572
  return;
573
573
  }
574
574
  const insertMigration = db.prepare("INSERT INTO schema_migrations (version, name) VALUES (?, ?)");
575
575
  for (const migration of pending) {
576
- logger$20.info({
576
+ logger$21.info({
577
577
  version: migration.version,
578
578
  name: migration.name
579
579
  }, "Applying migration");
@@ -587,14 +587,14 @@ function runMigrations(db) {
587
587
  });
588
588
  applyMigration();
589
589
  }
590
- logger$20.info({ version: migration.version }, "Migration applied successfully");
590
+ logger$21.info({ version: migration.version }, "Migration applied successfully");
591
591
  }
592
- logger$20.info({ count: pending.length }, "All pending migrations applied");
592
+ logger$21.info({ count: pending.length }, "All pending migrations applied");
593
593
  }
594
594
 
595
595
  //#endregion
596
596
  //#region src/persistence/database.ts
597
- const logger$19 = createLogger("persistence:database");
597
+ const logger$20 = createLogger("persistence:database");
598
598
  /**
599
599
  * Thin wrapper that opens a SQLite database, applies required PRAGMAs,
600
600
  * and exposes the raw BetterSqlite3 instance.
@@ -611,14 +611,14 @@ var DatabaseWrapper = class {
611
611
  */
612
612
  open() {
613
613
  if (this._db !== null) return;
614
- logger$19.info({ path: this._path }, "Opening SQLite database");
614
+ logger$20.info({ path: this._path }, "Opening SQLite database");
615
615
  this._db = new BetterSqlite3(this._path);
616
616
  const walResult = this._db.pragma("journal_mode = WAL");
617
- if (walResult?.[0]?.journal_mode !== "wal") logger$19.warn({ result: walResult?.[0]?.journal_mode }, "WAL pragma did not return expected \"wal\" — journal_mode may be \"memory\" or unsupported");
617
+ if (walResult?.[0]?.journal_mode !== "wal") logger$20.warn({ result: walResult?.[0]?.journal_mode }, "WAL pragma did not return expected \"wal\" — journal_mode may be \"memory\" or unsupported");
618
618
  this._db.pragma("busy_timeout = 5000");
619
619
  this._db.pragma("synchronous = NORMAL");
620
620
  this._db.pragma("foreign_keys = ON");
621
- logger$19.info({ path: this._path }, "SQLite database opened with WAL mode");
621
+ logger$20.info({ path: this._path }, "SQLite database opened with WAL mode");
622
622
  }
623
623
  /**
624
624
  * Close the database. Idempotent — calling close() when already closed is a no-op.
@@ -627,7 +627,7 @@ var DatabaseWrapper = class {
627
627
  if (this._db === null) return;
628
628
  this._db.close();
629
629
  this._db = null;
630
- logger$19.info({ path: this._path }, "SQLite database closed");
630
+ logger$20.info({ path: this._path }, "SQLite database closed");
631
631
  }
632
632
  /**
633
633
  * Return the raw BetterSqlite3 instance.
@@ -2675,7 +2675,7 @@ function truncateToTokens(text, maxTokens) {
2675
2675
 
2676
2676
  //#endregion
2677
2677
  //#region src/modules/context-compiler/context-compiler-impl.ts
2678
- const logger$18 = createLogger("context-compiler");
2678
+ const logger$19 = createLogger("context-compiler");
2679
2679
  /**
2680
2680
  * Fraction of the original token budget that must remain (after required +
2681
2681
  * important sections) before an optional section is included.
@@ -2767,7 +2767,7 @@ var ContextCompilerImpl = class {
2767
2767
  includedParts.push(truncated);
2768
2768
  remainingBudget -= truncatedTokens;
2769
2769
  anyTruncated = true;
2770
- logger$18.warn({
2770
+ logger$19.warn({
2771
2771
  section: section.name,
2772
2772
  originalTokens: tokens,
2773
2773
  budgetTokens: truncatedTokens
@@ -2781,7 +2781,7 @@ var ContextCompilerImpl = class {
2781
2781
  });
2782
2782
  } else {
2783
2783
  anyTruncated = true;
2784
- logger$18.warn({
2784
+ logger$19.warn({
2785
2785
  section: section.name,
2786
2786
  tokens
2787
2787
  }, "Context compiler: omitted \"important\" section — no budget remaining");
@@ -2808,7 +2808,7 @@ var ContextCompilerImpl = class {
2808
2808
  } else {
2809
2809
  if (tokens > 0) {
2810
2810
  anyTruncated = true;
2811
- logger$18.warn({
2811
+ logger$19.warn({
2812
2812
  section: section.name,
2813
2813
  tokens,
2814
2814
  budgetFractionRemaining: budgetFractionRemaining.toFixed(2)
@@ -3093,7 +3093,7 @@ function parseYamlResult(yamlText, schema) {
3093
3093
 
3094
3094
  //#endregion
3095
3095
  //#region src/modules/agent-dispatch/dispatcher-impl.ts
3096
- const logger$17 = createLogger("agent-dispatch");
3096
+ const logger$18 = createLogger("agent-dispatch");
3097
3097
  const SHUTDOWN_GRACE_MS = 1e4;
3098
3098
  const SHUTDOWN_MAX_WAIT_MS = 3e4;
3099
3099
  const CHARS_PER_TOKEN = 4;
@@ -3138,7 +3138,7 @@ function getAvailableMemory() {
3138
3138
  }).trim(), 10);
3139
3139
  _lastKnownPressureLevel = pressureLevel;
3140
3140
  if (pressureLevel >= 4) {
3141
- logger$17.warn({ pressureLevel }, "macOS kernel reports critical memory pressure");
3141
+ logger$18.warn({ pressureLevel }, "macOS kernel reports critical memory pressure");
3142
3142
  return 0;
3143
3143
  }
3144
3144
  } catch {}
@@ -3153,7 +3153,7 @@ function getAvailableMemory() {
3153
3153
  const speculative = parseInt(vmstat.match(/Pages speculative:\s+(\d+)/)?.[1] ?? "0", 10);
3154
3154
  const available = (free + purgeable + speculative) * pageSize;
3155
3155
  if (pressureLevel >= 2) {
3156
- logger$17.warn({
3156
+ logger$18.warn({
3157
3157
  pressureLevel,
3158
3158
  availableBeforeDiscount: available
3159
3159
  }, "macOS kernel reports memory pressure — discounting estimate");
@@ -3233,7 +3233,7 @@ var DispatcherImpl = class {
3233
3233
  resolve: typedResolve,
3234
3234
  reject
3235
3235
  });
3236
- logger$17.debug({
3236
+ logger$18.debug({
3237
3237
  id,
3238
3238
  queueLength: this._queue.length
3239
3239
  }, "Dispatch queued");
@@ -3264,7 +3264,7 @@ var DispatcherImpl = class {
3264
3264
  async shutdown() {
3265
3265
  this._shuttingDown = true;
3266
3266
  this._stopMemoryPressureTimer();
3267
- logger$17.info({
3267
+ logger$18.info({
3268
3268
  running: this._running.size,
3269
3269
  queued: this._queue.length
3270
3270
  }, "Dispatcher shutting down");
@@ -3297,13 +3297,13 @@ var DispatcherImpl = class {
3297
3297
  }
3298
3298
  }, 50);
3299
3299
  });
3300
- logger$17.info("Dispatcher shutdown complete");
3300
+ logger$18.info("Dispatcher shutdown complete");
3301
3301
  }
3302
3302
  async _startDispatch(id, request, resolve$2) {
3303
3303
  const { prompt, agent, taskType, timeout, outputSchema, workingDirectory, model, maxTurns } = request;
3304
3304
  const adapter = this._adapterRegistry.get(agent);
3305
3305
  if (adapter === void 0) {
3306
- logger$17.warn({
3306
+ logger$18.warn({
3307
3307
  id,
3308
3308
  agent
3309
3309
  }, "No adapter found for agent");
@@ -3349,7 +3349,7 @@ var DispatcherImpl = class {
3349
3349
  });
3350
3350
  const startedAt = Date.now();
3351
3351
  proc.on("error", (err) => {
3352
- logger$17.error({
3352
+ logger$18.error({
3353
3353
  id,
3354
3354
  binary: cmd.binary,
3355
3355
  error: err.message
@@ -3357,7 +3357,7 @@ var DispatcherImpl = class {
3357
3357
  });
3358
3358
  if (proc.stdin !== null) {
3359
3359
  proc.stdin.on("error", (err) => {
3360
- if (err.code !== "EPIPE") logger$17.warn({
3360
+ if (err.code !== "EPIPE") logger$18.warn({
3361
3361
  id,
3362
3362
  error: err.message
3363
3363
  }, "stdin write error");
@@ -3399,7 +3399,7 @@ var DispatcherImpl = class {
3399
3399
  agent,
3400
3400
  taskType
3401
3401
  });
3402
- logger$17.debug({
3402
+ logger$18.debug({
3403
3403
  id,
3404
3404
  agent,
3405
3405
  taskType,
@@ -3416,7 +3416,7 @@ var DispatcherImpl = class {
3416
3416
  dispatchId: id,
3417
3417
  timeoutMs
3418
3418
  });
3419
- logger$17.warn({
3419
+ logger$18.warn({
3420
3420
  id,
3421
3421
  agent,
3422
3422
  taskType,
@@ -3470,7 +3470,7 @@ var DispatcherImpl = class {
3470
3470
  exitCode: code,
3471
3471
  output: stdout
3472
3472
  });
3473
- logger$17.debug({
3473
+ logger$18.debug({
3474
3474
  id,
3475
3475
  agent,
3476
3476
  taskType,
@@ -3496,7 +3496,7 @@ var DispatcherImpl = class {
3496
3496
  error: stderr || `Process exited with code ${String(code)}`,
3497
3497
  exitCode: code
3498
3498
  });
3499
- logger$17.debug({
3499
+ logger$18.debug({
3500
3500
  id,
3501
3501
  agent,
3502
3502
  taskType,
@@ -3555,7 +3555,7 @@ var DispatcherImpl = class {
3555
3555
  const next = this._queue.shift();
3556
3556
  if (next === void 0) return;
3557
3557
  next.handle.status = "running";
3558
- logger$17.debug({
3558
+ logger$18.debug({
3559
3559
  id: next.id,
3560
3560
  queueLength: this._queue.length
3561
3561
  }, "Dequeued dispatch");
@@ -3568,7 +3568,7 @@ var DispatcherImpl = class {
3568
3568
  _isMemoryPressured() {
3569
3569
  const free = getAvailableMemory();
3570
3570
  if (free < MIN_FREE_MEMORY_BYTES) {
3571
- logger$17.warn({
3571
+ logger$18.warn({
3572
3572
  freeMB: Math.round(free / 1024 / 1024),
3573
3573
  thresholdMB: Math.round(MIN_FREE_MEMORY_BYTES / 1024 / 1024),
3574
3574
  pressureLevel: _lastKnownPressureLevel
@@ -3608,6 +3608,54 @@ var DispatcherImpl = class {
3608
3608
  };
3609
3609
  /** Default command for the build verification gate */
3610
3610
  const DEFAULT_VERIFY_COMMAND = "npm run build";
3611
+ /**
3612
+ * Detect the package manager used in a project by checking for lockfiles.
3613
+ *
3614
+ * Priority order (checked in sequence):
3615
+ * pnpm-lock.yaml → pnpm run build
3616
+ * yarn.lock → yarn run build
3617
+ * bun.lockb → bun run build
3618
+ * package-lock.json → npm run build
3619
+ * (none found) → npm run build (fallback, matches DEFAULT_VERIFY_COMMAND)
3620
+ *
3621
+ * Lockfile presence is used rather than the `packageManager` field in
3622
+ * package.json because lockfiles are more reliable and always present in
3623
+ * real projects.
3624
+ */
3625
+ function detectPackageManager(projectRoot) {
3626
+ const candidates = [
3627
+ {
3628
+ file: "pnpm-lock.yaml",
3629
+ packageManager: "pnpm",
3630
+ command: "pnpm run build"
3631
+ },
3632
+ {
3633
+ file: "yarn.lock",
3634
+ packageManager: "yarn",
3635
+ command: "yarn run build"
3636
+ },
3637
+ {
3638
+ file: "bun.lockb",
3639
+ packageManager: "bun",
3640
+ command: "bun run build"
3641
+ },
3642
+ {
3643
+ file: "package-lock.json",
3644
+ packageManager: "npm",
3645
+ command: "npm run build"
3646
+ }
3647
+ ];
3648
+ for (const candidate of candidates) if (existsSync$1(join$1(projectRoot, candidate.file))) return {
3649
+ packageManager: candidate.packageManager,
3650
+ lockfile: candidate.file,
3651
+ command: candidate.command
3652
+ };
3653
+ return {
3654
+ packageManager: "npm",
3655
+ lockfile: null,
3656
+ command: DEFAULT_VERIFY_COMMAND
3657
+ };
3658
+ }
3611
3659
  /** Default timeout in milliseconds for the build verification gate */
3612
3660
  const DEFAULT_VERIFY_TIMEOUT_MS = 6e4;
3613
3661
  /**
@@ -3624,7 +3672,16 @@ const DEFAULT_VERIFY_TIMEOUT_MS = 6e4;
3624
3672
  */
3625
3673
  function runBuildVerification(options) {
3626
3674
  const { verifyCommand, verifyTimeoutMs, projectRoot } = options;
3627
- const cmd = verifyCommand === void 0 ? DEFAULT_VERIFY_COMMAND : verifyCommand;
3675
+ let cmd;
3676
+ if (verifyCommand === void 0) {
3677
+ const detection = detectPackageManager(projectRoot);
3678
+ logger$18.info({
3679
+ packageManager: detection.packageManager,
3680
+ lockfile: detection.lockfile,
3681
+ resolvedCommand: detection.command
3682
+ }, "Build verification: resolved command via package manager detection");
3683
+ cmd = detection.command;
3684
+ } else cmd = verifyCommand;
3628
3685
  if (!cmd) return { status: "skipped" };
3629
3686
  const timeoutMs = verifyTimeoutMs ?? DEFAULT_VERIFY_TIMEOUT_MS;
3630
3687
  try {
@@ -3817,7 +3874,7 @@ function pickRecommendation(distribution, profile, totalIssues, reviewCycles, la
3817
3874
 
3818
3875
  //#endregion
3819
3876
  //#region src/modules/compiled-workflows/prompt-assembler.ts
3820
- const logger$16 = createLogger("compiled-workflows:prompt-assembler");
3877
+ const logger$17 = createLogger("compiled-workflows:prompt-assembler");
3821
3878
  /**
3822
3879
  * Assemble a final prompt from a template and sections map.
3823
3880
  *
@@ -3842,7 +3899,7 @@ function assemblePrompt(template, sections, tokenCeiling = 2200) {
3842
3899
  tokenCount,
3843
3900
  truncated: false
3844
3901
  };
3845
- logger$16.warn({
3902
+ logger$17.warn({
3846
3903
  tokenCount,
3847
3904
  ceiling: tokenCeiling
3848
3905
  }, "Prompt exceeds token ceiling — truncating optional sections");
@@ -3858,10 +3915,10 @@ function assemblePrompt(template, sections, tokenCeiling = 2200) {
3858
3915
  const targetSectionTokens = Math.max(0, currentSectionTokens - overBy);
3859
3916
  if (targetSectionTokens === 0) {
3860
3917
  contentMap[section.name] = "";
3861
- logger$16.warn({ sectionName: section.name }, "Section eliminated to fit token budget");
3918
+ logger$17.warn({ sectionName: section.name }, "Section eliminated to fit token budget");
3862
3919
  } else {
3863
3920
  contentMap[section.name] = truncateToTokens(section.content, targetSectionTokens);
3864
- logger$16.warn({
3921
+ logger$17.warn({
3865
3922
  sectionName: section.name,
3866
3923
  targetSectionTokens
3867
3924
  }, "Section truncated to fit token budget");
@@ -3872,7 +3929,7 @@ function assemblePrompt(template, sections, tokenCeiling = 2200) {
3872
3929
  }
3873
3930
  if (tokenCount <= tokenCeiling) break;
3874
3931
  }
3875
- if (tokenCount > tokenCeiling) logger$16.warn({
3932
+ if (tokenCount > tokenCeiling) logger$17.warn({
3876
3933
  tokenCount,
3877
3934
  ceiling: tokenCeiling
3878
3935
  }, "Required sections alone exceed token ceiling — returning over-budget prompt");
@@ -3959,6 +4016,20 @@ const CodeReviewIssueSchema = z.object({
3959
4016
  line: coerceOptionalNumber
3960
4017
  });
3961
4018
  /**
4019
+ * Schema for a single entry in the AC verification checklist emitted by
4020
+ * the code-review sub-agent. Each entry maps an acceptance criterion to a
4021
+ * status determination backed by concrete evidence from the diff.
4022
+ */
4023
+ const AcChecklistEntrySchema = z.object({
4024
+ ac_id: z.string(),
4025
+ status: z.enum([
4026
+ "met",
4027
+ "not_met",
4028
+ "partial"
4029
+ ]),
4030
+ evidence: z.string()
4031
+ });
4032
+ /**
3962
4033
  * Compute the verdict from the issue list using deterministic rules.
3963
4034
  *
3964
4035
  * The agent reports issues with severities; the pipeline computes the
@@ -3980,7 +4051,8 @@ function computeVerdict(issueList) {
3980
4051
  /**
3981
4052
  * Schema for the YAML output contract of the code-review sub-agent.
3982
4053
  *
3983
- * The agent must emit YAML with verdict, issues count, and issue_list.
4054
+ * The agent must emit YAML with verdict, issues count, issue_list, and
4055
+ * optionally an ac_checklist mapping each AC to its implementation status.
3984
4056
  * Example:
3985
4057
  * verdict: NEEDS_MINOR_FIXES
3986
4058
  * issues: 3
@@ -3989,11 +4061,20 @@ function computeVerdict(issueList) {
3989
4061
  * description: "Missing error handling in createFoo()"
3990
4062
  * file: "src/modules/foo/foo.ts"
3991
4063
  * line: 42
4064
+ * ac_checklist:
4065
+ * - ac_id: AC1
4066
+ * status: met
4067
+ * evidence: "Implemented in src/modules/foo/foo.ts:createFoo()"
4068
+ * - ac_id: AC2
4069
+ * status: not_met
4070
+ * evidence: "No implementation found for getConstraints()"
3992
4071
  * notes: "Generally clean implementation."
3993
4072
  *
3994
- * The transform auto-corrects the issues count and recomputes the verdict
3995
- * from the issue list. The agent's original verdict is preserved as
3996
- * `agentVerdict` for logging and debugging.
4073
+ * The transform:
4074
+ * 1. Auto-corrects the issues count from issue_list length.
4075
+ * 2. Auto-injects a major issue for each not_met AC not already flagged.
4076
+ * 3. Recomputes the verdict from the (possibly augmented) issue list.
4077
+ * The agent's original verdict is preserved as `agentVerdict` for logging.
3997
4078
  */
3998
4079
  const CodeReviewResultSchema = z.object({
3999
4080
  verdict: z.enum([
@@ -4003,13 +4084,27 @@ const CodeReviewResultSchema = z.object({
4003
4084
  ]),
4004
4085
  issues: coerceNumber,
4005
4086
  issue_list: z.array(CodeReviewIssueSchema),
4087
+ ac_checklist: z.array(AcChecklistEntrySchema).default([]),
4006
4088
  notes: z.string().optional()
4007
- }).transform((data) => ({
4008
- ...data,
4009
- issues: data.issue_list.length,
4010
- agentVerdict: data.verdict,
4011
- verdict: computeVerdict(data.issue_list)
4012
- }));
4089
+ }).transform((data) => {
4090
+ const augmentedIssueList = [...data.issue_list];
4091
+ for (const entry of data.ac_checklist) if (entry.status === "not_met") {
4092
+ const acIdPattern = new RegExp(`\\b${entry.ac_id}\\b`);
4093
+ const alreadyFlagged = augmentedIssueList.some((issue) => (issue.severity === "major" || issue.severity === "blocker") && acIdPattern.test(issue.description));
4094
+ if (!alreadyFlagged) augmentedIssueList.push({
4095
+ severity: "major",
4096
+ description: `${entry.ac_id} not implemented: ${entry.evidence}`,
4097
+ file: ""
4098
+ });
4099
+ }
4100
+ return {
4101
+ ...data,
4102
+ issue_list: augmentedIssueList,
4103
+ issues: augmentedIssueList.length,
4104
+ agentVerdict: data.verdict,
4105
+ verdict: computeVerdict(augmentedIssueList)
4106
+ };
4107
+ });
4013
4108
  /**
4014
4109
  * Schema for the YAML output contract of the test-plan sub-agent.
4015
4110
  *
@@ -4087,12 +4182,46 @@ const TestExpansionResultSchema = z.object({
4087
4182
  });
4088
4183
 
4089
4184
  //#endregion
4090
- //#region src/modules/compiled-workflows/create-story.ts
4091
- const logger$15 = createLogger("compiled-workflows:create-story");
4185
+ //#region src/modules/compiled-workflows/token-ceiling.ts
4186
+ /**
4187
+ * Default token ceilings for each compiled workflow.
4188
+ * These match the hardcoded constants previously defined inline in each workflow.
4189
+ */
4190
+ const TOKEN_CEILING_DEFAULTS = {
4191
+ "create-story": 3e3,
4192
+ "dev-story": 24e3,
4193
+ "code-review": 1e5,
4194
+ "test-plan": 8e3,
4195
+ "test-expansion": 2e4
4196
+ };
4092
4197
  /**
4093
- * Hard ceiling for the assembled create-story prompt.
4198
+ * Resolve the effective token ceiling for a workflow type.
4199
+ *
4200
+ * Returns the ceiling from `tokenCeilings` config if present and valid,
4201
+ * otherwise falls back to the hardcoded default.
4202
+ *
4203
+ * @param workflowType - One of: 'create-story', 'dev-story', 'code-review', 'test-plan', 'test-expansion'
4204
+ * @param tokenCeilings - Optional per-workflow overrides from parsed config
4205
+ * @returns `{ ceiling: number, source: 'config' | 'default' }`
4094
4206
  */
4095
- const TOKEN_CEILING$4 = 3e3;
4207
+ function getTokenCeiling(workflowType, tokenCeilings) {
4208
+ if (tokenCeilings !== void 0) {
4209
+ const configured = tokenCeilings[workflowType];
4210
+ if (configured !== void 0) return {
4211
+ ceiling: configured,
4212
+ source: "config"
4213
+ };
4214
+ }
4215
+ const defaultValue = TOKEN_CEILING_DEFAULTS[workflowType] ?? 0;
4216
+ return {
4217
+ ceiling: defaultValue,
4218
+ source: "default"
4219
+ };
4220
+ }
4221
+
4222
+ //#endregion
4223
+ //#region src/modules/compiled-workflows/create-story.ts
4224
+ const logger$16 = createLogger("compiled-workflows:create-story");
4096
4225
  /**
4097
4226
  * Execute the compiled create-story workflow.
4098
4227
  *
@@ -4112,17 +4241,23 @@ const TOKEN_CEILING$4 = 3e3;
4112
4241
  */
4113
4242
  async function runCreateStory(deps, params) {
4114
4243
  const { epicId, storyKey, pipelineRunId } = params;
4115
- logger$15.debug({
4244
+ logger$16.debug({
4116
4245
  epicId,
4117
4246
  storyKey,
4118
4247
  pipelineRunId
4119
4248
  }, "Starting create-story workflow");
4249
+ const { ceiling: TOKEN_CEILING, source: tokenCeilingSource } = getTokenCeiling("create-story", deps.tokenCeilings);
4250
+ logger$16.info({
4251
+ workflow: "create-story",
4252
+ ceiling: TOKEN_CEILING,
4253
+ source: tokenCeilingSource
4254
+ }, "Token ceiling resolved");
4120
4255
  let template;
4121
4256
  try {
4122
4257
  template = await deps.pack.getPrompt("create-story");
4123
4258
  } catch (err) {
4124
4259
  const error = err instanceof Error ? err.message : String(err);
4125
- logger$15.error({ error }, "Failed to retrieve create-story prompt template");
4260
+ logger$16.error({ error }, "Failed to retrieve create-story prompt template");
4126
4261
  return {
4127
4262
  result: "failed",
4128
4263
  error: `Failed to retrieve prompt template: ${error}`,
@@ -4163,11 +4298,11 @@ async function runCreateStory(deps, params) {
4163
4298
  content: storyTemplateContent,
4164
4299
  priority: "important"
4165
4300
  }
4166
- ], TOKEN_CEILING$4);
4167
- logger$15.debug({
4301
+ ], TOKEN_CEILING);
4302
+ logger$16.debug({
4168
4303
  tokenCount,
4169
4304
  truncated,
4170
- tokenCeiling: TOKEN_CEILING$4
4305
+ tokenCeiling: TOKEN_CEILING
4171
4306
  }, "Prompt assembled for create-story");
4172
4307
  const handle = deps.dispatcher.dispatch({
4173
4308
  prompt,
@@ -4181,7 +4316,7 @@ async function runCreateStory(deps, params) {
4181
4316
  dispatchResult = await handle.result;
4182
4317
  } catch (err) {
4183
4318
  const error = err instanceof Error ? err.message : String(err);
4184
- logger$15.error({
4319
+ logger$16.error({
4185
4320
  epicId,
4186
4321
  storyKey,
4187
4322
  error
@@ -4202,7 +4337,7 @@ async function runCreateStory(deps, params) {
4202
4337
  if (dispatchResult.status === "failed") {
4203
4338
  const errorMsg = dispatchResult.parseError ?? `Dispatch failed with exit code ${dispatchResult.exitCode}`;
4204
4339
  const stderrDetail = dispatchResult.output ? ` Output: ${dispatchResult.output}` : "";
4205
- logger$15.warn({
4340
+ logger$16.warn({
4206
4341
  epicId,
4207
4342
  storyKey,
4208
4343
  exitCode: dispatchResult.exitCode
@@ -4214,7 +4349,7 @@ async function runCreateStory(deps, params) {
4214
4349
  };
4215
4350
  }
4216
4351
  if (dispatchResult.status === "timeout") {
4217
- logger$15.warn({
4352
+ logger$16.warn({
4218
4353
  epicId,
4219
4354
  storyKey
4220
4355
  }, "Create-story dispatch timed out");
@@ -4227,7 +4362,7 @@ async function runCreateStory(deps, params) {
4227
4362
  if (dispatchResult.parsed === null) {
4228
4363
  const details = dispatchResult.parseError ?? "No YAML block found in output";
4229
4364
  const rawSnippet = dispatchResult.output ? dispatchResult.output.slice(0, 1e3) : "(empty)";
4230
- logger$15.warn({
4365
+ logger$16.warn({
4231
4366
  epicId,
4232
4367
  storyKey,
4233
4368
  details,
@@ -4243,7 +4378,7 @@ async function runCreateStory(deps, params) {
4243
4378
  const parseResult = CreateStoryResultSchema.safeParse(dispatchResult.parsed);
4244
4379
  if (!parseResult.success) {
4245
4380
  const details = parseResult.error.message;
4246
- logger$15.warn({
4381
+ logger$16.warn({
4247
4382
  epicId,
4248
4383
  storyKey,
4249
4384
  details
@@ -4256,7 +4391,7 @@ async function runCreateStory(deps, params) {
4256
4391
  };
4257
4392
  }
4258
4393
  const parsed = parseResult.data;
4259
- logger$15.info({
4394
+ logger$16.info({
4260
4395
  epicId,
4261
4396
  storyKey,
4262
4397
  storyFile: parsed.story_file,
@@ -4278,7 +4413,7 @@ function getImplementationDecisions(deps) {
4278
4413
  try {
4279
4414
  return getDecisionsByPhase(deps.db, "implementation");
4280
4415
  } catch (err) {
4281
- logger$15.warn({ error: err instanceof Error ? err.message : String(err) }, "Failed to retrieve implementation decisions");
4416
+ logger$16.warn({ error: err instanceof Error ? err.message : String(err) }, "Failed to retrieve implementation decisions");
4282
4417
  return [];
4283
4418
  }
4284
4419
  }
@@ -4321,13 +4456,13 @@ function getEpicShard(decisions, epicId, projectRoot, storyKey) {
4321
4456
  if (storyKey) {
4322
4457
  const storySection = extractStorySection(shardContent, storyKey);
4323
4458
  if (storySection) {
4324
- logger$15.debug({
4459
+ logger$16.debug({
4325
4460
  epicId,
4326
4461
  storyKey
4327
4462
  }, "Extracted per-story section from epic shard");
4328
4463
  return storySection;
4329
4464
  }
4330
- logger$15.debug({
4465
+ logger$16.debug({
4331
4466
  epicId,
4332
4467
  storyKey
4333
4468
  }, "No matching story section found — using full epic shard");
@@ -4337,11 +4472,11 @@ function getEpicShard(decisions, epicId, projectRoot, storyKey) {
4337
4472
  if (projectRoot) {
4338
4473
  const fallback = readEpicShardFromFile(projectRoot, epicId);
4339
4474
  if (fallback) {
4340
- logger$15.info({ epicId }, "Using file-based fallback for epic shard (decisions table empty)");
4475
+ logger$16.info({ epicId }, "Using file-based fallback for epic shard (decisions table empty)");
4341
4476
  if (storyKey) {
4342
4477
  const storySection = extractStorySection(fallback, storyKey);
4343
4478
  if (storySection) {
4344
- logger$15.debug({
4479
+ logger$16.debug({
4345
4480
  epicId,
4346
4481
  storyKey
4347
4482
  }, "Extracted per-story section from file-based epic shard");
@@ -4353,7 +4488,7 @@ function getEpicShard(decisions, epicId, projectRoot, storyKey) {
4353
4488
  }
4354
4489
  return "";
4355
4490
  } catch (err) {
4356
- logger$15.warn({
4491
+ logger$16.warn({
4357
4492
  epicId,
4358
4493
  error: err instanceof Error ? err.message : String(err)
4359
4494
  }, "Failed to retrieve epic shard");
@@ -4370,7 +4505,7 @@ function getPrevDevNotes(decisions, epicId) {
4370
4505
  if (devNotes.length === 0) return "";
4371
4506
  return devNotes[devNotes.length - 1].value;
4372
4507
  } catch (err) {
4373
- logger$15.warn({
4508
+ logger$16.warn({
4374
4509
  epicId,
4375
4510
  error: err instanceof Error ? err.message : String(err)
4376
4511
  }, "Failed to retrieve prev dev notes");
@@ -4390,13 +4525,13 @@ function getArchConstraints$2(deps) {
4390
4525
  if (deps.projectRoot) {
4391
4526
  const fallback = readArchConstraintsFromFile(deps.projectRoot);
4392
4527
  if (fallback) {
4393
- logger$15.info("Using file-based fallback for architecture constraints (decisions table empty)");
4528
+ logger$16.info("Using file-based fallback for architecture constraints (decisions table empty)");
4394
4529
  return fallback;
4395
4530
  }
4396
4531
  }
4397
4532
  return "";
4398
4533
  } catch (err) {
4399
- logger$15.warn({ error: err instanceof Error ? err.message : String(err) }, "Failed to retrieve architecture constraints");
4534
+ logger$16.warn({ error: err instanceof Error ? err.message : String(err) }, "Failed to retrieve architecture constraints");
4400
4535
  return "";
4401
4536
  }
4402
4537
  }
@@ -4416,7 +4551,7 @@ function readEpicShardFromFile(projectRoot, epicId) {
4416
4551
  const match = pattern.exec(content);
4417
4552
  return match ? match[0].trim() : "";
4418
4553
  } catch (err) {
4419
- logger$15.warn({
4554
+ logger$16.warn({
4420
4555
  epicId,
4421
4556
  error: err instanceof Error ? err.message : String(err)
4422
4557
  }, "File-based epic shard fallback failed");
@@ -4439,7 +4574,7 @@ function readArchConstraintsFromFile(projectRoot) {
4439
4574
  const content = readFileSync$1(archPath, "utf-8");
4440
4575
  return content.slice(0, 1500);
4441
4576
  } catch (err) {
4442
- logger$15.warn({ error: err instanceof Error ? err.message : String(err) }, "File-based architecture fallback failed");
4577
+ logger$16.warn({ error: err instanceof Error ? err.message : String(err) }, "File-based architecture fallback failed");
4443
4578
  return "";
4444
4579
  }
4445
4580
  }
@@ -4452,7 +4587,7 @@ async function getStoryTemplate(deps) {
4452
4587
  try {
4453
4588
  return await deps.pack.getTemplate("story");
4454
4589
  } catch (err) {
4455
- logger$15.warn({ error: err instanceof Error ? err.message : String(err) }, "Failed to retrieve story template from pack");
4590
+ logger$16.warn({ error: err instanceof Error ? err.message : String(err) }, "Failed to retrieve story template from pack");
4456
4591
  return "";
4457
4592
  }
4458
4593
  }
@@ -4489,7 +4624,7 @@ async function isValidStoryFile(filePath) {
4489
4624
 
4490
4625
  //#endregion
4491
4626
  //#region src/modules/compiled-workflows/git-helpers.ts
4492
- const logger$14 = createLogger("compiled-workflows:git-helpers");
4627
+ const logger$15 = createLogger("compiled-workflows:git-helpers");
4493
4628
  /**
4494
4629
  * Capture the full git diff for HEAD (working tree vs current commit).
4495
4630
  *
@@ -4585,7 +4720,7 @@ async function stageIntentToAdd(files, workingDirectory) {
4585
4720
  if (files.length === 0) return;
4586
4721
  const existing = files.filter((f) => {
4587
4722
  const exists = existsSync$1(f);
4588
- if (!exists) logger$14.debug({ file: f }, "Skipping nonexistent file in stageIntentToAdd");
4723
+ if (!exists) logger$15.debug({ file: f }, "Skipping nonexistent file in stageIntentToAdd");
4589
4724
  return exists;
4590
4725
  });
4591
4726
  if (existing.length === 0) return;
@@ -4619,7 +4754,7 @@ async function runGitCommand(args, cwd, logLabel) {
4619
4754
  stderr += chunk.toString("utf-8");
4620
4755
  });
4621
4756
  proc.on("error", (err) => {
4622
- logger$14.warn({
4757
+ logger$15.warn({
4623
4758
  label: logLabel,
4624
4759
  cwd,
4625
4760
  error: err.message
@@ -4628,7 +4763,7 @@ async function runGitCommand(args, cwd, logLabel) {
4628
4763
  });
4629
4764
  proc.on("close", (code) => {
4630
4765
  if (code !== 0) {
4631
- logger$14.warn({
4766
+ logger$15.warn({
4632
4767
  label: logLabel,
4633
4768
  cwd,
4634
4769
  code,
@@ -4644,7 +4779,7 @@ async function runGitCommand(args, cwd, logLabel) {
4644
4779
 
4645
4780
  //#endregion
4646
4781
  //#region src/modules/implementation-orchestrator/project-findings.ts
4647
- const logger$13 = createLogger("project-findings");
4782
+ const logger$14 = createLogger("project-findings");
4648
4783
  /** Maximum character length for the findings summary */
4649
4784
  const MAX_CHARS = 2e3;
4650
4785
  /**
@@ -4699,7 +4834,7 @@ function getProjectFindings(db) {
4699
4834
  if (summary.length > MAX_CHARS) summary = summary.slice(0, MAX_CHARS - 3) + "...";
4700
4835
  return summary;
4701
4836
  } catch (err) {
4702
- logger$13.warn({ err }, "Failed to query project findings (graceful fallback)");
4837
+ logger$14.warn({ err }, "Failed to query project findings (graceful fallback)");
4703
4838
  return "";
4704
4839
  }
4705
4840
  }
@@ -4720,11 +4855,119 @@ function extractRecurringPatterns(outcomes) {
4720
4855
  return [...patternCounts.entries()].filter(([, count]) => count >= 2).sort((a, b) => b[1] - a[1]).slice(0, 5).map(([pattern, count]) => `${pattern} (${count} occurrences)`);
4721
4856
  }
4722
4857
 
4858
+ //#endregion
4859
+ //#region src/modules/compiled-workflows/story-complexity.ts
4860
+ const logger$13 = createLogger("compiled-workflows:story-complexity");
4861
+ /**
4862
+ * Compute a complexity score from story markdown content.
4863
+ *
4864
+ * Returns { taskCount, subtaskCount, fileCount, complexityScore }.
4865
+ * All counts default to 0 when the corresponding sections are absent.
4866
+ *
4867
+ * @param storyContent - Raw markdown content of the story file
4868
+ */
4869
+ function computeStoryComplexity(storyContent) {
4870
+ const taskCount = countTopLevelTasks(storyContent);
4871
+ const subtaskCount = countSubtasks(storyContent);
4872
+ const fileCount = countFilesInLayout(storyContent);
4873
+ const complexityScore = Math.round(taskCount + subtaskCount * .5 + fileCount * .5);
4874
+ return {
4875
+ taskCount,
4876
+ subtaskCount,
4877
+ fileCount,
4878
+ complexityScore
4879
+ };
4880
+ }
4881
+ /**
4882
+ * Compute the resolved maxTurns for a dev-story dispatch.
4883
+ *
4884
+ * base 75 turns for score <= 10, +10 turns per additional complexity point, capped at 200.
4885
+ *
4886
+ * @param complexityScore - Score returned by computeStoryComplexity
4887
+ */
4888
+ function resolveDevStoryMaxTurns(complexityScore) {
4889
+ return Math.min(200, 75 + Math.max(0, complexityScore - 10) * 10);
4890
+ }
4891
+ /**
4892
+ * Compute the resolved maxTurns for a fix-story (major-rework) dispatch.
4893
+ *
4894
+ * base 50 turns for score <= 10, +10 turns per additional complexity point, capped at 150.
4895
+ *
4896
+ * @param complexityScore - Score returned by computeStoryComplexity
4897
+ */
4898
+ function resolveFixStoryMaxTurns(complexityScore) {
4899
+ return Math.min(150, 50 + Math.max(0, complexityScore - 10) * 10);
4900
+ }
4901
+ /**
4902
+ * Log the complexity result at info level.
4903
+ *
4904
+ * Emits a structured log with storyKey, taskCount, subtaskCount, fileCount,
4905
+ * complexityScore, and resolvedMaxTurns so operators can observe turn-limit scaling.
4906
+ *
4907
+ * @param storyKey - Story identifier (e.g. "24-6")
4908
+ * @param complexity - Result from computeStoryComplexity
4909
+ * @param resolvedMaxTurns - Turn limit resolved for this dispatch
4910
+ */
4911
+ function logComplexityResult(storyKey, complexity, resolvedMaxTurns) {
4912
+ logger$13.info({
4913
+ storyKey,
4914
+ taskCount: complexity.taskCount,
4915
+ subtaskCount: complexity.subtaskCount,
4916
+ fileCount: complexity.fileCount,
4917
+ complexityScore: complexity.complexityScore,
4918
+ resolvedMaxTurns
4919
+ }, "Story complexity computed");
4920
+ }
4921
+ /**
4922
+ * Count top-level task lines matching `- [ ] Task N:` (with literal "Task" keyword).
4923
+ */
4924
+ function countTopLevelTasks(content) {
4925
+ const taskPattern = /^- \[ \] Task \d+:/gm;
4926
+ return (content.match(taskPattern) ?? []).length;
4927
+ }
4928
+ /**
4929
+ * Count nested subtask lines — `- [ ]` lines that begin with one or more spaces or tabs.
4930
+ *
4931
+ * IMPORTANT: Use `[ \t]+` (space/tab only), NOT `\s+`, because `\s` includes `\n`.
4932
+ * With multiline `/gm`, using `\s+` causes false matches when an empty line (`\n`)
4933
+ * precedes a top-level `- [ ]` line — the `\n` gets consumed as "whitespace".
4934
+ */
4935
+ function countSubtasks(content) {
4936
+ const subtaskPattern = /^[ \t]+- \[ \]/gm;
4937
+ return (content.match(subtaskPattern) ?? []).length;
4938
+ }
4939
+ /**
4940
+ * Count file entries inside a "File Layout" fenced code block.
4941
+ *
4942
+ * Uses a split-based section extraction to avoid regex lazy-matching pitfalls.
4943
+ * Finds a heading containing "File Layout", extracts section content up to the
4944
+ * next heading (or end of string), then finds fenced code blocks within it.
4945
+ *
4946
+ * Returns 0 when no File Layout section or fenced code block is found.
4947
+ */
4948
+ function countFilesInLayout(content) {
4949
+ const headingMatch = content.match(/^#{2,4}\s+File\s+Layout\s*$/im);
4950
+ if (!headingMatch || headingMatch.index === void 0) return 0;
4951
+ const afterHeading = content.slice(headingMatch.index + headingMatch[0].length);
4952
+ const nextHeadingMatch = afterHeading.match(/^#{2,4}\s+/m);
4953
+ const sectionContent = nextHeadingMatch?.index !== void 0 ? afterHeading.slice(0, nextHeadingMatch.index) : afterHeading;
4954
+ const codeBlocks = sectionContent.match(/```[\s\S]*?```/g);
4955
+ if (!codeBlocks) return 0;
4956
+ const fileExtPattern = /\.(ts|js|json|sql|yaml|yml|md)\b/;
4957
+ let count = 0;
4958
+ for (const block of codeBlocks) {
4959
+ const lines = block.split("\n");
4960
+ for (const line of lines) {
4961
+ if (line.trimStart().startsWith("```")) continue;
4962
+ if (fileExtPattern.test(line)) count++;
4963
+ }
4964
+ }
4965
+ return count;
4966
+ }
4967
+
4723
4968
  //#endregion
4724
4969
  //#region src/modules/compiled-workflows/dev-story.ts
4725
4970
  const logger$12 = createLogger("compiled-workflows:dev-story");
4726
- /** Hard token ceiling for the assembled dev-story prompt */
4727
- const TOKEN_CEILING$3 = 24e3;
4728
4971
  /** Default timeout for dev-story dispatches in milliseconds (30 min) */
4729
4972
  const DEFAULT_TIMEOUT_MS$1 = 18e5;
4730
4973
  /** Default Vitest test patterns injected when no test-pattern decisions exist */
@@ -4751,6 +4994,12 @@ async function runDevStory(deps, params) {
4751
4994
  storyKey,
4752
4995
  storyFilePath
4753
4996
  }, "Starting compiled dev-story workflow");
4997
+ const { ceiling: TOKEN_CEILING, source: tokenCeilingSource } = getTokenCeiling("dev-story", deps.tokenCeilings);
4998
+ logger$12.info({
4999
+ workflow: "dev-story",
5000
+ ceiling: TOKEN_CEILING,
5001
+ source: tokenCeilingSource
5002
+ }, "Token ceiling resolved");
4754
5003
  const devStoryContextTemplate = {
4755
5004
  taskType: "dev-story",
4756
5005
  sections: [{
@@ -4824,6 +5073,9 @@ async function runDevStory(deps, params) {
4824
5073
  }, "Story file is empty");
4825
5074
  return makeFailureResult("story_file_empty");
4826
5075
  }
5076
+ const complexity = computeStoryComplexity(storyContent);
5077
+ const resolvedMaxTurns = resolveDevStoryMaxTurns(complexity.complexityScore);
5078
+ logComplexityResult(storyKey, complexity, resolvedMaxTurns);
4827
5079
  let testPatternsContent = "";
4828
5080
  try {
4829
5081
  const solutioningDecisions = getDecisionsByPhase(deps.db, "solutioning");
@@ -4920,11 +5172,11 @@ async function runDevStory(deps, params) {
4920
5172
  priority: "optional"
4921
5173
  }
4922
5174
  ];
4923
- const { prompt, tokenCount, truncated } = assemblePrompt(template, sections, TOKEN_CEILING$3);
5175
+ const { prompt, tokenCount, truncated } = assemblePrompt(template, sections, TOKEN_CEILING);
4924
5176
  logger$12.info({
4925
5177
  storyKey,
4926
5178
  tokenCount,
4927
- ceiling: TOKEN_CEILING$3,
5179
+ ceiling: TOKEN_CEILING,
4928
5180
  truncated
4929
5181
  }, "Assembled dev-story prompt");
4930
5182
  let dispatchResult;
@@ -4935,6 +5187,7 @@ async function runDevStory(deps, params) {
4935
5187
  taskType: "dev-story",
4936
5188
  timeout: DEFAULT_TIMEOUT_MS$1,
4937
5189
  outputSchema: DevStoryResultSchema,
5190
+ maxTurns: resolvedMaxTurns,
4938
5191
  ...deps.projectRoot !== void 0 ? { workingDirectory: deps.projectRoot } : {}
4939
5192
  });
4940
5193
  dispatchResult = await handle.result;
@@ -5153,12 +5406,6 @@ function extractFilesInScope(storyContent) {
5153
5406
  //#region src/modules/compiled-workflows/code-review.ts
5154
5407
  const logger$11 = createLogger("compiled-workflows:code-review");
5155
5408
  /**
5156
- * Hard token ceiling for the assembled code-review prompt (50,000 tokens).
5157
- * Quality reviews require seeing actual code diffs, not just file names.
5158
- * // TODO: consider externalizing to pack config when multiple packs exist
5159
- */
5160
- const TOKEN_CEILING$2 = 1e5;
5161
- /**
5162
5409
  * Default fallback result when dispatch fails or times out.
5163
5410
  * Uses NEEDS_MINOR_FIXES (not NEEDS_MAJOR_REWORK) so a parse/schema failure
5164
5411
  * doesn't trigger a full rework cycle. The orchestrator's phantom-review
@@ -5201,6 +5448,12 @@ async function runCodeReview(deps, params) {
5201
5448
  cwd,
5202
5449
  pipelineRunId
5203
5450
  }, "Starting code-review workflow");
5451
+ const { ceiling: TOKEN_CEILING, source: tokenCeilingSource } = getTokenCeiling("code-review", deps.tokenCeilings);
5452
+ logger$11.info({
5453
+ workflow: "code-review",
5454
+ ceiling: TOKEN_CEILING,
5455
+ source: tokenCeilingSource
5456
+ }, "Token ceiling resolved");
5204
5457
  let template;
5205
5458
  try {
5206
5459
  template = await deps.pack.getPrompt("code-review");
@@ -5235,7 +5488,7 @@ async function runCodeReview(deps, params) {
5235
5488
  if (filesModified && filesModified.length > 0) {
5236
5489
  const scopedDiff = await getGitDiffForFiles(filesModified, cwd);
5237
5490
  const scopedTotal = nonDiffTokens + countTokens(scopedDiff);
5238
- if (scopedTotal <= TOKEN_CEILING$2) {
5491
+ if (scopedTotal <= TOKEN_CEILING) {
5239
5492
  gitDiffContent = scopedDiff;
5240
5493
  logger$11.debug({
5241
5494
  fileCount: filesModified.length,
@@ -5244,7 +5497,7 @@ async function runCodeReview(deps, params) {
5244
5497
  } else {
5245
5498
  logger$11.warn({
5246
5499
  estimatedTotal: scopedTotal,
5247
- ceiling: TOKEN_CEILING$2,
5500
+ ceiling: TOKEN_CEILING,
5248
5501
  fileCount: filesModified.length
5249
5502
  }, "Scoped diff exceeds token ceiling — falling back to stat-only summary");
5250
5503
  gitDiffContent = await getGitDiffStatSummary(cwd);
@@ -5254,11 +5507,11 @@ async function runCodeReview(deps, params) {
5254
5507
  await stageIntentToAdd(changedFiles, cwd);
5255
5508
  const fullDiff = await getGitDiffSummary(cwd);
5256
5509
  const fullTotal = nonDiffTokens + countTokens(fullDiff);
5257
- if (fullTotal <= TOKEN_CEILING$2) gitDiffContent = fullDiff;
5510
+ if (fullTotal <= TOKEN_CEILING) gitDiffContent = fullDiff;
5258
5511
  else {
5259
5512
  logger$11.warn({
5260
5513
  estimatedTotal: fullTotal,
5261
- ceiling: TOKEN_CEILING$2
5514
+ ceiling: TOKEN_CEILING
5262
5515
  }, "Full git diff would exceed token ceiling — using stat-only summary");
5263
5516
  gitDiffContent = await getGitDiffStatSummary(cwd);
5264
5517
  }
@@ -5322,7 +5575,7 @@ async function runCodeReview(deps, params) {
5322
5575
  priority: "optional"
5323
5576
  }
5324
5577
  ];
5325
- const assembleResult = assemblePrompt(template, sections, TOKEN_CEILING$2);
5578
+ const assembleResult = assemblePrompt(template, sections, TOKEN_CEILING);
5326
5579
  if (assembleResult.truncated) logger$11.warn({
5327
5580
  storyKey,
5328
5581
  tokenCount: assembleResult.tokenCount
@@ -5450,8 +5703,6 @@ function getArchConstraints$1(deps) {
5450
5703
  //#endregion
5451
5704
  //#region src/modules/compiled-workflows/test-plan.ts
5452
5705
  const logger$10 = createLogger("compiled-workflows:test-plan");
5453
- /** Hard token ceiling for the assembled test-plan prompt */
5454
- const TOKEN_CEILING$1 = 8e3;
5455
5706
  /** Default timeout for test-plan dispatches in milliseconds (5 min — lightweight call) */
5456
5707
  const DEFAULT_TIMEOUT_MS = 3e5;
5457
5708
  /**
@@ -5467,6 +5718,12 @@ async function runTestPlan(deps, params) {
5467
5718
  storyKey,
5468
5719
  storyFilePath
5469
5720
  }, "Starting compiled test-plan workflow");
5721
+ const { ceiling: TOKEN_CEILING, source: tokenCeilingSource } = getTokenCeiling("test-plan", deps.tokenCeilings);
5722
+ logger$10.info({
5723
+ workflow: "test-plan",
5724
+ ceiling: TOKEN_CEILING,
5725
+ source: tokenCeilingSource
5726
+ }, "Token ceiling resolved");
5470
5727
  let template;
5471
5728
  try {
5472
5729
  template = await deps.pack.getPrompt("test-plan");
@@ -5502,11 +5759,11 @@ async function runTestPlan(deps, params) {
5502
5759
  name: "story_content",
5503
5760
  content: storyContent,
5504
5761
  priority: "required"
5505
- }], TOKEN_CEILING$1);
5762
+ }], TOKEN_CEILING);
5506
5763
  logger$10.info({
5507
5764
  storyKey,
5508
5765
  tokenCount,
5509
- ceiling: TOKEN_CEILING$1,
5766
+ ceiling: TOKEN_CEILING,
5510
5767
  truncated
5511
5768
  }, "Assembled test-plan prompt");
5512
5769
  let dispatchResult;
@@ -5622,10 +5879,6 @@ function makeTestPlanFailureResult(error) {
5622
5879
  //#endregion
5623
5880
  //#region src/modules/compiled-workflows/test-expansion.ts
5624
5881
  const logger$9 = createLogger("compiled-workflows:test-expansion");
5625
- /**
5626
- * Hard token ceiling for the assembled test-expansion prompt (20,000 tokens).
5627
- */
5628
- const TOKEN_CEILING = 2e4;
5629
5882
  function defaultFallbackResult(error, tokenUsage) {
5630
5883
  return {
5631
5884
  expansion_priority: "low",
@@ -5661,6 +5914,12 @@ async function runTestExpansion(deps, params) {
5661
5914
  cwd,
5662
5915
  pipelineRunId
5663
5916
  }, "Starting test-expansion workflow");
5917
+ const { ceiling: TOKEN_CEILING, source: tokenCeilingSource } = getTokenCeiling("test-expansion", deps.tokenCeilings);
5918
+ logger$9.info({
5919
+ workflow: "test-expansion",
5920
+ ceiling: TOKEN_CEILING,
5921
+ source: tokenCeilingSource
5922
+ }, "Token ceiling resolved");
5664
5923
  let template;
5665
5924
  try {
5666
5925
  template = await deps.pack.getPrompt("test-expansion");
@@ -6864,6 +7123,27 @@ function createPauseGate() {
6864
7123
  };
6865
7124
  }
6866
7125
  /**
7126
+ * Build the targeted_files content string from a code-review issue list.
7127
+ * Deduplicates file paths and includes line numbers where available.
7128
+ * Returns empty string when no issues have file references.
7129
+ */
7130
+ function buildTargetedFilesContent(issueList) {
7131
+ const seen = new Map();
7132
+ for (const issue of issueList) {
7133
+ const iss = issue;
7134
+ if (!iss.file) continue;
7135
+ if (!seen.has(iss.file)) seen.set(iss.file, new Set());
7136
+ if (iss.line !== void 0) seen.get(iss.file).add(iss.line);
7137
+ }
7138
+ if (seen.size === 0) return "";
7139
+ const lines = [];
7140
+ for (const [file, lineNums] of seen) if (lineNums.size > 0) {
7141
+ const sorted = [...lineNums].sort((a, b) => a - b);
7142
+ lines.push(`- ${file} (lines: ${sorted.join(", ")})`);
7143
+ } else lines.push(`- ${file}`);
7144
+ return lines.join("\n");
7145
+ }
7146
+ /**
6867
7147
  * Factory function that creates an ImplementationOrchestrator instance.
6868
7148
  *
6869
7149
  * @param deps - Injected dependencies (db, pack, contextCompiler, dispatcher,
@@ -6871,8 +7151,8 @@ function createPauseGate() {
6871
7151
  * @returns A fully-configured ImplementationOrchestrator ready to call run()
6872
7152
  */
6873
7153
  function createImplementationOrchestrator(deps) {
6874
- const { db, pack, contextCompiler, dispatcher, eventBus, config, projectRoot } = deps;
6875
- const logger$21 = createLogger("implementation-orchestrator");
7154
+ const { db, pack, contextCompiler, dispatcher, eventBus, config, projectRoot, tokenCeilings } = deps;
7155
+ const logger$22 = createLogger("implementation-orchestrator");
6876
7156
  let _state = "IDLE";
6877
7157
  let _startedAt;
6878
7158
  let _completedAt;
@@ -6915,7 +7195,7 @@ function createImplementationOrchestrator(deps) {
6915
7195
  const nowMs = Date.now();
6916
7196
  for (const [phase, startMs] of starts) {
6917
7197
  const endMs = ends?.get(phase);
6918
- if (endMs === void 0) logger$21.warn({
7198
+ if (endMs === void 0) logger$22.warn({
6919
7199
  storyKey,
6920
7200
  phase
6921
7201
  }, "Phase has no end time — story may have errored mid-phase. Duration capped to now() and may be inflated.");
@@ -6964,7 +7244,7 @@ function createImplementationOrchestrator(deps) {
6964
7244
  rationale: `Story ${storyKey} completed with result=${result} in ${wallClockSeconds}s. Tokens: ${tokenAgg.input}+${tokenAgg.output}. Review cycles: ${reviewCycles}.`
6965
7245
  });
6966
7246
  } catch (decisionErr) {
6967
- logger$21.warn({
7247
+ logger$22.warn({
6968
7248
  err: decisionErr,
6969
7249
  storyKey
6970
7250
  }, "Failed to write story-metrics decision (best-effort)");
@@ -6992,13 +7272,13 @@ function createImplementationOrchestrator(deps) {
6992
7272
  dispatches: _storyDispatches.get(storyKey) ?? 0
6993
7273
  });
6994
7274
  } catch (emitErr) {
6995
- logger$21.warn({
7275
+ logger$22.warn({
6996
7276
  err: emitErr,
6997
7277
  storyKey
6998
7278
  }, "Failed to emit story:metrics event (best-effort)");
6999
7279
  }
7000
7280
  } catch (err) {
7001
- logger$21.warn({
7281
+ logger$22.warn({
7002
7282
  err,
7003
7283
  storyKey
7004
7284
  }, "Failed to write story metrics (best-effort)");
@@ -7027,7 +7307,7 @@ function createImplementationOrchestrator(deps) {
7027
7307
  rationale: `Story ${storyKey} ${outcome} after ${reviewCycles} review cycle(s).`
7028
7308
  });
7029
7309
  } catch (err) {
7030
- logger$21.warn({
7310
+ logger$22.warn({
7031
7311
  err,
7032
7312
  storyKey
7033
7313
  }, "Failed to write story-outcome decision (best-effort)");
@@ -7053,7 +7333,7 @@ function createImplementationOrchestrator(deps) {
7053
7333
  rationale: `Escalation diagnosis for ${payload.storyKey}: ${diagnosis.recommendedAction} — ${diagnosis.rationale}`
7054
7334
  });
7055
7335
  } catch (err) {
7056
- logger$21.warn({
7336
+ logger$22.warn({
7057
7337
  err,
7058
7338
  storyKey: payload.storyKey
7059
7339
  }, "Failed to persist escalation diagnosis (best-effort)");
@@ -7103,7 +7383,7 @@ function createImplementationOrchestrator(deps) {
7103
7383
  token_usage_json: serialized
7104
7384
  });
7105
7385
  } catch (err) {
7106
- logger$21.warn("Failed to persist orchestrator state", { err });
7386
+ logger$22.warn("Failed to persist orchestrator state", { err });
7107
7387
  }
7108
7388
  }
7109
7389
  function recordProgress() {
@@ -7150,7 +7430,7 @@ function createImplementationOrchestrator(deps) {
7150
7430
  }
7151
7431
  if (childActive) {
7152
7432
  _lastProgressTs = Date.now();
7153
- logger$21.debug({
7433
+ logger$22.debug({
7154
7434
  storyKey: key,
7155
7435
  phase: s.phase,
7156
7436
  childPids
@@ -7159,7 +7439,7 @@ function createImplementationOrchestrator(deps) {
7159
7439
  }
7160
7440
  _stalledStories.add(key);
7161
7441
  _storiesWithStall.add(key);
7162
- logger$21.warn({
7442
+ logger$22.warn({
7163
7443
  storyKey: key,
7164
7444
  phase: s.phase,
7165
7445
  elapsedMs: elapsed,
@@ -7204,7 +7484,7 @@ function createImplementationOrchestrator(deps) {
7204
7484
  for (let attempt = 0; attempt < MEMORY_PRESSURE_BACKOFF_MS.length; attempt++) {
7205
7485
  const memState = dispatcher.getMemoryState();
7206
7486
  if (!memState.isPressured) return true;
7207
- logger$21.warn({
7487
+ logger$22.warn({
7208
7488
  storyKey,
7209
7489
  freeMB: memState.freeMB,
7210
7490
  thresholdMB: memState.thresholdMB,
@@ -7224,11 +7504,11 @@ function createImplementationOrchestrator(deps) {
7224
7504
  * exhausted retries the story is ESCALATED.
7225
7505
  */
7226
7506
  async function processStory(storyKey) {
7227
- logger$21.info("Processing story", { storyKey });
7507
+ logger$22.info("Processing story", { storyKey });
7228
7508
  {
7229
7509
  const memoryOk = await checkMemoryPressure(storyKey);
7230
7510
  if (!memoryOk) {
7231
- logger$21.warn({ storyKey }, "Memory pressure exhausted — escalating story without dispatch");
7511
+ logger$22.warn({ storyKey }, "Memory pressure exhausted — escalating story without dispatch");
7232
7512
  _stories.set(storyKey, {
7233
7513
  phase: "ESCALATED",
7234
7514
  reviewCycles: 0,
@@ -7262,14 +7542,14 @@ function createImplementationOrchestrator(deps) {
7262
7542
  if (match) {
7263
7543
  const candidatePath = join$1(artifactsDir, match);
7264
7544
  const validation = await isValidStoryFile(candidatePath);
7265
- if (!validation.valid) logger$21.warn({
7545
+ if (!validation.valid) logger$22.warn({
7266
7546
  storyKey,
7267
7547
  storyFilePath: candidatePath,
7268
7548
  reason: validation.reason
7269
7549
  }, `Existing story file for ${storyKey} is invalid (${validation.reason}) — re-creating`);
7270
7550
  else {
7271
7551
  storyFilePath = candidatePath;
7272
- logger$21.info({
7552
+ logger$22.info({
7273
7553
  storyKey,
7274
7554
  storyFilePath
7275
7555
  }, "Found existing story file — skipping create-story");
@@ -7294,7 +7574,8 @@ function createImplementationOrchestrator(deps) {
7294
7574
  pack,
7295
7575
  contextCompiler,
7296
7576
  dispatcher,
7297
- projectRoot
7577
+ projectRoot,
7578
+ tokenCeilings
7298
7579
  }, {
7299
7580
  epicId: storyKey.split("-")[0] ?? storyKey,
7300
7581
  storyKey,
@@ -7372,17 +7653,18 @@ function createImplementationOrchestrator(deps) {
7372
7653
  pack,
7373
7654
  contextCompiler,
7374
7655
  dispatcher,
7375
- projectRoot
7656
+ projectRoot,
7657
+ tokenCeilings
7376
7658
  }, {
7377
7659
  storyKey,
7378
7660
  storyFilePath: storyFilePath ?? "",
7379
7661
  pipelineRunId: config.pipelineRunId
7380
7662
  });
7381
7663
  testPlanPhaseResult = testPlanResult.result;
7382
- if (testPlanResult.result === "success") logger$21.info({ storyKey }, "Test plan generated successfully");
7383
- else logger$21.warn({ storyKey }, "Test planning returned failed result — proceeding to dev-story without test plan");
7664
+ if (testPlanResult.result === "success") logger$22.info({ storyKey }, "Test plan generated successfully");
7665
+ else logger$22.warn({ storyKey }, "Test planning returned failed result — proceeding to dev-story without test plan");
7384
7666
  } catch (err) {
7385
- logger$21.warn({
7667
+ logger$22.warn({
7386
7668
  storyKey,
7387
7669
  err
7388
7670
  }, "Test planning failed — proceeding to dev-story without test plan");
@@ -7406,7 +7688,7 @@ function createImplementationOrchestrator(deps) {
7406
7688
  try {
7407
7689
  storyContentForAnalysis = await readFile$1(storyFilePath ?? "", "utf-8");
7408
7690
  } catch (err) {
7409
- logger$21.error({
7691
+ logger$22.error({
7410
7692
  storyKey,
7411
7693
  storyFilePath,
7412
7694
  error: err instanceof Error ? err.message : String(err)
@@ -7414,7 +7696,7 @@ function createImplementationOrchestrator(deps) {
7414
7696
  }
7415
7697
  const analysis = analyzeStoryComplexity(storyContentForAnalysis);
7416
7698
  const batches = planTaskBatches(analysis);
7417
- logger$21.info({
7699
+ logger$22.info({
7418
7700
  storyKey,
7419
7701
  estimatedScope: analysis.estimatedScope,
7420
7702
  batchCount: batches.length,
@@ -7432,7 +7714,7 @@ function createImplementationOrchestrator(deps) {
7432
7714
  if (_state !== "RUNNING") break;
7433
7715
  const taskScope = batch.taskIds.map((id, i) => `T${id}: ${batch.taskTitles[i] ?? ""}`).join("\n");
7434
7716
  const priorFiles = allFilesModified.size > 0 ? Array.from(allFilesModified) : void 0;
7435
- logger$21.info({
7717
+ logger$22.info({
7436
7718
  storyKey,
7437
7719
  batchIndex: batch.batchIndex,
7438
7720
  taskCount: batch.taskIds.length
@@ -7446,7 +7728,8 @@ function createImplementationOrchestrator(deps) {
7446
7728
  pack,
7447
7729
  contextCompiler,
7448
7730
  dispatcher,
7449
- projectRoot
7731
+ projectRoot,
7732
+ tokenCeilings
7450
7733
  }, {
7451
7734
  storyKey,
7452
7735
  storyFilePath: storyFilePath ?? "",
@@ -7456,7 +7739,7 @@ function createImplementationOrchestrator(deps) {
7456
7739
  });
7457
7740
  } catch (batchErr) {
7458
7741
  const errMsg = batchErr instanceof Error ? batchErr.message : String(batchErr);
7459
- logger$21.warn({
7742
+ logger$22.warn({
7460
7743
  storyKey,
7461
7744
  batchIndex: batch.batchIndex,
7462
7745
  error: errMsg
@@ -7476,7 +7759,7 @@ function createImplementationOrchestrator(deps) {
7476
7759
  filesModified: batchFilesModified,
7477
7760
  result: batchResult.result === "success" ? "success" : "failed"
7478
7761
  };
7479
- logger$21.info(batchMetrics, "Batch dev-story metrics");
7762
+ logger$22.info(batchMetrics, "Batch dev-story metrics");
7480
7763
  for (const f of batchFilesModified) allFilesModified.add(f);
7481
7764
  if (batchFilesModified.length > 0) batchFileGroups.push({
7482
7765
  batchIndex: batch.batchIndex,
@@ -7498,13 +7781,13 @@ function createImplementationOrchestrator(deps) {
7498
7781
  })
7499
7782
  });
7500
7783
  } catch (tokenErr) {
7501
- logger$21.warn({
7784
+ logger$22.warn({
7502
7785
  storyKey,
7503
7786
  batchIndex: batch.batchIndex,
7504
7787
  err: tokenErr
7505
7788
  }, "Failed to record batch token usage");
7506
7789
  }
7507
- if (batchResult.result === "failed") logger$21.warn({
7790
+ if (batchResult.result === "failed") logger$22.warn({
7508
7791
  storyKey,
7509
7792
  batchIndex: batch.batchIndex,
7510
7793
  error: batchResult.error
@@ -7525,7 +7808,8 @@ function createImplementationOrchestrator(deps) {
7525
7808
  pack,
7526
7809
  contextCompiler,
7527
7810
  dispatcher,
7528
- projectRoot
7811
+ projectRoot,
7812
+ tokenCeilings
7529
7813
  }, {
7530
7814
  storyKey,
7531
7815
  storyFilePath: storyFilePath ?? "",
@@ -7539,7 +7823,7 @@ function createImplementationOrchestrator(deps) {
7539
7823
  });
7540
7824
  persistState();
7541
7825
  if (devResult.result === "success") devStoryWasSuccess = true;
7542
- else logger$21.warn("Dev-story reported failure, proceeding to code review", {
7826
+ else logger$22.warn("Dev-story reported failure, proceeding to code review", {
7543
7827
  storyKey,
7544
7828
  error: devResult.error,
7545
7829
  filesModified: devFilesModified.length
@@ -7567,7 +7851,7 @@ function createImplementationOrchestrator(deps) {
7567
7851
  if (devStoryWasSuccess) {
7568
7852
  gitDiffFiles = checkGitDiffFiles(projectRoot ?? process.cwd());
7569
7853
  if (gitDiffFiles.length === 0) {
7570
- logger$21.warn({ storyKey }, "Zero-diff detected after COMPLETE dev-story — no file changes in git working tree");
7854
+ logger$22.warn({ storyKey }, "Zero-diff detected after COMPLETE dev-story — no file changes in git working tree");
7571
7855
  eventBus.emit("orchestrator:zero-diff-escalation", {
7572
7856
  storyKey,
7573
7857
  reason: "zero-diff-on-complete"
@@ -7598,7 +7882,7 @@ function createImplementationOrchestrator(deps) {
7598
7882
  });
7599
7883
  if (buildVerifyResult.status === "passed") {
7600
7884
  eventBus.emit("story:build-verification-passed", { storyKey });
7601
- logger$21.info({ storyKey }, "Build verification passed");
7885
+ logger$22.info({ storyKey }, "Build verification passed");
7602
7886
  } else if (buildVerifyResult.status === "failed" || buildVerifyResult.status === "timeout") {
7603
7887
  const truncatedOutput = (buildVerifyResult.output ?? "").slice(0, 2e3);
7604
7888
  const reason = buildVerifyResult.reason ?? "build-verification-failed";
@@ -7607,7 +7891,7 @@ function createImplementationOrchestrator(deps) {
7607
7891
  exitCode: buildVerifyResult.exitCode ?? 1,
7608
7892
  output: truncatedOutput
7609
7893
  });
7610
- logger$21.warn({
7894
+ logger$22.warn({
7611
7895
  storyKey,
7612
7896
  reason,
7613
7897
  exitCode: buildVerifyResult.exitCode
@@ -7637,7 +7921,7 @@ function createImplementationOrchestrator(deps) {
7637
7921
  storyKey
7638
7922
  });
7639
7923
  if (icResult.potentiallyAffectedTests.length > 0) {
7640
- logger$21.warn({
7924
+ logger$22.warn({
7641
7925
  storyKey,
7642
7926
  modifiedInterfaces: icResult.modifiedInterfaces,
7643
7927
  potentiallyAffectedTests: icResult.potentiallyAffectedTests
@@ -7682,7 +7966,7 @@ function createImplementationOrchestrator(deps) {
7682
7966
  "NEEDS_MAJOR_REWORK": 2
7683
7967
  };
7684
7968
  for (const group of batchFileGroups) {
7685
- logger$21.info({
7969
+ logger$22.info({
7686
7970
  storyKey,
7687
7971
  batchIndex: group.batchIndex,
7688
7972
  fileCount: group.files.length
@@ -7693,7 +7977,8 @@ function createImplementationOrchestrator(deps) {
7693
7977
  pack,
7694
7978
  contextCompiler,
7695
7979
  dispatcher,
7696
- projectRoot
7980
+ projectRoot,
7981
+ tokenCeilings
7697
7982
  }, {
7698
7983
  storyKey,
7699
7984
  storyFilePath: storyFilePath ?? "",
@@ -7719,7 +8004,7 @@ function createImplementationOrchestrator(deps) {
7719
8004
  rawOutput: lastRawOutput,
7720
8005
  tokenUsage: aggregateTokens
7721
8006
  };
7722
- logger$21.info({
8007
+ logger$22.info({
7723
8008
  storyKey,
7724
8009
  batchCount: batchFileGroups.length,
7725
8010
  verdict: worstVerdict,
@@ -7732,7 +8017,8 @@ function createImplementationOrchestrator(deps) {
7732
8017
  pack,
7733
8018
  contextCompiler,
7734
8019
  dispatcher,
7735
- projectRoot
8020
+ projectRoot,
8021
+ tokenCeilings
7736
8022
  }, {
7737
8023
  storyKey,
7738
8024
  storyFilePath: storyFilePath ?? "",
@@ -7745,7 +8031,7 @@ function createImplementationOrchestrator(deps) {
7745
8031
  const isPhantomReview = reviewResult.dispatchFailed === true || reviewResult.verdict !== "SHIP_IT" && (reviewResult.issue_list === void 0 || reviewResult.issue_list.length === 0) && reviewResult.error !== void 0;
7746
8032
  if (isPhantomReview && !timeoutRetried) {
7747
8033
  timeoutRetried = true;
7748
- logger$21.warn({
8034
+ logger$22.warn({
7749
8035
  storyKey,
7750
8036
  reviewCycles,
7751
8037
  error: reviewResult.error
@@ -7755,7 +8041,7 @@ function createImplementationOrchestrator(deps) {
7755
8041
  verdict = reviewResult.verdict;
7756
8042
  issueList = reviewResult.issue_list ?? [];
7757
8043
  if (verdict === "NEEDS_MAJOR_REWORK" && reviewCycles > 0 && previousIssueList.length > 0 && issueList.length < previousIssueList.length) {
7758
- logger$21.info({
8044
+ logger$22.info({
7759
8045
  storyKey,
7760
8046
  originalVerdict: verdict,
7761
8047
  issuesBefore: previousIssueList.length,
@@ -7791,7 +8077,7 @@ function createImplementationOrchestrator(deps) {
7791
8077
  if (_decomposition !== void 0) parts.push(`decomposed: ${_decomposition.batchCount} batches`);
7792
8078
  parts.push(`${fileCount} files`);
7793
8079
  parts.push(`${totalTokensK} tokens`);
7794
- logger$21.info({
8080
+ logger$22.info({
7795
8081
  storyKey,
7796
8082
  verdict,
7797
8083
  agentVerdict: reviewResult.agentVerdict
@@ -7834,7 +8120,8 @@ function createImplementationOrchestrator(deps) {
7834
8120
  pack,
7835
8121
  contextCompiler,
7836
8122
  dispatcher,
7837
- projectRoot
8123
+ projectRoot,
8124
+ tokenCeilings
7838
8125
  }, {
7839
8126
  storyKey,
7840
8127
  storyFilePath: storyFilePath ?? "",
@@ -7842,7 +8129,7 @@ function createImplementationOrchestrator(deps) {
7842
8129
  filesModified: devFilesModified,
7843
8130
  workingDirectory: projectRoot
7844
8131
  });
7845
- logger$21.debug({
8132
+ logger$22.debug({
7846
8133
  storyKey,
7847
8134
  expansion_priority: expansionResult.expansion_priority,
7848
8135
  coverage_gaps: expansionResult.coverage_gaps.length
@@ -7855,7 +8142,7 @@ function createImplementationOrchestrator(deps) {
7855
8142
  value: JSON.stringify(expansionResult)
7856
8143
  });
7857
8144
  } catch (expansionErr) {
7858
- logger$21.warn({
8145
+ logger$22.warn({
7859
8146
  storyKey,
7860
8147
  error: expansionErr instanceof Error ? expansionErr.message : String(expansionErr)
7861
8148
  }, "Test expansion failed — story verdict unchanged");
@@ -7882,7 +8169,7 @@ function createImplementationOrchestrator(deps) {
7882
8169
  persistState();
7883
8170
  return;
7884
8171
  }
7885
- logger$21.info({
8172
+ logger$22.info({
7886
8173
  storyKey,
7887
8174
  reviewCycles: finalReviewCycles,
7888
8175
  issueCount: issueList.length
@@ -7892,9 +8179,13 @@ function createImplementationOrchestrator(deps) {
7892
8179
  updateStory(storyKey, { phase: "NEEDS_FIXES" });
7893
8180
  try {
7894
8181
  let fixPrompt;
8182
+ let autoApproveMaxTurns;
7895
8183
  try {
7896
8184
  const fixTemplate = await pack.getPrompt("fix-story");
7897
8185
  const storyContent = await readFile$1(storyFilePath ?? "", "utf-8");
8186
+ const complexity = computeStoryComplexity(storyContent);
8187
+ autoApproveMaxTurns = resolveFixStoryMaxTurns(complexity.complexityScore);
8188
+ logComplexityResult(storyKey, complexity, autoApproveMaxTurns);
7898
8189
  let reviewFeedback;
7899
8190
  if (issueList.length === 0) reviewFeedback = `Verdict: ${verdict}\nIssues: Minor issues flagged but no specifics provided. Review the story ACs and fix any remaining gaps.`;
7900
8191
  else reviewFeedback = [
@@ -7911,6 +8202,7 @@ function createImplementationOrchestrator(deps) {
7911
8202
  const constraints = decisions.filter((d) => d.category === "architecture");
7912
8203
  archConstraints = constraints.map((d) => `${d.key}: ${d.value}`).join("\n");
7913
8204
  } catch {}
8205
+ const targetedFilesContent = buildTargetedFilesContent(issueList);
7914
8206
  const sections = [
7915
8207
  {
7916
8208
  name: "story_content",
@@ -7926,19 +8218,25 @@ function createImplementationOrchestrator(deps) {
7926
8218
  name: "arch_constraints",
7927
8219
  content: archConstraints,
7928
8220
  priority: "optional"
7929
- }
8221
+ },
8222
+ ...targetedFilesContent ? [{
8223
+ name: "targeted_files",
8224
+ content: targetedFilesContent,
8225
+ priority: "important"
8226
+ }] : []
7930
8227
  ];
7931
8228
  const assembled = assemblePrompt(fixTemplate, sections, 24e3);
7932
8229
  fixPrompt = assembled.prompt;
7933
8230
  } catch {
7934
8231
  fixPrompt = `Fix story ${storyKey}: verdict=${verdict}, minor fixes needed`;
7935
- logger$21.warn("Failed to assemble auto-approve fix prompt, using fallback", { storyKey });
8232
+ logger$22.warn("Failed to assemble auto-approve fix prompt, using fallback", { storyKey });
7936
8233
  }
7937
8234
  const handle = dispatcher.dispatch({
7938
8235
  prompt: fixPrompt,
7939
8236
  agent: "claude-code",
7940
8237
  taskType: "minor-fixes",
7941
- workingDirectory: projectRoot
8238
+ workingDirectory: projectRoot,
8239
+ ...autoApproveMaxTurns !== void 0 ? { maxTurns: autoApproveMaxTurns } : {}
7942
8240
  });
7943
8241
  const fixResult = await handle.result;
7944
8242
  eventBus.emit("orchestrator:story-phase-complete", {
@@ -7949,9 +8247,9 @@ function createImplementationOrchestrator(deps) {
7949
8247
  output: fixResult.tokenEstimate.output
7950
8248
  } : void 0 }
7951
8249
  });
7952
- if (fixResult.status === "timeout") logger$21.warn("Auto-approve fix timed out — approving anyway (issues were minor)", { storyKey });
8250
+ if (fixResult.status === "timeout") logger$22.warn("Auto-approve fix timed out — approving anyway (issues were minor)", { storyKey });
7953
8251
  } catch (err) {
7954
- logger$21.warn("Auto-approve fix dispatch failed — approving anyway (issues were minor)", {
8252
+ logger$22.warn("Auto-approve fix dispatch failed — approving anyway (issues were minor)", {
7955
8253
  storyKey,
7956
8254
  err
7957
8255
  });
@@ -7981,9 +8279,15 @@ function createImplementationOrchestrator(deps) {
7981
8279
  let fixPrompt;
7982
8280
  const isMajorRework = taskType === "major-rework";
7983
8281
  const templateName = isMajorRework ? "rework-story" : "fix-story";
8282
+ let fixMaxTurns;
7984
8283
  try {
7985
8284
  const fixTemplate = await pack.getPrompt(templateName);
7986
8285
  const storyContent = await readFile$1(storyFilePath ?? "", "utf-8");
8286
+ {
8287
+ const complexity = computeStoryComplexity(storyContent);
8288
+ fixMaxTurns = resolveFixStoryMaxTurns(complexity.complexityScore);
8289
+ logComplexityResult(storyKey, complexity, fixMaxTurns);
8290
+ }
7987
8291
  let reviewFeedback;
7988
8292
  if (issueList.length === 0) reviewFeedback = isMajorRework ? [
7989
8293
  `Verdict: ${verdict}`,
@@ -8033,28 +8337,36 @@ function createImplementationOrchestrator(deps) {
8033
8337
  content: "",
8034
8338
  priority: "optional"
8035
8339
  }
8036
- ] : [
8037
- {
8038
- name: "story_content",
8039
- content: storyContent,
8040
- priority: "required"
8041
- },
8042
- {
8043
- name: "review_feedback",
8044
- content: reviewFeedback,
8045
- priority: "required"
8046
- },
8047
- {
8048
- name: "arch_constraints",
8049
- content: archConstraints,
8050
- priority: "optional"
8051
- }
8052
- ];
8340
+ ] : (() => {
8341
+ const targetedFilesContent = buildTargetedFilesContent(issueList);
8342
+ return [
8343
+ {
8344
+ name: "story_content",
8345
+ content: storyContent,
8346
+ priority: "required"
8347
+ },
8348
+ {
8349
+ name: "review_feedback",
8350
+ content: reviewFeedback,
8351
+ priority: "required"
8352
+ },
8353
+ {
8354
+ name: "arch_constraints",
8355
+ content: archConstraints,
8356
+ priority: "optional"
8357
+ },
8358
+ ...targetedFilesContent ? [{
8359
+ name: "targeted_files",
8360
+ content: targetedFilesContent,
8361
+ priority: "important"
8362
+ }] : []
8363
+ ];
8364
+ })();
8053
8365
  const assembled = assemblePrompt(fixTemplate, sections, 24e3);
8054
8366
  fixPrompt = assembled.prompt;
8055
8367
  } catch {
8056
8368
  fixPrompt = `Fix story ${storyKey}: verdict=${verdict}, taskType=${taskType}`;
8057
- logger$21.warn("Failed to assemble fix prompt, using fallback", {
8369
+ logger$22.warn("Failed to assemble fix prompt, using fallback", {
8058
8370
  storyKey,
8059
8371
  taskType
8060
8372
  });
@@ -8066,12 +8378,14 @@ function createImplementationOrchestrator(deps) {
8066
8378
  taskType,
8067
8379
  ...fixModel !== void 0 ? { model: fixModel } : {},
8068
8380
  outputSchema: DevStoryResultSchema,
8381
+ ...fixMaxTurns !== void 0 ? { maxTurns: fixMaxTurns } : {},
8069
8382
  ...projectRoot !== void 0 ? { workingDirectory: projectRoot } : {}
8070
8383
  }) : dispatcher.dispatch({
8071
8384
  prompt: fixPrompt,
8072
8385
  agent: "claude-code",
8073
8386
  taskType,
8074
8387
  ...fixModel !== void 0 ? { model: fixModel } : {},
8388
+ ...fixMaxTurns !== void 0 ? { maxTurns: fixMaxTurns } : {},
8075
8389
  ...projectRoot !== void 0 ? { workingDirectory: projectRoot } : {}
8076
8390
  });
8077
8391
  const fixResult = await handle.result;
@@ -8084,7 +8398,7 @@ function createImplementationOrchestrator(deps) {
8084
8398
  } : void 0 }
8085
8399
  });
8086
8400
  if (fixResult.status === "timeout") {
8087
- logger$21.warn("Fix dispatch timed out — escalating story", {
8401
+ logger$22.warn("Fix dispatch timed out — escalating story", {
8088
8402
  storyKey,
8089
8403
  taskType
8090
8404
  });
@@ -8106,7 +8420,7 @@ function createImplementationOrchestrator(deps) {
8106
8420
  }
8107
8421
  if (fixResult.status === "failed") {
8108
8422
  if (isMajorRework) {
8109
- logger$21.warn("Major rework dispatch failed — escalating story", {
8423
+ logger$22.warn("Major rework dispatch failed — escalating story", {
8110
8424
  storyKey,
8111
8425
  exitCode: fixResult.exitCode
8112
8426
  });
@@ -8126,14 +8440,14 @@ function createImplementationOrchestrator(deps) {
8126
8440
  persistState();
8127
8441
  return;
8128
8442
  }
8129
- logger$21.warn("Fix dispatch failed", {
8443
+ logger$22.warn("Fix dispatch failed", {
8130
8444
  storyKey,
8131
8445
  taskType,
8132
8446
  exitCode: fixResult.exitCode
8133
8447
  });
8134
8448
  }
8135
8449
  } catch (err) {
8136
- logger$21.warn("Fix dispatch failed, continuing to next review", {
8450
+ logger$22.warn("Fix dispatch failed, continuing to next review", {
8137
8451
  storyKey,
8138
8452
  taskType,
8139
8453
  err
@@ -8196,11 +8510,11 @@ function createImplementationOrchestrator(deps) {
8196
8510
  }
8197
8511
  async function run(storyKeys) {
8198
8512
  if (_state === "RUNNING" || _state === "PAUSED") {
8199
- logger$21.warn("run() called while orchestrator is already running or paused — ignoring", { state: _state });
8513
+ logger$22.warn("run() called while orchestrator is already running or paused — ignoring", { state: _state });
8200
8514
  return getStatus();
8201
8515
  }
8202
8516
  if (_state === "COMPLETE") {
8203
- logger$21.warn("run() called on a COMPLETE orchestrator — ignoring", { state: _state });
8517
+ logger$22.warn("run() called on a COMPLETE orchestrator — ignoring", { state: _state });
8204
8518
  return getStatus();
8205
8519
  }
8206
8520
  _state = "RUNNING";
@@ -8218,13 +8532,13 @@ function createImplementationOrchestrator(deps) {
8218
8532
  if (config.enableHeartbeat) startHeartbeat();
8219
8533
  if (projectRoot !== void 0) {
8220
8534
  const seedResult = seedMethodologyContext(db, projectRoot);
8221
- if (seedResult.decisionsCreated > 0) logger$21.info({
8535
+ if (seedResult.decisionsCreated > 0) logger$22.info({
8222
8536
  decisionsCreated: seedResult.decisionsCreated,
8223
8537
  skippedCategories: seedResult.skippedCategories
8224
8538
  }, "Methodology context seeded from planning artifacts");
8225
8539
  }
8226
8540
  const groups = detectConflictGroups(storyKeys, { moduleMap: pack.manifest.conflictGroups });
8227
- logger$21.info("Orchestrator starting", {
8541
+ logger$22.info("Orchestrator starting", {
8228
8542
  storyCount: storyKeys.length,
8229
8543
  groupCount: groups.length,
8230
8544
  maxConcurrency: config.maxConcurrency
@@ -8236,7 +8550,7 @@ function createImplementationOrchestrator(deps) {
8236
8550
  _state = "FAILED";
8237
8551
  _completedAt = new Date().toISOString();
8238
8552
  persistState();
8239
- logger$21.error("Orchestrator failed with unhandled error", { err });
8553
+ logger$22.error("Orchestrator failed with unhandled error", { err });
8240
8554
  return getStatus();
8241
8555
  }
8242
8556
  stopHeartbeat();
@@ -8263,7 +8577,7 @@ function createImplementationOrchestrator(deps) {
8263
8577
  _pauseGate = createPauseGate();
8264
8578
  _state = "PAUSED";
8265
8579
  eventBus.emit("orchestrator:paused", {});
8266
- logger$21.info("Orchestrator paused");
8580
+ logger$22.info("Orchestrator paused");
8267
8581
  }
8268
8582
  function resume() {
8269
8583
  if (_state !== "PAUSED") return;
@@ -8274,7 +8588,7 @@ function createImplementationOrchestrator(deps) {
8274
8588
  }
8275
8589
  _state = "RUNNING";
8276
8590
  eventBus.emit("orchestrator:resumed", {});
8277
- logger$21.info("Orchestrator resumed");
8591
+ logger$22.info("Orchestrator resumed");
8278
8592
  }
8279
8593
  return {
8280
8594
  run,
@@ -13228,4 +13542,4 @@ function registerRunCommand(program, _version = "0.0.0", projectRoot = process.c
13228
13542
 
13229
13543
  //#endregion
13230
13544
  export { DatabaseWrapper, SUBSTRATE_OWNED_SETTINGS_KEYS, VALID_PHASES, buildPipelineStatusOutput, createContextCompiler, createDispatcher, createImplementationOrchestrator, createPackLoader, createPhaseOrchestrator, createStopAfterGate, findPackageRoot, formatOutput, formatPhaseCompletionSummary, formatPipelineStatusHuman, formatPipelineSummary, formatTokenTelemetry, getAllDescendantPids, getAutoHealthData, getSubstrateDefaultSettings, parseDbTimestampAsUtc, registerHealthCommand, registerRunCommand, resolveBmadMethodSrcPath, resolveBmadMethodVersion, resolveMainRepoRoot, runAnalysisPhase, runMigrations, runPlanningPhase, runRunAction, runSolutioningPhase, validateStopAfterFromConflict };
13231
- //# sourceMappingURL=run-DG5j6vJI.js.map
13545
+ //# sourceMappingURL=run-IeDyGCak.js.map