@nathapp/nax 0.70.1 → 0.70.2

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 (2) hide show
  1. package/dist/nax.js +554 -432
  2. package/package.json +6 -2
package/dist/nax.js CHANGED
@@ -17737,7 +17737,7 @@ var init_schema = __esm(() => {
17737
17737
  init_defaults();
17738
17738
  });
17739
17739
 
17740
- // src/logging/types.ts
17740
+ // src/log-format/types.ts
17741
17741
  var EMOJI;
17742
17742
  var init_types2 = __esm(() => {
17743
17743
  EMOJI = {
@@ -17759,7 +17759,7 @@ var init_types2 = __esm(() => {
17759
17759
  };
17760
17760
  });
17761
17761
 
17762
- // src/logging/formatter.ts
17762
+ // src/log-format/formatter.ts
17763
17763
  function formatTimestamp(isoTimestamp) {
17764
17764
  const date5 = new Date(isoTimestamp);
17765
17765
  return date5.toLocaleTimeString("en-US", {
@@ -18072,8 +18072,8 @@ var init_formatter = __esm(() => {
18072
18072
  ];
18073
18073
  });
18074
18074
 
18075
- // src/logging/index.ts
18076
- var init_logging = __esm(() => {
18075
+ // src/log-format/index.ts
18076
+ var init_log_format = __esm(() => {
18077
18077
  init_formatter();
18078
18078
  init_types2();
18079
18079
  });
@@ -18310,7 +18310,7 @@ function resetLogger() {
18310
18310
  }
18311
18311
  var LOG_LEVEL_PRIORITY, instance = null, noopLogger;
18312
18312
  var init_logger = __esm(() => {
18313
- init_logging();
18313
+ init_log_format();
18314
18314
  init_formatters();
18315
18315
  init_redact();
18316
18316
  LOG_LEVEL_PRIORITY = {
@@ -18963,6 +18963,19 @@ function rejectLegacyRectificationKeys(conf) {
18963
18963
  `);
18964
18964
  throw new NaxError(message, "CONFIG_LEGACY_RECTIFICATION_KEYS", { stage: "config", legacyKeys });
18965
18965
  }
18966
+ function rejectUnimplementedScopedProfile(conf) {
18967
+ const execution = conf.execution;
18968
+ if (execution?.permissionProfile !== "scoped")
18969
+ return;
18970
+ const message = [
18971
+ 'Invalid configuration \u2014 execution.permissionProfile: "scoped" is not yet implemented.',
18972
+ "The scoped (per-stage tool allowlist) profile is tracked by GitHub #374 and would",
18973
+ 'otherwise silently run as "safe", giving you weaker permissions than intended.',
18974
+ 'Use "unrestricted" or "safe" for now.'
18975
+ ].join(`
18976
+ `);
18977
+ throw new NaxError(message, "CONFIG_SCOPED_PROFILE_UNIMPLEMENTED", { stage: "config" });
18978
+ }
18966
18979
  function applyBatchModeCompat(conf) {
18967
18980
  const routing = conf.routing;
18968
18981
  const llm = routing?.llm;
@@ -19072,6 +19085,7 @@ async function loadConfig(startDir, cliOverrides) {
19072
19085
  }
19073
19086
  rejectLegacyAgentKeys(rawConfig);
19074
19087
  rejectLegacyRectificationKeys(rawConfig);
19088
+ rejectUnimplementedScopedProfile(rawConfig);
19075
19089
  const result = NaxConfigSchema.safeParse(rawConfig);
19076
19090
  if (!result.success) {
19077
19091
  const errors3 = result.error.issues.map((err) => {
@@ -19141,6 +19155,7 @@ async function loadConfigForWorkdir(rootConfigPath, packageDir, cliOverrides) {
19141
19155
  rawMerged.profileChain = packageChain;
19142
19156
  rejectLegacyAgentKeys(rawMerged);
19143
19157
  rejectLegacyRectificationKeys(rawMerged);
19158
+ rejectUnimplementedScopedProfile(rawMerged);
19144
19159
  const result = NaxConfigSchema.safeParse(rawMerged);
19145
19160
  if (!result.success) {
19146
19161
  const errors3 = result.error.issues.map((err) => {
@@ -45276,7 +45291,7 @@ function attachLoggingSubscriber(bus, runId) {
45276
45291
  offError();
45277
45292
  };
45278
45293
  }
45279
- var init_logging2 = __esm(() => {
45294
+ var init_logging = __esm(() => {
45280
45295
  init_logger2();
45281
45296
  });
45282
45297
 
@@ -45794,7 +45809,7 @@ var init_idle_watchdog = __esm(() => {
45794
45809
  // src/runtime/middleware/index.ts
45795
45810
  var init_middleware = __esm(() => {
45796
45811
  init_cancellation();
45797
- init_logging2();
45812
+ init_logging();
45798
45813
  init_agent_stream_logging();
45799
45814
  init_idle_watchdog();
45800
45815
  });
@@ -54979,159 +54994,66 @@ var init_non_blocking_fix = __esm(() => {
54979
54994
  };
54980
54995
  });
54981
54996
 
54982
- // src/execution/story-orchestrator-logging.ts
54983
- function formatPhaseResultMessage(opName, success2, stage, status) {
54984
- if (opName === "greenfield-gate") {
54985
- return success2 ? "Greenfield-gate: pre-existing tests detected (not greenfield) \u2014 proceeding with normal TDD" : "Greenfield-gate: no pre-existing tests \u2014 greenfield run, pausing TDD test-writer";
54986
- }
54987
- if (status === "skipped") {
54988
- return `Phase skipped: ${opName}`;
54989
- }
54990
- if (stage === "rectification") {
54991
- return `Rectification strategy completed: ${opName}`;
54992
- }
54993
- return success2 ? `Phase passed: ${opName}` : `Phase failed: ${opName}`;
54994
- }
54995
- function buildPhaseOutcomeLogData(storyId, opName, output, durationMs) {
54996
- if (output === null || output === undefined || typeof output !== "object")
54997
- return null;
54998
- const r = output;
54999
- const success2 = r.success === true || r.passed === true;
55000
- const findingsCount = Array.isArray(r.normalizedFindings) ? r.normalizedFindings.length : Array.isArray(r.findings) ? r.findings.length : undefined;
55001
- const status = typeof r.status === "string" ? r.status : undefined;
55002
- const data = { storyId, phase: opName, durationMs };
55003
- if (findingsCount !== undefined)
55004
- data.findingsCount = findingsCount;
55005
- if (status !== undefined)
55006
- data.status = status;
55007
- if (typeof r.failureCategory === "string")
55008
- data.failureCategory = r.failureCategory;
55009
- if (typeof r.reviewReason === "string")
55010
- data.reviewReason = r.reviewReason;
55011
- return { success: success2, data };
55012
- }
55013
- function logDeterministicPhaseOutcome(storyId, opName, output, durationMs, isTddPhase, stage, progressData = {}) {
55014
- if (isTddPhase)
55015
- return;
55016
- if (opName === "semantic-review" || opName === "adversarial-review")
55017
- return;
55018
- const built = buildPhaseOutcomeLogData(storyId, opName, output, durationMs);
55019
- if (!built)
55020
- return;
55021
- const { success: success2 } = built;
55022
- const data = { ...built.data, ...progressData };
55023
- const status = typeof built.data.status === "string" ? built.data.status : undefined;
55024
- const logger = getSafeLogger();
55025
- const message = formatPhaseResultMessage(opName, success2, stage, status);
55026
- if (stage === "rectification") {
55027
- logger?.info("story-orchestrator", message, data);
55028
- return;
55029
- }
55030
- if (success2) {
55031
- logger?.info("story-orchestrator", message, data);
55032
- } else {
55033
- logger?.warn("story-orchestrator", message, data);
55034
- }
55035
- }
55036
- var init_story_orchestrator_logging = __esm(() => {
55037
- init_logger2();
54997
+ // src/execution/story-orchestrator/types.ts
54998
+ var EXHAUSTED_EXIT_REASONS, TDD_OP_NAMES, CANONICAL_ORDER, PHASE_KIND_TO_STATE_KEY, STRATEGY_TO_REVALIDATION_PHASES, STRICT_VERDICT_PHASE_NAMES;
54999
+ var init_types9 = __esm(() => {
55000
+ EXHAUSTED_EXIT_REASONS = new Set([
55001
+ "max-attempts-total",
55002
+ "max-attempts-per-strategy",
55003
+ "bail-when",
55004
+ "no-strategy",
55005
+ "agent-gave-up",
55006
+ "validate-short-circuit"
55007
+ ]);
55008
+ TDD_OP_NAMES = new Set(["test-writer", "implementer", "verifier"]);
55009
+ CANONICAL_ORDER = [
55010
+ "test-writer",
55011
+ "greenfield-gate",
55012
+ "implementer",
55013
+ "full-suite-gate",
55014
+ "verifier",
55015
+ "verify-scoped",
55016
+ "lint-check",
55017
+ "typecheck-check",
55018
+ "semantic-review",
55019
+ "adversarial-review"
55020
+ ];
55021
+ PHASE_KIND_TO_STATE_KEY = {
55022
+ "test-writer": "testWriter",
55023
+ "greenfield-gate": "greenfieldGate",
55024
+ implementer: "implementer",
55025
+ "full-suite-gate": "fullSuiteGate",
55026
+ verifier: "verifier",
55027
+ "verify-scoped": "verifyScoped",
55028
+ "lint-check": "lintCheck",
55029
+ "typecheck-check": "typecheckCheck",
55030
+ "semantic-review": "semanticReview",
55031
+ "adversarial-review": "adversarialReview"
55032
+ };
55033
+ STRATEGY_TO_REVALIDATION_PHASES = {
55034
+ "mechanical-lintfix": ["lint-check"],
55035
+ "mechanical-formatfix": ["lint-check"],
55036
+ "autofix-implementer": ["lint-check", "typecheck-check", "full-suite-gate", "semantic-review", "adversarial-review"],
55037
+ "autofix-test-writer": ["lint-check", "typecheck-check", "full-suite-gate", "adversarial-review"],
55038
+ "full-suite-rectify": [
55039
+ "lint-check",
55040
+ "typecheck-check",
55041
+ "full-suite-gate",
55042
+ "verifier",
55043
+ "verify-scoped",
55044
+ "semantic-review"
55045
+ ]
55046
+ };
55047
+ STRICT_VERDICT_PHASE_NAMES = new Set([
55048
+ "full-suite-gate",
55049
+ "verify-scoped",
55050
+ "lint-check",
55051
+ "typecheck-check",
55052
+ "verifier"
55053
+ ]);
55038
55054
  });
55039
55055
 
55040
- // src/execution/story-orchestrator.ts
55041
- async function refreshReviewInputForDispatch(opName, input) {
55042
- if (opName !== "semantic-review" && opName !== "adversarial-review")
55043
- return input;
55044
- const i = input;
55045
- const { _refresh } = i;
55046
- if (!_refresh || !i.workdir)
55047
- return input;
55048
- try {
55049
- if (opName === "semantic-review") {
55050
- const { _refresh: _, ...semInput } = input;
55051
- const fresh2 = await _storyOrchestratorDeps.prepareSemanticReviewInput({
55052
- workdir: semInput.workdir,
55053
- projectDir: _refresh.projectDir,
55054
- storyId: _refresh.storyId,
55055
- storyGitRef: _refresh.storyGitRef,
55056
- config: _refresh.config,
55057
- naxIgnoreIndex: _refresh.naxIgnoreIndex,
55058
- resolvedTestPatterns: _refresh.resolvedTestPatterns,
55059
- semanticConfig: semInput.semanticConfig
55060
- });
55061
- return {
55062
- ...semInput,
55063
- stat: fresh2.stat,
55064
- diff: fresh2.diff,
55065
- excludePatterns: fresh2.excludePatterns,
55066
- storyGitRef: fresh2.effectiveRef ?? semInput.storyGitRef
55067
- };
55068
- }
55069
- const { _refresh: __, ...advInput } = input;
55070
- const fresh = await _storyOrchestratorDeps.prepareAdversarialReviewInput({
55071
- workdir: advInput.workdir,
55072
- projectDir: _refresh.projectDir,
55073
- storyId: _refresh.storyId,
55074
- storyGitRef: _refresh.storyGitRef,
55075
- config: _refresh.config,
55076
- naxIgnoreIndex: _refresh.naxIgnoreIndex,
55077
- resolvedTestPatterns: _refresh.resolvedTestPatterns,
55078
- adversarialConfig: advInput.adversarialConfig
55079
- });
55080
- return {
55081
- ...advInput,
55082
- stat: fresh.stat,
55083
- diff: fresh.diff,
55084
- testInventory: fresh.testInventory,
55085
- excludePatterns: fresh.excludePatterns,
55086
- testGlobs: fresh.testGlobs,
55087
- refExcludePatterns: fresh.refExcludePatterns,
55088
- storyGitRef: fresh.effectiveRef ?? advInput.storyGitRef
55089
- };
55090
- } catch (err) {
55091
- getSafeLogger()?.warn("story-orchestrator", "review input refresh failed \u2014 dispatching with stale input", {
55092
- storyId: _refresh.storyId,
55093
- phase: opName,
55094
- error: errorMessage(err)
55095
- });
55096
- const { _refresh: _stripped, ...fallback } = input;
55097
- return fallback;
55098
- }
55099
- }
55100
- function isSlot(value) {
55101
- return value !== null && typeof value === "object" && "op" in value && "input" in value && typeof value.op?.kind === "string";
55102
- }
55103
- function setPhase(state, kind, slot) {
55104
- const key = PHASE_KIND_TO_STATE_KEY[kind];
55105
- if (state[key] !== undefined) {
55106
- throw new NaxError(`StoryOrchestratorBuilder: addX was called twice for phase "${kind}"`, "ORCHESTRATOR_PHASE_DUPLICATE", { stage: "execution", kind });
55107
- }
55108
- state[key] = { kind, slot };
55109
- }
55110
- function collectOrderedPhases(state) {
55111
- return CANONICAL_ORDER.flatMap((kind) => {
55112
- if (kind === "test-writer" && state.testWriter)
55113
- return [state.testWriter];
55114
- if (kind === "greenfield-gate" && state.greenfieldGate)
55115
- return [state.greenfieldGate];
55116
- if (kind === "implementer" && state.implementer)
55117
- return [state.implementer];
55118
- if (kind === "full-suite-gate" && state.fullSuiteGate)
55119
- return [state.fullSuiteGate];
55120
- if (kind === "verifier" && state.verifier)
55121
- return [state.verifier];
55122
- if (kind === "verify-scoped" && state.verifyScoped)
55123
- return [state.verifyScoped];
55124
- if (kind === "lint-check" && state.lintCheck)
55125
- return [state.lintCheck];
55126
- if (kind === "typecheck-check" && state.typecheckCheck)
55127
- return [state.typecheckCheck];
55128
- if (kind === "semantic-review" && state.semanticReview)
55129
- return [state.semanticReview];
55130
- if (kind === "adversarial-review" && state.adversarialReview)
55131
- return [state.adversarialReview];
55132
- return [];
55133
- });
55134
- }
55056
+ // src/execution/story-orchestrator/phase-eval.ts
55135
55057
  function phaseExplicitlyPassed(output) {
55136
55058
  if (output === null || output === undefined || typeof output !== "object")
55137
55059
  return false;
@@ -55189,34 +55111,6 @@ function gateFailureKeys(gateOutput) {
55189
55111
  }
55190
55112
  return keys;
55191
55113
  }
55192
- function shouldSkipPhaseForRectification(phase, state, phaseOutputs) {
55193
- if (phase.kind !== "full-suite-gate")
55194
- return false;
55195
- const verifierName = state.verifier?.slot.op.name;
55196
- if (!verifierName)
55197
- return false;
55198
- return phaseExplicitlyPassed(phaseOutputs[verifierName]);
55199
- }
55200
- function gatherRectificationFindings(phaseOutputs, phases, state) {
55201
- const findings = [];
55202
- for (const phase of phases) {
55203
- if (shouldSkipPhaseForRectification(phase, state, phaseOutputs))
55204
- continue;
55205
- findings.push(...extractPhaseFindings(phaseOutputs[phase.slot.op.name]));
55206
- }
55207
- return findings;
55208
- }
55209
- function collectRectificationPhases(state) {
55210
- return [
55211
- state.fullSuiteGate,
55212
- state.verifier,
55213
- state.verifyScoped,
55214
- state.lintCheck,
55215
- state.typecheckCheck,
55216
- state.semanticReview,
55217
- state.adversarialReview
55218
- ].filter((phase) => phase !== undefined);
55219
- }
55220
55114
  function phasesToRevalidate(strategiesRun, allPhases) {
55221
55115
  if (!strategiesRun || strategiesRun.length === 0)
55222
55116
  return allPhases;
@@ -55236,6 +55130,111 @@ function orderGateLast(phases) {
55236
55130
  const gates = phases.filter((p) => p.kind === "full-suite-gate");
55237
55131
  return [...rest, ...gates];
55238
55132
  }
55133
+ var init_phase_eval = __esm(() => {
55134
+ init_logger2();
55135
+ init_types9();
55136
+ });
55137
+
55138
+ // src/execution/story-orchestrator/phase-state.ts
55139
+ function isSlot(value) {
55140
+ return value !== null && typeof value === "object" && "op" in value && "input" in value && typeof value.op?.kind === "string";
55141
+ }
55142
+ function setPhase(state, kind, slot) {
55143
+ const key = PHASE_KIND_TO_STATE_KEY[kind];
55144
+ if (state[key] !== undefined) {
55145
+ throw new NaxError(`StoryOrchestratorBuilder: addX was called twice for phase "${kind}"`, "ORCHESTRATOR_PHASE_DUPLICATE", { stage: "execution", kind });
55146
+ }
55147
+ state[key] = { kind, slot };
55148
+ }
55149
+ function collectOrderedPhases(state) {
55150
+ return CANONICAL_ORDER.flatMap((kind) => {
55151
+ if (kind === "test-writer" && state.testWriter)
55152
+ return [state.testWriter];
55153
+ if (kind === "greenfield-gate" && state.greenfieldGate)
55154
+ return [state.greenfieldGate];
55155
+ if (kind === "implementer" && state.implementer)
55156
+ return [state.implementer];
55157
+ if (kind === "full-suite-gate" && state.fullSuiteGate)
55158
+ return [state.fullSuiteGate];
55159
+ if (kind === "verifier" && state.verifier)
55160
+ return [state.verifier];
55161
+ if (kind === "verify-scoped" && state.verifyScoped)
55162
+ return [state.verifyScoped];
55163
+ if (kind === "lint-check" && state.lintCheck)
55164
+ return [state.lintCheck];
55165
+ if (kind === "typecheck-check" && state.typecheckCheck)
55166
+ return [state.typecheckCheck];
55167
+ if (kind === "semantic-review" && state.semanticReview)
55168
+ return [state.semanticReview];
55169
+ if (kind === "adversarial-review" && state.adversarialReview)
55170
+ return [state.adversarialReview];
55171
+ return [];
55172
+ });
55173
+ }
55174
+ var init_phase_state = __esm(() => {
55175
+ init_errors();
55176
+ init_types9();
55177
+ });
55178
+
55179
+ // src/execution/story-orchestrator-logging.ts
55180
+ function formatPhaseResultMessage(opName, success2, stage, status) {
55181
+ if (opName === "greenfield-gate") {
55182
+ return success2 ? "Greenfield-gate: pre-existing tests detected (not greenfield) \u2014 proceeding with normal TDD" : "Greenfield-gate: no pre-existing tests \u2014 greenfield run, pausing TDD test-writer";
55183
+ }
55184
+ if (status === "skipped") {
55185
+ return `Phase skipped: ${opName}`;
55186
+ }
55187
+ if (stage === "rectification") {
55188
+ return `Rectification strategy completed: ${opName}`;
55189
+ }
55190
+ return success2 ? `Phase passed: ${opName}` : `Phase failed: ${opName}`;
55191
+ }
55192
+ function buildPhaseOutcomeLogData(storyId, opName, output, durationMs) {
55193
+ if (output === null || output === undefined || typeof output !== "object")
55194
+ return null;
55195
+ const r = output;
55196
+ const success2 = r.success === true || r.passed === true;
55197
+ const findingsCount = Array.isArray(r.normalizedFindings) ? r.normalizedFindings.length : Array.isArray(r.findings) ? r.findings.length : undefined;
55198
+ const status = typeof r.status === "string" ? r.status : undefined;
55199
+ const data = { storyId, phase: opName, durationMs };
55200
+ if (findingsCount !== undefined)
55201
+ data.findingsCount = findingsCount;
55202
+ if (status !== undefined)
55203
+ data.status = status;
55204
+ if (typeof r.failureCategory === "string")
55205
+ data.failureCategory = r.failureCategory;
55206
+ if (typeof r.reviewReason === "string")
55207
+ data.reviewReason = r.reviewReason;
55208
+ return { success: success2, data };
55209
+ }
55210
+ function logDeterministicPhaseOutcome(storyId, opName, output, durationMs, isTddPhase, stage, progressData = {}) {
55211
+ if (isTddPhase)
55212
+ return;
55213
+ if (opName === "semantic-review" || opName === "adversarial-review")
55214
+ return;
55215
+ const built = buildPhaseOutcomeLogData(storyId, opName, output, durationMs);
55216
+ if (!built)
55217
+ return;
55218
+ const { success: success2 } = built;
55219
+ const data = { ...built.data, ...progressData };
55220
+ const status = typeof built.data.status === "string" ? built.data.status : undefined;
55221
+ const logger = getSafeLogger();
55222
+ const message = formatPhaseResultMessage(opName, success2, stage, status);
55223
+ if (stage === "rectification") {
55224
+ logger?.info("story-orchestrator", message, data);
55225
+ return;
55226
+ }
55227
+ if (success2) {
55228
+ logger?.info("story-orchestrator", message, data);
55229
+ } else {
55230
+ logger?.warn("story-orchestrator", message, data);
55231
+ }
55232
+ }
55233
+ var init_story_orchestrator_logging = __esm(() => {
55234
+ init_logger2();
55235
+ });
55236
+
55237
+ // src/execution/story-orchestrator/review-decision.ts
55239
55238
  function toReviewDecisionPayload(opName, output) {
55240
55239
  if (output === null || output === undefined || typeof output !== "object")
55241
55240
  return null;
@@ -55351,6 +55350,70 @@ function logUnifiedReviewPhaseResult(storyId, opName, output) {
55351
55350
  truncated: findingsCount > findingsSummary.length
55352
55351
  });
55353
55352
  }
55353
+ var init_review_decision = __esm(() => {
55354
+ init_logger2();
55355
+ });
55356
+
55357
+ // src/execution/story-orchestrator/run-phase.ts
55358
+ async function refreshReviewInputForDispatch(opName, input) {
55359
+ if (opName !== "semantic-review" && opName !== "adversarial-review")
55360
+ return input;
55361
+ const i = input;
55362
+ const { _refresh } = i;
55363
+ if (!_refresh || !i.workdir)
55364
+ return input;
55365
+ try {
55366
+ if (opName === "semantic-review") {
55367
+ const { _refresh: _, ...semInput } = input;
55368
+ const fresh2 = await _storyOrchestratorDeps.prepareSemanticReviewInput({
55369
+ workdir: semInput.workdir,
55370
+ projectDir: _refresh.projectDir,
55371
+ storyId: _refresh.storyId,
55372
+ storyGitRef: _refresh.storyGitRef,
55373
+ config: _refresh.config,
55374
+ naxIgnoreIndex: _refresh.naxIgnoreIndex,
55375
+ resolvedTestPatterns: _refresh.resolvedTestPatterns,
55376
+ semanticConfig: semInput.semanticConfig
55377
+ });
55378
+ return {
55379
+ ...semInput,
55380
+ stat: fresh2.stat,
55381
+ diff: fresh2.diff,
55382
+ excludePatterns: fresh2.excludePatterns,
55383
+ storyGitRef: fresh2.effectiveRef ?? semInput.storyGitRef
55384
+ };
55385
+ }
55386
+ const { _refresh: __, ...advInput } = input;
55387
+ const fresh = await _storyOrchestratorDeps.prepareAdversarialReviewInput({
55388
+ workdir: advInput.workdir,
55389
+ projectDir: _refresh.projectDir,
55390
+ storyId: _refresh.storyId,
55391
+ storyGitRef: _refresh.storyGitRef,
55392
+ config: _refresh.config,
55393
+ naxIgnoreIndex: _refresh.naxIgnoreIndex,
55394
+ resolvedTestPatterns: _refresh.resolvedTestPatterns,
55395
+ adversarialConfig: advInput.adversarialConfig
55396
+ });
55397
+ return {
55398
+ ...advInput,
55399
+ stat: fresh.stat,
55400
+ diff: fresh.diff,
55401
+ testInventory: fresh.testInventory,
55402
+ excludePatterns: fresh.excludePatterns,
55403
+ testGlobs: fresh.testGlobs,
55404
+ refExcludePatterns: fresh.refExcludePatterns,
55405
+ storyGitRef: fresh.effectiveRef ?? advInput.storyGitRef
55406
+ };
55407
+ } catch (err) {
55408
+ getSafeLogger()?.warn("story-orchestrator", "review input refresh failed \u2014 dispatching with stale input", {
55409
+ storyId: _refresh.storyId,
55410
+ phase: opName,
55411
+ error: errorMessage(err)
55412
+ });
55413
+ const { _refresh: _stripped, ...fallback } = input;
55414
+ return fallback;
55415
+ }
55416
+ }
55354
55417
  async function runPhase(ctx, slot, phaseCosts, phaseOutputs, isThreeSession = false, progress) {
55355
55418
  const logger = getSafeLogger();
55356
55419
  const opName = slot.op.name;
@@ -55436,6 +55499,57 @@ function withIncreasingFailuresBail(strategies, enabled, consecutiveIncreases) {
55436
55499
  }
55437
55500
  }));
55438
55501
  }
55502
+ var _storyOrchestratorDeps;
55503
+ var init_run_phase = __esm(() => {
55504
+ init_findings();
55505
+ init_logger2();
55506
+ init_operations();
55507
+ init_pipeline();
55508
+ init_review();
55509
+ init_git();
55510
+ init_non_blocking_fix();
55511
+ init_story_orchestrator_logging();
55512
+ init_review_decision();
55513
+ init_types9();
55514
+ _storyOrchestratorDeps = {
55515
+ callOp,
55516
+ runFixCycle,
55517
+ captureGitRef,
55518
+ prepareSemanticReviewInput,
55519
+ prepareAdversarialReviewInput,
55520
+ runNonBlockingFix
55521
+ };
55522
+ });
55523
+
55524
+ // src/execution/story-orchestrator/rectification.ts
55525
+ function shouldSkipPhaseForRectification(phase, state, phaseOutputs) {
55526
+ if (phase.kind !== "full-suite-gate")
55527
+ return false;
55528
+ const verifierName = state.verifier?.slot.op.name;
55529
+ if (!verifierName)
55530
+ return false;
55531
+ return phaseExplicitlyPassed(phaseOutputs[verifierName]);
55532
+ }
55533
+ function gatherRectificationFindings(phaseOutputs, phases, state) {
55534
+ const findings = [];
55535
+ for (const phase of phases) {
55536
+ if (shouldSkipPhaseForRectification(phase, state, phaseOutputs))
55537
+ continue;
55538
+ findings.push(...extractPhaseFindings(phaseOutputs[phase.slot.op.name]));
55539
+ }
55540
+ return findings;
55541
+ }
55542
+ function collectRectificationPhases(state) {
55543
+ return [
55544
+ state.fullSuiteGate,
55545
+ state.verifier,
55546
+ state.verifyScoped,
55547
+ state.lintCheck,
55548
+ state.typecheckCheck,
55549
+ state.semanticReview,
55550
+ state.adversarialReview
55551
+ ].filter((phase) => phase !== undefined);
55552
+ }
55439
55553
  async function runRectification(ctx, state, phaseCosts, phaseOutputs, overrides) {
55440
55554
  const rectification = state.rectification;
55441
55555
  const baseValidationPhases = collectRectificationPhases(state);
@@ -55533,7 +55647,15 @@ async function runRectification(ctx, state, phaseCosts, phaseOutputs, overrides)
55533
55647
  }
55534
55648
  return {};
55535
55649
  }
55650
+ var init_rectification = __esm(() => {
55651
+ init_logger2();
55652
+ init_phase_eval();
55653
+ init_phase_eval();
55654
+ init_run_phase();
55655
+ init_types9();
55656
+ });
55536
55657
 
55658
+ // src/execution/story-orchestrator/execution-plan.ts
55537
55659
  class ExecutionPlan {
55538
55660
  ctx;
55539
55661
  state;
@@ -55687,18 +55809,29 @@ class ExecutionPlan {
55687
55809
  } else if (verifierPassedSsot && gateName !== undefined && !phasePassed(gateName, phaseOutputs[gateName], this.ctx.storyId)) {
55688
55810
  logger?.warn("story-orchestrator", "Full-suite gate failed but verifier judged story OK \u2014 treating gate failures as unrelated regressions", { storyId: this.ctx.storyId, packageDir: this.ctx.packageDir });
55689
55811
  }
55690
- const success2 = Object.entries(phaseOutputs).every(([name, output]) => {
55812
+ const requiredReviewPhaseNames = [
55813
+ this.state.semanticReview?.slot.op.name,
55814
+ this.state.adversarialReview?.slot.op.name
55815
+ ].filter((name) => name !== undefined);
55816
+ const missingRequiredReviewPhases = requiredReviewPhaseNames.filter((name) => !(name in phaseOutputs));
55817
+ if (missingRequiredReviewPhases.length > 0) {
55818
+ logger?.warn("story-orchestrator", "Configured review phase(s) never ran \u2014 story cannot pass without review judgment, failing for escalation", { storyId: this.ctx.storyId, packageDir: this.ctx.packageDir, missingRequiredReviewPhases });
55819
+ }
55820
+ const success2 = missingRequiredReviewPhases.length === 0 && Object.entries(phaseOutputs).every(([name, output]) => {
55691
55821
  if (verifierPassedSsot && name === gateName)
55692
55822
  return true;
55693
55823
  return phasePassed(name, output, this.ctx.storyId);
55694
55824
  });
55695
55825
  const totalCostUsd = Object.values(phaseCosts).reduce((sum, cost) => sum + cost, 0);
55696
55826
  const durationMs = Date.now() - startedAt;
55697
- const failedPhases = Object.entries(phaseOutputs).filter(([name, output]) => {
55698
- if (verifierPassedSsot && name === gateName)
55699
- return false;
55700
- return !phasePassed(name, output, this.ctx.storyId);
55701
- }).map(([name]) => name);
55827
+ const failedPhases = [
55828
+ ...Object.entries(phaseOutputs).filter(([name, output]) => {
55829
+ if (verifierPassedSsot && name === gateName)
55830
+ return false;
55831
+ return !phasePassed(name, output, this.ctx.storyId);
55832
+ }).map(([name]) => name),
55833
+ ...missingRequiredReviewPhases.map((name) => `${name} (never ran)`)
55834
+ ];
55702
55835
  const summary = {
55703
55836
  storyId: this.ctx.storyId,
55704
55837
  success: success2,
@@ -55711,6 +55844,8 @@ class ExecutionPlan {
55711
55844
  summary.rectificationExhausted = true;
55712
55845
  if (rectResult.unfixedFindings)
55713
55846
  summary.unfixedFindingsCount = rectResult.unfixedFindings.length;
55847
+ if (missingRequiredReviewPhases.length > 0)
55848
+ summary.missingRequiredReviewPhases = missingRequiredReviewPhases;
55714
55849
  if (success2) {
55715
55850
  logger?.info("story-orchestrator", "Story orchestration complete", summary);
55716
55851
  } else {
@@ -55723,11 +55858,21 @@ class ExecutionPlan {
55723
55858
  durationMs,
55724
55859
  phaseOutputs,
55725
55860
  ...rectResult,
55726
- gateRegressedDuringRect
55861
+ gateRegressedDuringRect,
55862
+ missingRequiredReviewPhases: missingRequiredReviewPhases.length > 0 ? missingRequiredReviewPhases : undefined
55727
55863
  };
55728
55864
  }
55729
55865
  }
55866
+ var init_execution_plan = __esm(() => {
55867
+ init_logger2();
55868
+ init_non_blocking_fix();
55869
+ init_phase_eval();
55870
+ init_phase_state();
55871
+ init_rectification();
55872
+ init_run_phase();
55873
+ });
55730
55874
 
55875
+ // src/execution/story-orchestrator/builder.ts
55731
55876
  class StoryOrchestratorBuilder {
55732
55877
  state = {};
55733
55878
  addImplementer(value) {
@@ -55787,80 +55932,21 @@ class StoryOrchestratorBuilder {
55787
55932
  return new ExecutionPlan(ctx, { ...this.state }, opts.isThreeSession ?? false);
55788
55933
  }
55789
55934
  }
55790
- var _storyOrchestratorDeps, EXHAUSTED_EXIT_REASONS, TDD_OP_NAMES, STRICT_VERDICT_PHASE_NAMES, CANONICAL_ORDER, PHASE_KIND_TO_STATE_KEY, STRATEGY_TO_REVALIDATION_PHASES;
55791
- var init_story_orchestrator = __esm(() => {
55935
+ var init_builder2 = __esm(() => {
55792
55936
  init_errors();
55793
- init_findings();
55794
- init_logger2();
55795
55937
  init_operations();
55796
- init_call();
55797
- init_event_bus();
55798
- init_prepare_inputs();
55799
- init_git();
55800
- init_non_blocking_fix();
55801
- init_story_orchestrator_logging();
55802
- _storyOrchestratorDeps = {
55803
- callOp,
55804
- runFixCycle,
55805
- captureGitRef,
55806
- prepareSemanticReviewInput,
55807
- prepareAdversarialReviewInput,
55808
- runNonBlockingFix
55809
- };
55810
- EXHAUSTED_EXIT_REASONS = new Set([
55811
- "max-attempts-total",
55812
- "max-attempts-per-strategy",
55813
- "bail-when",
55814
- "no-strategy",
55815
- "agent-gave-up",
55816
- "validate-short-circuit"
55817
- ]);
55818
- TDD_OP_NAMES = new Set(["test-writer", "implementer", "verifier"]);
55819
- STRICT_VERDICT_PHASE_NAMES = new Set([
55820
- fullSuiteGateOp.name,
55821
- verifyScopedOp.name,
55822
- lintCheckOp.name,
55823
- typecheckCheckOp.name,
55824
- verifierOp.name
55825
- ]);
55826
- CANONICAL_ORDER = [
55827
- "test-writer",
55828
- "greenfield-gate",
55829
- "implementer",
55830
- "full-suite-gate",
55831
- "verifier",
55832
- "verify-scoped",
55833
- "lint-check",
55834
- "typecheck-check",
55835
- "semantic-review",
55836
- "adversarial-review"
55837
- ];
55838
- PHASE_KIND_TO_STATE_KEY = {
55839
- "test-writer": "testWriter",
55840
- "greenfield-gate": "greenfieldGate",
55841
- implementer: "implementer",
55842
- "full-suite-gate": "fullSuiteGate",
55843
- verifier: "verifier",
55844
- "verify-scoped": "verifyScoped",
55845
- "lint-check": "lintCheck",
55846
- "typecheck-check": "typecheckCheck",
55847
- "semantic-review": "semanticReview",
55848
- "adversarial-review": "adversarialReview"
55849
- };
55850
- STRATEGY_TO_REVALIDATION_PHASES = {
55851
- "mechanical-lintfix": ["lint-check"],
55852
- "mechanical-formatfix": ["lint-check"],
55853
- "autofix-implementer": ["lint-check", "typecheck-check", "full-suite-gate", "semantic-review", "adversarial-review"],
55854
- "autofix-test-writer": ["lint-check", "typecheck-check", "full-suite-gate", "adversarial-review"],
55855
- "full-suite-rectify": [
55856
- "lint-check",
55857
- "typecheck-check",
55858
- "full-suite-gate",
55859
- "verifier",
55860
- "verify-scoped",
55861
- "semantic-review"
55862
- ]
55863
- };
55938
+ init_execution_plan();
55939
+ init_phase_state();
55940
+ });
55941
+
55942
+ // src/execution/story-orchestrator/index.ts
55943
+ var init_story_orchestrator = __esm(() => {
55944
+ init_builder2();
55945
+ init_execution_plan();
55946
+ init_phase_eval();
55947
+ init_rectification();
55948
+ init_run_phase();
55949
+ init_types9();
55864
55950
  });
55865
55951
 
55866
55952
  // src/execution/build-plan-for-strategy.ts
@@ -56247,7 +56333,7 @@ function routeTddFailure(failureCategory, isLiteMode, ctx, reviewReason, failure
56247
56333
  }
56248
56334
  return { action: "escalate", reason: buildReason("isolation-violation") };
56249
56335
  }
56250
- if (failureCategory === "session-failure" || failureCategory === "tests-failing" || failureCategory === "full-suite-gate-exhausted" || failureCategory === "verifier-rejected" || failureCategory === "runtime-crash") {
56336
+ if (failureCategory === "session-failure" || failureCategory === "tests-failing" || failureCategory === "full-suite-gate-exhausted" || failureCategory === "verifier-rejected" || failureCategory === "runtime-crash" || failureCategory === "review-incomplete") {
56251
56337
  return { action: "escalate", reason: buildReason(failureCategory) };
56252
56338
  }
56253
56339
  if (failureCategory === "greenfield-no-tests") {
@@ -56346,22 +56432,8 @@ async function closeAllRunSessions(sessionManager, agentGetFn, opts) {
56346
56432
  return totalClosed;
56347
56433
  }
56348
56434
 
56349
- // src/execution/post-run.ts
56350
- function shouldRollbackTddFailure(tddMode, failureCategory) {
56351
- return tddMode?.rollbackEnabled === true && failureCategory === "isolation-violation";
56352
- }
56353
- function extractPauseReason(phaseOutputs) {
56354
- for (const output of Object.values(phaseOutputs)) {
56355
- if (output !== null && typeof output === "object") {
56356
- const record2 = output;
56357
- if (typeof record2.pauseReason === "string" && record2.pauseReason) {
56358
- return record2.pauseReason;
56359
- }
56360
- }
56361
- }
56362
- return;
56363
- }
56364
- function deriveTddFailureCategory(phaseOutputs, unfixedFindings, gateRegressedDuringRect) {
56435
+ // src/execution/tdd-failure-category.ts
56436
+ function deriveTddFailureCategory(phaseOutputs, unfixedFindings, gateRegressedDuringRect, missingRequiredReviewPhases) {
56365
56437
  const testWriterOutput = phaseOutputs[testWriterOp.name];
56366
56438
  if (testWriterOutput?.success === false) {
56367
56439
  return "session-failure";
@@ -56400,6 +56472,29 @@ function deriveTddFailureCategory(phaseOutputs, unfixedFindings, gateRegressedDu
56400
56472
  if (implOutput?.success === false) {
56401
56473
  return "session-failure";
56402
56474
  }
56475
+ if (missingRequiredReviewPhases && missingRequiredReviewPhases.length > 0) {
56476
+ return "review-incomplete";
56477
+ }
56478
+ return;
56479
+ }
56480
+ var init_tdd_failure_category = __esm(() => {
56481
+ init_operations();
56482
+ init_story_orchestrator();
56483
+ });
56484
+
56485
+ // src/execution/post-run.ts
56486
+ function shouldRollbackTddFailure(tddMode, failureCategory) {
56487
+ return tddMode?.rollbackEnabled === true && failureCategory === "isolation-violation";
56488
+ }
56489
+ function extractPauseReason(phaseOutputs) {
56490
+ for (const output of Object.values(phaseOutputs)) {
56491
+ if (output !== null && typeof output === "object") {
56492
+ const record2 = output;
56493
+ if (typeof record2.pauseReason === "string" && record2.pauseReason) {
56494
+ return record2.pauseReason;
56495
+ }
56496
+ }
56497
+ }
56403
56498
  return;
56404
56499
  }
56405
56500
  async function cleanupSessionOnFailure(ctx) {
@@ -56497,7 +56592,7 @@ async function applyPostRunInspection(ctx, planResult, opts) {
56497
56592
  }
56498
56593
  }
56499
56594
  const pauseReason = extractPauseReason(planResult.phaseOutputs);
56500
- const failureCategory = isTdd && !planResult.success ? deriveTddFailureCategory(planResult.phaseOutputs, planResult.unfixedFindings, planResult.gateRegressedDuringRect) : undefined;
56595
+ const failureCategory = isTdd && !planResult.success ? deriveTddFailureCategory(planResult.phaseOutputs, planResult.unfixedFindings, planResult.gateRegressedDuringRect, planResult.missingRequiredReviewPhases) : undefined;
56501
56596
  if (isTdd && !planResult.success && !failureCategory) {
56502
56597
  const phaseSignals = {};
56503
56598
  for (const [name, output] of Object.entries(planResult.phaseOutputs)) {
@@ -56716,7 +56811,7 @@ var init_post_run = __esm(() => {
56716
56811
  init_scratch_writer();
56717
56812
  init_rollback();
56718
56813
  init_git();
56719
- init_story_orchestrator();
56814
+ init_tdd_failure_category();
56720
56815
  _postRunDeps = {
56721
56816
  detectMergeConflict,
56722
56817
  checkMergeConflict,
@@ -58853,17 +58948,46 @@ var init_setup_verify = __esm(() => {
58853
58948
  };
58854
58949
  });
58855
58950
 
58951
+ // src/cli/setup-write.ts
58952
+ import { join as join57 } from "path";
58953
+ async function writeSetupConfig(workdir, config2, monoConfigs, _opts, deps = _writeSetupDeps) {
58954
+ const naxDir = join57(workdir, ".nax");
58955
+ const naxConfigPath = join57(naxDir, "config.json");
58956
+ const written = [];
58957
+ await deps.mkdir(naxDir);
58958
+ await deps.writeFile(naxConfigPath, JSON.stringify(config2, null, 2));
58959
+ written.push(naxConfigPath);
58960
+ for (const mc of monoConfigs) {
58961
+ const monoDir = join57(naxDir, "mono", mc.relativeDir);
58962
+ const monoConfigPath = join57(monoDir, "config.json");
58963
+ await deps.mkdir(monoDir);
58964
+ await deps.writeFile(monoConfigPath, JSON.stringify(mc.config, null, 2));
58965
+ written.push(monoConfigPath);
58966
+ }
58967
+ return { written };
58968
+ }
58969
+ var _writeSetupDeps;
58970
+ var init_setup_write = __esm(() => {
58971
+ _writeSetupDeps = {
58972
+ writeFile: (path14, content) => Bun.write(path14, content).then(() => {}),
58973
+ mkdir: async (path14) => {
58974
+ const proc = Bun.spawn(["mkdir", "-p", path14]);
58975
+ await proc.exited;
58976
+ }
58977
+ };
58978
+ });
58979
+
58856
58980
  // src/cli/setup.ts
58857
58981
  var exports_setup = {};
58858
58982
  __export(exports_setup, {
58859
58983
  setupCommand: () => setupCommand,
58860
58984
  _setupDeps: () => _setupDeps
58861
58985
  });
58862
- import { join as join57 } from "path";
58986
+ import { join as join58 } from "path";
58863
58987
  async function setupCommand(options = {}) {
58864
58988
  const workdir = options.dir ?? process.cwd();
58865
- const naxDir = join57(workdir, ".nax");
58866
- const naxConfigPath = join57(naxDir, "config.json");
58989
+ const naxDir = join58(workdir, ".nax");
58990
+ const naxConfigPath = join58(naxDir, "config.json");
58867
58991
  const exists = await _setupDeps.fileExists(naxConfigPath);
58868
58992
  if (exists && !options.force) {
58869
58993
  _setupDeps.stderr("[setup] .nax/config.json already exists. Use --force to overwrite.");
@@ -58893,13 +59017,7 @@ ${JSON.stringify(plan.config, null, 2)}`);
58893
59017
  if (options.fillScripts) {
58894
59018
  await _setupDeps.fillScripts(workdir, analysis);
58895
59019
  }
58896
- await _setupDeps.mkdir(naxDir);
58897
- await _setupDeps.writeFile(naxConfigPath, JSON.stringify(plan.config, null, 2));
58898
- for (const mc of plan.monoConfigs) {
58899
- const monoDir = join57(naxDir, "mono", mc.relativeDir);
58900
- await _setupDeps.mkdir(monoDir);
58901
- await _setupDeps.writeFile(join57(monoDir, "config.json"), JSON.stringify(mc.config, null, 2));
58902
- }
59020
+ await _setupDeps.writeSetupConfig(workdir, plan.config, plan.monoConfigs, { force: options.force });
58903
59021
  const gateResult = await _setupDeps.runGate(workdir, plan.config);
58904
59022
  if (gateResult !== 0) {
58905
59023
  return gateResult;
@@ -58916,6 +59034,7 @@ var init_setup = __esm(() => {
58916
59034
  init_setup_fill();
58917
59035
  init_setup_llm();
58918
59036
  init_setup_verify();
59037
+ init_setup_write();
58919
59038
  _setupDeps = {
58920
59039
  analyzeRepo,
58921
59040
  fillScripts,
@@ -58937,11 +59056,7 @@ var init_setup = __esm(() => {
58937
59056
  generateSetupPlan: (ctx, analysis) => generateSetupPlan(ctx, analysis),
58938
59057
  runGate: (workdir, config2) => runSetupGate(workdir, config2),
58939
59058
  fileExists: (path14) => Bun.file(path14).exists(),
58940
- writeFile: (path14, content) => Bun.write(path14, content).then(() => {}),
58941
- mkdir: async (path14) => {
58942
- const proc = Bun.spawn(["mkdir", "-p", path14]);
58943
- await proc.exited;
58944
- },
59059
+ writeSetupConfig: (workdir, config2, monoConfigs, opts) => writeSetupConfig(workdir, config2, monoConfigs, opts),
58945
59060
  stdout: (msg) => {
58946
59061
  process.stdout.write(`${msg}
58947
59062
  `);
@@ -60380,7 +60495,7 @@ var init_command_argv = __esm(() => {
60380
60495
  });
60381
60496
 
60382
60497
  // src/hooks/runner.ts
60383
- import { join as join74 } from "path";
60498
+ import { join as join75 } from "path";
60384
60499
  function createDrainDeadline2(deadlineMs) {
60385
60500
  let timeoutId;
60386
60501
  const promise2 = new Promise((resolve16) => {
@@ -60399,14 +60514,14 @@ async function loadHooksConfig(projectDir, globalDir) {
60399
60514
  let globalHooks = { hooks: {} };
60400
60515
  let projectHooks = { hooks: {} };
60401
60516
  let skipGlobal = false;
60402
- const projectPath = join74(projectDir, "hooks.json");
60517
+ const projectPath = join75(projectDir, "hooks.json");
60403
60518
  const projectData = await loadJsonFile(projectPath, "hooks");
60404
60519
  if (projectData) {
60405
60520
  projectHooks = projectData;
60406
60521
  skipGlobal = projectData.skipGlobal ?? false;
60407
60522
  }
60408
60523
  if (!skipGlobal && globalDir) {
60409
- const globalPath = join74(globalDir, "hooks.json");
60524
+ const globalPath = join75(globalDir, "hooks.json");
60410
60525
  const globalData = await loadJsonFile(globalPath, "hooks");
60411
60526
  if (globalData) {
60412
60527
  globalHooks = globalData;
@@ -60576,7 +60691,7 @@ var package_default;
60576
60691
  var init_package = __esm(() => {
60577
60692
  package_default = {
60578
60693
  name: "@nathapp/nax",
60579
- version: "0.70.1",
60694
+ version: "0.70.2",
60580
60695
  description: "AI Coding Agent Orchestrator \u2014 loops until done",
60581
60696
  type: "module",
60582
60697
  bin: {
@@ -60587,7 +60702,7 @@ var init_package = __esm(() => {
60587
60702
  dev: "bun run bin/nax.ts",
60588
60703
  build: 'bun build bin/nax.ts --outdir dist --target bun --define "GIT_COMMIT=\\"$(git rev-parse --short HEAD)\\""',
60589
60704
  typecheck: "bun x tsc --noEmit && bun x tsc --noEmit -p tsconfig.contracts.json",
60590
- lint: "bun x biome check src/ bin/ && bun run check:no-real-global-nax && bun run check:alias-internals && bun run check:deep-relatives && bun run check:nax-error && bun run check:logger-storyid",
60705
+ lint: "bun x biome check src/ bin/ && bun run check:no-real-global-nax && bun run check:alias-internals && bun run check:deep-relatives && bun run check:nax-error && bun run check:logger-storyid && bun run check:file-sizes",
60591
60706
  "lint:json": "bun x biome check src/ bin/ --reporter json && bun run check:nax-error 1>&2 && bun run check:logger-storyid 1>&2",
60592
60707
  "lint:fix": "bun x biome check --write src/ bin/",
60593
60708
  "check:no-real-global-nax": "bun run scripts/check-no-real-global-nax.ts",
@@ -60598,6 +60713,8 @@ var init_package = __esm(() => {
60598
60713
  "check:nax-error:update": "bun run scripts/check-nax-error.ts --update-baseline",
60599
60714
  "check:logger-storyid": "bun run scripts/check-logger-storyid.ts",
60600
60715
  "check:logger-storyid:update": "bun run scripts/check-logger-storyid.ts --update-baseline",
60716
+ "check:file-sizes": "bun run scripts/check-file-sizes.ts",
60717
+ "check:file-sizes:update": "bun run scripts/check-file-sizes.ts --update-baseline",
60601
60718
  release: "bun scripts/release.ts",
60602
60719
  test: "AGENT=1 bun run scripts/run-tests.ts",
60603
60720
  "test:verbose": "bun run scripts/run-tests.ts",
@@ -60609,6 +60726,8 @@ var init_package = __esm(() => {
60609
60726
  "test:integration": "bun test ./test/integration/ --timeout=60000",
60610
60727
  "test:ui": "bun test ./test/ui/ --timeout=60000",
60611
60728
  "test:e2e": "timeout -k 5s 180s bun test test/e2e/ --timeout=60000",
60729
+ "test:coverage": "bun run scripts/check-coverage.ts",
60730
+ "test:coverage:report": "bun run scripts/check-coverage.ts --report",
60612
60731
  "check-test-overlap": "bun run scripts/check-test-overlap.ts",
60613
60732
  "check-dead-tests": "bun run scripts/check-dead-tests.ts",
60614
60733
  "check:test-sizes": "bun run scripts/check-test-sizes.ts",
@@ -60672,8 +60791,8 @@ var init_version = __esm(() => {
60672
60791
  NAX_VERSION = package_default.version;
60673
60792
  NAX_COMMIT = (() => {
60674
60793
  try {
60675
- if (/^[0-9a-f]{6,10}$/.test("c53be9bf"))
60676
- return "c53be9bf";
60794
+ if (/^[0-9a-f]{6,10}$/.test("0e202cfa"))
60795
+ return "0e202cfa";
60677
60796
  } catch {}
60678
60797
  try {
60679
60798
  const result = Bun.spawnSync(["git", "rev-parse", "--short", "HEAD"], {
@@ -61551,15 +61670,15 @@ var init_acceptance_loop = __esm(() => {
61551
61670
 
61552
61671
  // src/session/scratch-purge.ts
61553
61672
  import { mkdir as mkdir12, rename, rm } from "fs/promises";
61554
- import { dirname as dirname12, join as join75 } from "path";
61673
+ import { dirname as dirname12, join as join76 } from "path";
61555
61674
  async function purgeStaleScratch(projectDir, featureName, retentionDays, archiveInsteadOfDelete = false) {
61556
- const sessionsDir = join75(projectDir, ".nax", "features", featureName, "sessions");
61675
+ const sessionsDir = join76(projectDir, ".nax", "features", featureName, "sessions");
61557
61676
  const sessionIds = await _scratchPurgeDeps.listSessionDirs(sessionsDir);
61558
61677
  const cutoffMs = _scratchPurgeDeps.now() - retentionDays * 86400000;
61559
61678
  let purged = 0;
61560
61679
  for (const sessionId of sessionIds) {
61561
- const sessionDir = join75(sessionsDir, sessionId);
61562
- const descriptorPath = join75(sessionDir, "descriptor.json");
61680
+ const sessionDir = join76(sessionsDir, sessionId);
61681
+ const descriptorPath = join76(sessionDir, "descriptor.json");
61563
61682
  if (!await _scratchPurgeDeps.fileExists(descriptorPath))
61564
61683
  continue;
61565
61684
  let lastActivityAt;
@@ -61575,7 +61694,7 @@ async function purgeStaleScratch(projectDir, featureName, retentionDays, archive
61575
61694
  if (new Date(lastActivityAt).getTime() >= cutoffMs)
61576
61695
  continue;
61577
61696
  if (archiveInsteadOfDelete) {
61578
- const archiveDest = join75(projectDir, ".nax", "features", featureName, "_archive", "sessions", sessionId);
61697
+ const archiveDest = join76(projectDir, ".nax", "features", featureName, "_archive", "sessions", sessionId);
61579
61698
  await _scratchPurgeDeps.move(sessionDir, archiveDest);
61580
61699
  } else {
61581
61700
  await _scratchPurgeDeps.remove(sessionDir);
@@ -62280,7 +62399,7 @@ function outputRunFooter(options) {
62280
62399
  }
62281
62400
  var init_headless_formatter = __esm(() => {
62282
62401
  init_source();
62283
- init_logging();
62402
+ init_log_format();
62284
62403
  init_version();
62285
62404
  });
62286
62405
 
@@ -62326,12 +62445,12 @@ var DEFAULT_MAX_BATCH_SIZE = 4;
62326
62445
 
62327
62446
  // src/pipeline/subscribers/events-writer.ts
62328
62447
  import { appendFile as appendFile4, mkdir as mkdir13 } from "fs/promises";
62329
- import { basename as basename13, join as join76 } from "path";
62448
+ import { basename as basename13, join as join77 } from "path";
62330
62449
  function wireEventsWriter(bus, feature, runId, workdir) {
62331
62450
  const logger = getSafeLogger();
62332
62451
  const project = basename13(workdir);
62333
- const eventsDir = join76(getEventsRootDir(), project);
62334
- const eventsFile = join76(eventsDir, "events.jsonl");
62452
+ const eventsDir = join77(getEventsRootDir(), project);
62453
+ const eventsFile = join77(eventsDir, "events.jsonl");
62335
62454
  let dirReady = false;
62336
62455
  const write = (line) => {
62337
62456
  return (async () => {
@@ -62512,12 +62631,12 @@ var init_interaction2 = __esm(() => {
62512
62631
 
62513
62632
  // src/pipeline/subscribers/registry.ts
62514
62633
  import { mkdir as mkdir14, writeFile as writeFile2 } from "fs/promises";
62515
- import { basename as basename14, join as join77 } from "path";
62634
+ import { basename as basename14, join as join78 } from "path";
62516
62635
  function wireRegistry(bus, feature, runId, workdir, outputDir) {
62517
62636
  const logger = getSafeLogger();
62518
62637
  const project = basename14(workdir);
62519
- const runDir = join77(getRunsDir(), `${project}-${feature}-${runId}`);
62520
- const metaFile = join77(runDir, "meta.json");
62638
+ const runDir = join78(getRunsDir(), `${project}-${feature}-${runId}`);
62639
+ const metaFile = join78(runDir, "meta.json");
62521
62640
  const unsub = bus.on("run:started", (_ev) => {
62522
62641
  return (async () => {
62523
62642
  try {
@@ -62527,8 +62646,8 @@ function wireRegistry(bus, feature, runId, workdir, outputDir) {
62527
62646
  project,
62528
62647
  feature,
62529
62648
  workdir,
62530
- statusPath: join77(outputDir, "features", feature, "status.json"),
62531
- eventsDir: join77(outputDir, "features", feature, "runs"),
62649
+ statusPath: join78(outputDir, "features", feature, "status.json"),
62650
+ eventsDir: join78(outputDir, "features", feature, "runs"),
62532
62651
  registeredAt: new Date().toISOString()
62533
62652
  };
62534
62653
  await writeFile2(metaFile, JSON.stringify(meta3, null, 2));
@@ -62760,7 +62879,7 @@ function buildPreviewRouting(story, config2) {
62760
62879
 
62761
62880
  // src/worktree/types.ts
62762
62881
  var WorktreeDependencyPreparationError;
62763
- var init_types9 = __esm(() => {
62882
+ var init_types10 = __esm(() => {
62764
62883
  WorktreeDependencyPreparationError = class WorktreeDependencyPreparationError extends Error {
62765
62884
  mode;
62766
62885
  failureCategory = "dependency-prep";
@@ -62774,7 +62893,7 @@ var init_types9 = __esm(() => {
62774
62893
 
62775
62894
  // src/worktree/dependencies.ts
62776
62895
  import { existsSync as existsSync30 } from "fs";
62777
- import { join as join78 } from "path";
62896
+ import { join as join79 } from "path";
62778
62897
  async function prepareWorktreeDependencies(options) {
62779
62898
  const mode = options.config.execution.worktreeDependencies.mode;
62780
62899
  const resolvedCwd = resolveDependencyCwd(options);
@@ -62788,7 +62907,7 @@ async function prepareWorktreeDependencies(options) {
62788
62907
  }
62789
62908
  }
62790
62909
  function resolveDependencyCwd(options) {
62791
- return options.storyWorkdir ? join78(options.worktreeRoot, options.storyWorkdir) : options.worktreeRoot;
62910
+ return options.storyWorkdir ? join79(options.worktreeRoot, options.storyWorkdir) : options.worktreeRoot;
62792
62911
  }
62793
62912
  function resolveInheritedDependencies(options, resolvedCwd) {
62794
62913
  if (hasDependencyManifests(options.worktreeRoot, resolvedCwd)) {
@@ -62798,7 +62917,7 @@ function resolveInheritedDependencies(options, resolvedCwd) {
62798
62917
  }
62799
62918
  function hasDependencyManifests(worktreeRoot, resolvedCwd) {
62800
62919
  const directories = resolvedCwd === worktreeRoot ? [worktreeRoot] : [worktreeRoot, resolvedCwd];
62801
- return directories.some((directory) => PHASE_ONE_INHERIT_UNSUPPORTED_FILES.some((filename) => _worktreeDependencyDeps.existsSync(join78(directory, filename))));
62920
+ return directories.some((directory) => PHASE_ONE_INHERIT_UNSUPPORTED_FILES.some((filename) => _worktreeDependencyDeps.existsSync(join79(directory, filename))));
62802
62921
  }
62803
62922
  async function provisionDependencies(config2, worktreeRoot, resolvedCwd) {
62804
62923
  const setupCommand2 = config2.execution.worktreeDependencies.setupCommand;
@@ -62830,7 +62949,7 @@ var PHASE_ONE_INHERIT_UNSUPPORTED_FILES, _worktreeDependencyDeps;
62830
62949
  var init_dependencies = __esm(() => {
62831
62950
  init_bun_deps();
62832
62951
  init_command_argv();
62833
- init_types9();
62952
+ init_types10();
62834
62953
  PHASE_ONE_INHERIT_UNSUPPORTED_FILES = [
62835
62954
  "package.json",
62836
62955
  "bun.lock",
@@ -62862,13 +62981,13 @@ __export(exports_manager, {
62862
62981
  });
62863
62982
  import { existsSync as existsSync31, symlinkSync } from "fs";
62864
62983
  import { mkdir as mkdir15 } from "fs/promises";
62865
- import { join as join79 } from "path";
62984
+ import { join as join80 } from "path";
62866
62985
 
62867
62986
  class WorktreeManager {
62868
62987
  async ensureGitExcludes(projectRoot) {
62869
62988
  const logger = getSafeLogger();
62870
- const infoDir = join79(projectRoot, ".git", "info");
62871
- const excludePath = join79(infoDir, "exclude");
62989
+ const infoDir = join80(projectRoot, ".git", "info");
62990
+ const excludePath = join80(infoDir, "exclude");
62872
62991
  try {
62873
62992
  await mkdir15(infoDir, { recursive: true });
62874
62993
  let existing = "";
@@ -62895,7 +63014,7 @@ ${missing.join(`
62895
63014
  }
62896
63015
  async create(projectRoot, storyId) {
62897
63016
  validateStoryId(storyId);
62898
- const worktreePath = join79(projectRoot, ".nax-wt", storyId);
63017
+ const worktreePath = join80(projectRoot, ".nax-wt", storyId);
62899
63018
  const branchName = `nax/${storyId}`;
62900
63019
  try {
62901
63020
  const pruneProc = _managerDeps.spawn(["git", "worktree", "prune"], {
@@ -62935,9 +63054,9 @@ ${missing.join(`
62935
63054
  }
62936
63055
  throw new Error(`Failed to create worktree: ${String(error48)}`);
62937
63056
  }
62938
- const envSource = join79(projectRoot, ".env");
63057
+ const envSource = join80(projectRoot, ".env");
62939
63058
  if (existsSync31(envSource)) {
62940
- const envTarget = join79(worktreePath, ".env");
63059
+ const envTarget = join80(worktreePath, ".env");
62941
63060
  try {
62942
63061
  symlinkSync(envSource, envTarget, "file");
62943
63062
  } catch (error48) {
@@ -62948,7 +63067,7 @@ ${missing.join(`
62948
63067
  }
62949
63068
  async remove(projectRoot, storyId) {
62950
63069
  validateStoryId(storyId);
62951
- const worktreePath = join79(projectRoot, ".nax-wt", storyId);
63070
+ const worktreePath = join80(projectRoot, ".nax-wt", storyId);
62952
63071
  const branchName = `nax/${storyId}`;
62953
63072
  try {
62954
63073
  const proc = _managerDeps.spawn(["git", "worktree", "remove", worktreePath, "--force"], {
@@ -63532,6 +63651,8 @@ function resolveMaxAttemptsOutcome(failureCategory) {
63532
63651
  return "pause";
63533
63652
  case "runtime-crash":
63534
63653
  return "pause";
63654
+ case "review-incomplete":
63655
+ return "pause";
63535
63656
  case "session-failure":
63536
63657
  case "tests-failing":
63537
63658
  case "full-suite-gate-exhausted":
@@ -63770,10 +63891,10 @@ var init_merge_conflict_rectify = __esm(() => {
63770
63891
  });
63771
63892
 
63772
63893
  // src/execution/pipeline-result-handler.ts
63773
- import { join as join80 } from "path";
63894
+ import { join as join81 } from "path";
63774
63895
  async function removeWorktreeDirectory(projectRoot, storyId) {
63775
63896
  const logger = getSafeLogger();
63776
- const worktreePath = join80(projectRoot, ".nax-wt", storyId);
63897
+ const worktreePath = join81(projectRoot, ".nax-wt", storyId);
63777
63898
  try {
63778
63899
  const proc = _resultHandlerDeps.spawn(["git", "worktree", "remove", worktreePath, "--force"], {
63779
63900
  cwd: projectRoot,
@@ -63990,7 +64111,7 @@ var init_pipeline_result_handler = __esm(() => {
63990
64111
 
63991
64112
  // src/execution/iteration-runner.ts
63992
64113
  import { existsSync as existsSync32 } from "fs";
63993
- import { join as join81 } from "path";
64114
+ import { join as join82 } from "path";
63994
64115
  async function runIteration(ctx, prd, selection, iterations, totalCost, allStoryMetrics) {
63995
64116
  const { story, storiesToExecute, routing, isBatchExecution } = selection;
63996
64117
  if (ctx.dryRun) {
@@ -64015,7 +64136,7 @@ async function runIteration(ctx, prd, selection, iterations, totalCost, allStory
64015
64136
  const storyStartTime = Date.now();
64016
64137
  let effectiveWorkdir = ctx.workdir;
64017
64138
  if (ctx.config.execution.storyIsolation === "worktree") {
64018
- const worktreePath = join81(ctx.workdir, ".nax-wt", story.id);
64139
+ const worktreePath = join82(ctx.workdir, ".nax-wt", story.id);
64019
64140
  const worktreeExists = _iterationRunnerDeps.existsSync(worktreePath);
64020
64141
  if (!worktreeExists) {
64021
64142
  await _iterationRunnerDeps.worktreeManager.ensureGitExcludes(ctx.workdir);
@@ -64035,7 +64156,7 @@ async function runIteration(ctx, prd, selection, iterations, totalCost, allStory
64035
64156
  }
64036
64157
  const accumulatedAttemptCost = (story.priorFailures || []).reduce((sum, f) => sum + (f.cost || 0), 0);
64037
64158
  const profileOverride = profileOverrideFromConfig(ctx.config);
64038
- const effectiveConfig = story.workdir ? await _iterationRunnerDeps.loadConfigForWorkdir(join81(ctx.workdir, ".nax", "config.json"), story.workdir, profileOverride) : ctx.config;
64159
+ const effectiveConfig = story.workdir ? await _iterationRunnerDeps.loadConfigForWorkdir(join82(ctx.workdir, ".nax", "config.json"), story.workdir, profileOverride) : ctx.config;
64039
64160
  let dependencyContext;
64040
64161
  if (ctx.config.execution.storyIsolation === "worktree") {
64041
64162
  try {
@@ -64062,7 +64183,7 @@ async function runIteration(ctx, prd, selection, iterations, totalCost, allStory
64062
64183
  };
64063
64184
  }
64064
64185
  }
64065
- const resolvedWorkdir = dependencyContext?.cwd ? dependencyContext.cwd : ctx.config.execution.storyIsolation === "worktree" ? story.workdir ? join81(effectiveWorkdir, story.workdir) : effectiveWorkdir : story.workdir ? join81(ctx.workdir, story.workdir) : ctx.workdir;
64186
+ const resolvedWorkdir = dependencyContext?.cwd ? dependencyContext.cwd : ctx.config.execution.storyIsolation === "worktree" ? story.workdir ? join82(effectiveWorkdir, story.workdir) : effectiveWorkdir : story.workdir ? join82(ctx.workdir, story.workdir) : ctx.workdir;
64066
64187
  const pipelineContext = {
64067
64188
  config: effectiveConfig,
64068
64189
  rootConfig: ctx.config,
@@ -64265,7 +64386,7 @@ __export(exports_parallel_worker, {
64265
64386
  buildWorktreePipelineContext: () => buildWorktreePipelineContext,
64266
64387
  _parallelWorkerDeps: () => _parallelWorkerDeps
64267
64388
  });
64268
- import { join as join82 } from "path";
64389
+ import { join as join83 } from "path";
64269
64390
  function buildWorktreePipelineContext(base, _story) {
64270
64391
  return { ...base, prd: structuredClone(base.prd) };
64271
64392
  }
@@ -64288,7 +64409,7 @@ async function executeStoryInWorktree(story, worktreePath, dependencyContext, co
64288
64409
  story,
64289
64410
  stories: [story],
64290
64411
  projectDir: context.projectDir,
64291
- workdir: dependencyContext.cwd ?? (story.workdir ? join82(worktreePath, story.workdir) : worktreePath),
64412
+ workdir: dependencyContext.cwd ?? (story.workdir ? join83(worktreePath, story.workdir) : worktreePath),
64292
64413
  worktreeDependencyContext: dependencyContext,
64293
64414
  routing,
64294
64415
  storyGitRef: storyGitRef ?? undefined
@@ -65188,7 +65309,7 @@ async function writeStatusFile(filePath, status) {
65188
65309
  var init_status_file = () => {};
65189
65310
 
65190
65311
  // src/execution/status-writer.ts
65191
- import { join as join83 } from "path";
65312
+ import { join as join84 } from "path";
65192
65313
 
65193
65314
  class StatusWriter {
65194
65315
  statusFile;
@@ -65307,7 +65428,7 @@ class StatusWriter {
65307
65428
  if (!this._prd)
65308
65429
  return;
65309
65430
  const safeLogger = getSafeLogger();
65310
- const featureStatusPath = join83(featureDir, "status.json");
65431
+ const featureStatusPath = join84(featureDir, "status.json");
65311
65432
  const write = async () => {
65312
65433
  try {
65313
65434
  const base = this.getSnapshot(totalCost, iterations);
@@ -65741,7 +65862,7 @@ __export(exports_run_initialization, {
65741
65862
  initializeRun: () => initializeRun,
65742
65863
  _reconcileDeps: () => _reconcileDeps
65743
65864
  });
65744
- import { join as join84 } from "path";
65865
+ import { join as join85 } from "path";
65745
65866
  async function reconcileState(prd, prdPath, workdir, config2) {
65746
65867
  const logger = getSafeLogger();
65747
65868
  let reconciledCount = 0;
@@ -65758,7 +65879,7 @@ async function reconcileState(prd, prdPath, workdir, config2) {
65758
65879
  });
65759
65880
  continue;
65760
65881
  }
65761
- const effectiveWorkdir = story.workdir ? join84(workdir, story.workdir) : workdir;
65882
+ const effectiveWorkdir = story.workdir ? join85(workdir, story.workdir) : workdir;
65762
65883
  try {
65763
65884
  const reviewResult = await _reconcileDeps.runReview(config2.review, effectiveWorkdir, config2.execution);
65764
65885
  if (!reviewResult.success) {
@@ -95589,7 +95710,7 @@ __export(exports_curator, {
95589
95710
  });
95590
95711
  import { readdirSync as readdirSync8 } from "fs";
95591
95712
  import { unlink as unlink4 } from "fs/promises";
95592
- import { basename as basename15, join as join86 } from "path";
95713
+ import { basename as basename15, join as join87 } from "path";
95593
95714
  function getProjectKey(config2, projectDir) {
95594
95715
  return config2.name?.trim() || basename15(projectDir);
95595
95716
  }
@@ -95672,7 +95793,7 @@ async function curatorStatus(options) {
95672
95793
  const config2 = await _curatorCmdDeps.loadConfig(resolved.projectDir);
95673
95794
  const projectKey = getProjectKey(config2, resolved.projectDir);
95674
95795
  const outputDir = _curatorCmdDeps.projectOutputDir(projectKey, config2.outputDir);
95675
- const runsDir = join86(outputDir, "runs");
95796
+ const runsDir = join87(outputDir, "runs");
95676
95797
  const runIds = listRunIds(runsDir);
95677
95798
  let runId;
95678
95799
  if (options.run) {
@@ -95689,8 +95810,8 @@ async function curatorStatus(options) {
95689
95810
  runId = runIds[runIds.length - 1];
95690
95811
  }
95691
95812
  console.log(`Run: ${runId}`);
95692
- const runDir = join86(runsDir, runId);
95693
- const observationsPath = join86(runDir, "observations.jsonl");
95813
+ const runDir = join87(runsDir, runId);
95814
+ const observationsPath = join87(runDir, "observations.jsonl");
95694
95815
  const observations = await parseObservations(observationsPath);
95695
95816
  const counts = new Map;
95696
95817
  for (const obs of observations) {
@@ -95700,7 +95821,7 @@ async function curatorStatus(options) {
95700
95821
  for (const [kind, count] of counts.entries()) {
95701
95822
  console.log(` ${kind}: ${count}`);
95702
95823
  }
95703
- const proposalsPath = join86(runDir, "curator-proposals.md");
95824
+ const proposalsPath = join87(runDir, "curator-proposals.md");
95704
95825
  const proposalText = await _curatorCmdDeps.readFile(proposalsPath).catch(() => null);
95705
95826
  if (proposalText !== null) {
95706
95827
  console.log("");
@@ -95714,8 +95835,8 @@ async function curatorCommit(options) {
95714
95835
  const config2 = await _curatorCmdDeps.loadConfig(resolved.projectDir);
95715
95836
  const projectKey = getProjectKey(config2, resolved.projectDir);
95716
95837
  const outputDir = _curatorCmdDeps.projectOutputDir(projectKey, config2.outputDir);
95717
- const runDir = join86(outputDir, "runs", options.runId);
95718
- const proposalsPath = join86(runDir, "curator-proposals.md");
95838
+ const runDir = join87(outputDir, "runs", options.runId);
95839
+ const proposalsPath = join87(runDir, "curator-proposals.md");
95719
95840
  const proposalText = await _curatorCmdDeps.readFile(proposalsPath).catch(() => null);
95720
95841
  if (proposalText === null) {
95721
95842
  console.log(`curator-proposals.md not found for run ${options.runId}.`);
@@ -95731,7 +95852,7 @@ async function curatorCommit(options) {
95731
95852
  const dropFileState = new Map;
95732
95853
  const skippedDrops = new Set;
95733
95854
  for (const drop2 of drops) {
95734
- const targetPath = join86(resolved.projectDir, drop2.canonicalFile);
95855
+ const targetPath = join87(resolved.projectDir, drop2.canonicalFile);
95735
95856
  if (!dropFileState.has(targetPath)) {
95736
95857
  const fileExists2 = await Bun.file(targetPath).exists();
95737
95858
  const existing = fileExists2 ? await _curatorCmdDeps.readFile(targetPath).catch(() => "") : "";
@@ -95765,7 +95886,7 @@ async function curatorCommit(options) {
95765
95886
  if (skippedDrops.has(drop2)) {
95766
95887
  continue;
95767
95888
  }
95768
- const targetPath = join86(resolved.projectDir, drop2.canonicalFile);
95889
+ const targetPath = join87(resolved.projectDir, drop2.canonicalFile);
95769
95890
  const existing = await _curatorCmdDeps.readFile(targetPath).catch(() => "");
95770
95891
  const filtered = filterDropContent(existing, drop2.description);
95771
95892
  await _curatorCmdDeps.writeFile(targetPath, filtered);
@@ -95774,7 +95895,7 @@ async function curatorCommit(options) {
95774
95895
  }
95775
95896
  const adds = proposals.filter((p) => p.action === "add" || p.action === "advisory");
95776
95897
  for (const add2 of adds) {
95777
- const targetPath = join86(resolved.projectDir, add2.canonicalFile);
95898
+ const targetPath = join87(resolved.projectDir, add2.canonicalFile);
95778
95899
  const content = buildAddContent(add2);
95779
95900
  await _curatorCmdDeps.appendFile(targetPath, content);
95780
95901
  modifiedFiles.add(targetPath);
@@ -95811,7 +95932,7 @@ async function curatorDryrun(options) {
95811
95932
  const config2 = await _curatorCmdDeps.loadConfig(resolved.projectDir);
95812
95933
  const projectKey = getProjectKey(config2, resolved.projectDir);
95813
95934
  const outputDir = _curatorCmdDeps.projectOutputDir(projectKey, config2.outputDir);
95814
- const runsDir = join86(outputDir, "runs");
95935
+ const runsDir = join87(outputDir, "runs");
95815
95936
  const runIds = listRunIds(runsDir);
95816
95937
  if (runIds.length === 0) {
95817
95938
  console.log("No runs found.");
@@ -95822,7 +95943,7 @@ async function curatorDryrun(options) {
95822
95943
  console.log(`Run ${options.run} not found in ${runsDir}.`);
95823
95944
  return;
95824
95945
  }
95825
- const observationsPath = join86(runsDir, runId, "observations.jsonl");
95946
+ const observationsPath = join87(runsDir, runId, "observations.jsonl");
95826
95947
  const observations = await parseObservations(observationsPath);
95827
95948
  const thresholds = getThresholds(config2);
95828
95949
  const proposals = runHeuristics(observations, thresholds);
@@ -95863,12 +95984,12 @@ async function curatorGc(options) {
95863
95984
  await _curatorCmdDeps.writeFile(rollupPath, newContent);
95864
95985
  const projectKey = getProjectKey(config2, resolved.projectDir);
95865
95986
  const outputDir = _curatorCmdDeps.projectOutputDir(projectKey, config2.outputDir);
95866
- const perRunsDir = join86(outputDir, "runs");
95987
+ const perRunsDir = join87(outputDir, "runs");
95867
95988
  for (const runId of uniqueRunIds) {
95868
95989
  if (!keepSet.has(runId)) {
95869
- const runDir = join86(perRunsDir, runId);
95870
- await _curatorCmdDeps.removeFile(join86(runDir, "observations.jsonl"));
95871
- await _curatorCmdDeps.removeFile(join86(runDir, "curator-proposals.md"));
95990
+ const runDir = join87(perRunsDir, runId);
95991
+ await _curatorCmdDeps.removeFile(join87(runDir, "observations.jsonl"));
95992
+ await _curatorCmdDeps.removeFile(join87(runDir, "curator-proposals.md"));
95872
95993
  }
95873
95994
  }
95874
95995
  console.log(`[gc] Pruned rollup to ${keep} most recent runs (was ${uniqueRunIds.length}).`);
@@ -95913,7 +96034,7 @@ var init_curator2 = __esm(() => {
95913
96034
  init_source();
95914
96035
  import { existsSync as existsSync35, mkdirSync as mkdirSync7 } from "fs";
95915
96036
  import { homedir as homedir3 } from "os";
95916
- import { basename as basename16, join as join87 } from "path";
96037
+ import { basename as basename16, join as join88 } from "path";
95917
96038
 
95918
96039
  // node_modules/commander/esm.mjs
95919
96040
  var import__ = __toESM(require_commander(), 1);
@@ -97037,6 +97158,7 @@ async function runsShowCommand(options) {
97037
97158
  init_prompts2();
97038
97159
  init_init2();
97039
97160
  init_setup();
97161
+ init_setup_write();
97040
97162
 
97041
97163
  // src/cli/plugins.ts
97042
97164
  init_paths();
@@ -97112,7 +97234,7 @@ init_source();
97112
97234
  init_loader();
97113
97235
  init_generator2();
97114
97236
  import { existsSync as existsSync24 } from "fs";
97115
- import { join as join62 } from "path";
97237
+ import { join as join63 } from "path";
97116
97238
  var VALID_AGENTS = ["claude", "codex", "opencode", "cursor", "windsurf", "aider", "gemini"];
97117
97239
  async function generateCommand(options) {
97118
97240
  const workdir = options.dir ?? process.cwd();
@@ -97155,7 +97277,7 @@ async function generateCommand(options) {
97155
97277
  return;
97156
97278
  }
97157
97279
  if (options.package) {
97158
- const packageDir = join62(workdir, options.package);
97280
+ const packageDir = join63(workdir, options.package);
97159
97281
  if (dryRun) {
97160
97282
  console.log(source_default.yellow("\u26A0 Dry run \u2014 no files will be written"));
97161
97283
  }
@@ -97175,8 +97297,8 @@ async function generateCommand(options) {
97175
97297
  process.exit(1);
97176
97298
  return;
97177
97299
  }
97178
- const contextPath = options.context ? join62(workdir, options.context) : join62(workdir, ".nax/context.md");
97179
- const outputDir = options.output ? join62(workdir, options.output) : workdir;
97300
+ const contextPath = options.context ? join63(workdir, options.context) : join63(workdir, ".nax/context.md");
97301
+ const outputDir = options.output ? join63(workdir, options.output) : workdir;
97180
97302
  const autoInject = !options.noAutoInject;
97181
97303
  if (!existsSync24(contextPath)) {
97182
97304
  console.error(source_default.red(`\u2717 Context file not found: ${contextPath}`));
@@ -97282,7 +97404,7 @@ async function generateCommand(options) {
97282
97404
  // src/cli/config-display.ts
97283
97405
  init_loader();
97284
97406
  import { existsSync as existsSync26 } from "fs";
97285
- import { join as join64 } from "path";
97407
+ import { join as join65 } from "path";
97286
97408
 
97287
97409
  // src/cli/config-descriptions.ts
97288
97410
  var FIELD_DESCRIPTIONS = {
@@ -97535,7 +97657,7 @@ function deepEqual(a, b) {
97535
97657
  init_defaults();
97536
97658
  init_loader();
97537
97659
  import { existsSync as existsSync25 } from "fs";
97538
- import { join as join63 } from "path";
97660
+ import { join as join64 } from "path";
97539
97661
  async function loadConfigFile(path19) {
97540
97662
  if (!existsSync25(path19))
97541
97663
  return null;
@@ -97557,7 +97679,7 @@ async function loadProjectConfig() {
97557
97679
  const projectDir = findProjectDir();
97558
97680
  if (!projectDir)
97559
97681
  return null;
97560
- const projectPath = join63(projectDir, "config.json");
97682
+ const projectPath = join64(projectDir, "config.json");
97561
97683
  return await loadConfigFile(projectPath);
97562
97684
  }
97563
97685
 
@@ -97617,7 +97739,7 @@ async function configCommand(config2, options = {}) {
97617
97739
  function determineConfigSources() {
97618
97740
  const globalPath = globalConfigPath();
97619
97741
  const projectDir = findProjectDir();
97620
- const projectPath = projectDir ? join64(projectDir, "config.json") : null;
97742
+ const projectPath = projectDir ? join65(projectDir, "config.json") : null;
97621
97743
  return {
97622
97744
  global: fileExists(globalPath) ? globalPath : null,
97623
97745
  project: projectPath && fileExists(projectPath) ? projectPath : null
@@ -97766,15 +97888,15 @@ init_paths();
97766
97888
  init_profile();
97767
97889
  import { mkdirSync as mkdirSync5 } from "fs";
97768
97890
  import { readdirSync as readdirSync5 } from "fs";
97769
- import { join as join65 } from "path";
97891
+ import { join as join66 } from "path";
97770
97892
  var _profileCLIDeps = {
97771
97893
  env: process.env
97772
97894
  };
97773
97895
  var SENSITIVE_KEY_PATTERN = /key|token|secret|password|credential/i;
97774
97896
  var VAR_PATTERN = /\$[A-Za-z_][A-Za-z0-9_]*/;
97775
97897
  async function profileListCommand(startDir) {
97776
- const globalProfilesDir = join65(globalConfigDir(), "profiles");
97777
- const projectProfilesDir = join65(projectConfigDir(startDir), "profiles");
97898
+ const globalProfilesDir = join66(globalConfigDir(), "profiles");
97899
+ const projectProfilesDir = join66(projectConfigDir(startDir), "profiles");
97778
97900
  const globalProfiles = scanProfileDir(globalProfilesDir);
97779
97901
  const projectProfiles = scanProfileDir(projectProfilesDir);
97780
97902
  const activeProfile = await resolveProfileName({}, _profileCLIDeps.env, startDir);
@@ -97833,7 +97955,7 @@ function maskProfileValues(obj) {
97833
97955
  return result;
97834
97956
  }
97835
97957
  async function profileUseCommand(profileName, startDir) {
97836
- const configPath = join65(projectConfigDir(startDir), "config.json");
97958
+ const configPath = join66(projectConfigDir(startDir), "config.json");
97837
97959
  const configFile = Bun.file(configPath);
97838
97960
  let existing = {};
97839
97961
  if (await configFile.exists()) {
@@ -97852,8 +97974,8 @@ async function profileCurrentCommand(startDir) {
97852
97974
  return resolveProfileName({}, _profileCLIDeps.env, startDir);
97853
97975
  }
97854
97976
  async function profileCreateCommand(profileName, startDir) {
97855
- const profilesDir = join65(projectConfigDir(startDir), "profiles");
97856
- const profilePath = join65(profilesDir, `${profileName}.json`);
97977
+ const profilesDir = join66(projectConfigDir(startDir), "profiles");
97978
+ const profilePath = join66(profilesDir, `${profileName}.json`);
97857
97979
  const profileFile = Bun.file(profilePath);
97858
97980
  if (await profileFile.exists()) {
97859
97981
  throw new Error(`Profile "${profileName}" already exists at ${profilePath}`);
@@ -97975,7 +98097,7 @@ async function contextInspectCommand(options) {
97975
98097
  init_canonical_loader();
97976
98098
  init_errors();
97977
98099
  import { mkdir as mkdir11 } from "fs/promises";
97978
- import { basename as basename12, join as join66 } from "path";
98100
+ import { basename as basename12, join as join67 } from "path";
97979
98101
  var _rulesCLIDeps = {
97980
98102
  readFile: async (path19) => Bun.file(path19).text(),
97981
98103
  writeFile: async (path19, content) => {
@@ -97984,7 +98106,7 @@ var _rulesCLIDeps = {
97984
98106
  fileExists: async (path19) => Bun.file(path19).exists(),
97985
98107
  globInDir: (dir) => {
97986
98108
  try {
97987
- return [...new Bun.Glob("*.md").scanSync({ cwd: dir })].sort().map((f) => join66(dir, f));
98109
+ return [...new Bun.Glob("*.md").scanSync({ cwd: dir })].sort().map((f) => join67(dir, f));
97988
98110
  } catch {
97989
98111
  return [];
97990
98112
  }
@@ -98033,7 +98155,7 @@ ${r.content}`).join(`
98033
98155
  `);
98034
98156
  const shimContent = `${header + body}
98035
98157
  `;
98036
- const shimPath = join66(workdir, shimFileName);
98158
+ const shimPath = join67(workdir, shimFileName);
98037
98159
  if (options.dryRun) {
98038
98160
  console.log(`[dry-run] Would write ${shimPath} (${shimContent.length} bytes)`);
98039
98161
  return;
@@ -98062,14 +98184,14 @@ function neutralizeContent(content) {
98062
98184
  }
98063
98185
  async function collectMigrationSources(workdir) {
98064
98186
  const sources = [];
98065
- const claudeMdPath = join66(workdir, "CLAUDE.md");
98187
+ const claudeMdPath = join67(workdir, "CLAUDE.md");
98066
98188
  if (await _rulesCLIDeps.fileExists(claudeMdPath)) {
98067
98189
  const content = await _rulesCLIDeps.readFile(claudeMdPath);
98068
98190
  if (content.trim()) {
98069
98191
  sources.push({ sourcePath: claudeMdPath, targetFileName: "project-conventions.md", content });
98070
98192
  }
98071
98193
  }
98072
- const rulesDir = join66(workdir, ".claude", "rules");
98194
+ const rulesDir = join67(workdir, ".claude", "rules");
98073
98195
  const ruleFiles = _rulesCLIDeps.globInDir(rulesDir);
98074
98196
  for (const filePath of ruleFiles) {
98075
98197
  try {
@@ -98089,7 +98211,7 @@ async function rulesMigrateCommand(options) {
98089
98211
  console.log("[WARN] No source files found (checked CLAUDE.md and .claude/rules/*.md). Nothing to migrate.");
98090
98212
  return;
98091
98213
  }
98092
- const targetDir = join66(workdir, CANONICAL_RULES_DIR);
98214
+ const targetDir = join67(workdir, CANONICAL_RULES_DIR);
98093
98215
  if (!options.dryRun) {
98094
98216
  try {
98095
98217
  await _rulesCLIDeps.mkdir(targetDir);
@@ -98100,7 +98222,7 @@ async function rulesMigrateCommand(options) {
98100
98222
  let written = 0;
98101
98223
  let skipped = 0;
98102
98224
  for (const { sourcePath, targetFileName, content } of sources) {
98103
- const targetPath = join66(targetDir, targetFileName);
98225
+ const targetPath = join67(targetDir, targetFileName);
98104
98226
  if (!force && !options.dryRun && await _rulesCLIDeps.fileExists(targetPath)) {
98105
98227
  console.log(`[skip] ${targetFileName} already exists (use --force to overwrite)`);
98106
98228
  skipped++;
@@ -98139,7 +98261,7 @@ function collectCanonicalRuleRoots(workdir) {
98139
98261
  const packageRel = normalized.slice(0, idx);
98140
98262
  if (!packageRel)
98141
98263
  continue;
98142
- roots.add(join66(workdir, packageRel));
98264
+ roots.add(join67(workdir, packageRel));
98143
98265
  }
98144
98266
  return [...roots].sort();
98145
98267
  }
@@ -98191,7 +98313,7 @@ init_logger2();
98191
98313
  init_detect2();
98192
98314
  init_workspace();
98193
98315
  init_common();
98194
- import { join as join67 } from "path";
98316
+ import { join as join68 } from "path";
98195
98317
  function resolveEffective(detected, configPatterns) {
98196
98318
  if (configPatterns !== undefined)
98197
98319
  return "config";
@@ -98276,7 +98398,7 @@ async function detectCommand(options) {
98276
98398
  const rootDetected = detectionMap[""] ?? { patterns: [], confidence: "empty", sources: [] };
98277
98399
  const pkgEntries = await Promise.all(packageDirs.map(async (dir) => {
98278
98400
  const det = detectionMap[dir] ?? { patterns: [], confidence: "empty", sources: [] };
98279
- const pkgConfigPath = join67(workdir, ".nax", "mono", dir, "config.json");
98401
+ const pkgConfigPath = join68(workdir, ".nax", "mono", dir, "config.json");
98280
98402
  const pkgRaw = await loadRawConfig(pkgConfigPath);
98281
98403
  const pkgPatterns = deepGet(pkgRaw, TEST_PATTERNS_KEY);
98282
98404
  const effective = Array.isArray(pkgPatterns) ? pkgPatterns : undefined;
@@ -98330,13 +98452,13 @@ async function detectCommand(options) {
98330
98452
  if (rootDetected.confidence === "empty") {
98331
98453
  console.log(source_default.yellow(" root: skipped (empty detection)"));
98332
98454
  } else {
98333
- const rootConfigPath = join67(workdir, ".nax", "config.json");
98455
+ const rootConfigPath = join68(workdir, ".nax", "config.json");
98334
98456
  try {
98335
98457
  const status = await applyToConfig(rootConfigPath, rootDetected.patterns, options.force ?? false);
98336
98458
  if (status === "skipped") {
98337
98459
  console.log(source_default.dim(" root: skipped (testFilePatterns already set; use --force to overwrite)"));
98338
98460
  } else {
98339
- console.log(source_default.green(` root: ${status} \u2192 ${join67(".nax", "config.json")}`));
98461
+ console.log(source_default.green(` root: ${status} \u2192 ${join68(".nax", "config.json")}`));
98340
98462
  }
98341
98463
  } catch (err) {
98342
98464
  console.error(source_default.red(` root: write failed \u2014 ${err.message}`));
@@ -98349,13 +98471,13 @@ async function detectCommand(options) {
98349
98471
  console.log(source_default.dim(` ${dir}: skipped (empty detection)`));
98350
98472
  continue;
98351
98473
  }
98352
- const pkgConfigPath = join67(workdir, ".nax", "mono", dir, "config.json");
98474
+ const pkgConfigPath = join68(workdir, ".nax", "mono", dir, "config.json");
98353
98475
  try {
98354
98476
  const status = await applyToConfig(pkgConfigPath, det.patterns, options.force ?? false);
98355
98477
  if (status === "skipped") {
98356
98478
  console.log(source_default.dim(` ${dir}: skipped (already set)`));
98357
98479
  } else {
98358
- console.log(source_default.green(` ${dir}: ${status} \u2192 ${join67(".nax", "mono", dir, "config.json")}`));
98480
+ console.log(source_default.green(` ${dir}: ${status} \u2192 ${join68(".nax", "mono", dir, "config.json")}`));
98359
98481
  }
98360
98482
  } catch (err) {
98361
98483
  console.error(source_default.red(` ${dir}: write failed \u2014 ${err.message}`));
@@ -98373,19 +98495,19 @@ async function detectCommand(options) {
98373
98495
  // src/commands/logs.ts
98374
98496
  init_common();
98375
98497
  import { existsSync as existsSync28 } from "fs";
98376
- import { join as join70 } from "path";
98498
+ import { join as join71 } from "path";
98377
98499
 
98378
98500
  // src/commands/logs-formatter.ts
98379
98501
  init_source();
98380
98502
  init_formatter();
98381
98503
  import { readdirSync as readdirSync7 } from "fs";
98382
- import { join as join69 } from "path";
98504
+ import { join as join70 } from "path";
98383
98505
 
98384
98506
  // src/commands/logs-reader.ts
98385
98507
  init_paths3();
98386
98508
  import { existsSync as existsSync27, readdirSync as readdirSync6 } from "fs";
98387
98509
  import { readdir as readdir3 } from "fs/promises";
98388
- import { join as join68 } from "path";
98510
+ import { join as join69 } from "path";
98389
98511
  var _logsReaderDeps = {
98390
98512
  getRunsDir
98391
98513
  };
@@ -98399,7 +98521,7 @@ async function resolveRunFileFromRegistry(runId) {
98399
98521
  }
98400
98522
  let matched = null;
98401
98523
  for (const entry of entries) {
98402
- const metaPath = join68(runsDir, entry, "meta.json");
98524
+ const metaPath = join69(runsDir, entry, "meta.json");
98403
98525
  try {
98404
98526
  const meta3 = await Bun.file(metaPath).json();
98405
98527
  if (meta3.runId === runId || meta3.runId.startsWith(runId)) {
@@ -98421,14 +98543,14 @@ async function resolveRunFileFromRegistry(runId) {
98421
98543
  return null;
98422
98544
  }
98423
98545
  const specificFile = files.find((f) => f === `${matched.runId}.jsonl`);
98424
- return join68(matched.eventsDir, specificFile ?? files[0]);
98546
+ return join69(matched.eventsDir, specificFile ?? files[0]);
98425
98547
  }
98426
98548
  async function selectRunFile(runsDir) {
98427
98549
  const files = readdirSync6(runsDir).filter((f) => f.endsWith(".jsonl") && f !== "latest.jsonl").sort().reverse();
98428
98550
  if (files.length === 0) {
98429
98551
  return null;
98430
98552
  }
98431
- return join68(runsDir, files[0]);
98553
+ return join69(runsDir, files[0]);
98432
98554
  }
98433
98555
  async function extractRunSummary(filePath) {
98434
98556
  const file3 = Bun.file(filePath);
@@ -98514,7 +98636,7 @@ Runs:
98514
98636
  console.log(source_default.gray(" Timestamp Stories Duration Cost Status"));
98515
98637
  console.log(source_default.gray(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
98516
98638
  for (const file3 of files) {
98517
- const filePath = join69(runsDir, file3);
98639
+ const filePath = join70(runsDir, file3);
98518
98640
  const summary = await extractRunSummary(filePath);
98519
98641
  const timestamp = file3.replace(".jsonl", "");
98520
98642
  const stories = summary ? `${summary.passed}/${summary.total}` : "?/?";
@@ -98628,7 +98750,7 @@ async function logsCommand(options) {
98628
98750
  return;
98629
98751
  }
98630
98752
  const resolved = resolveProject({ dir: options.dir });
98631
- const naxDir = join70(resolved.projectDir, ".nax");
98753
+ const naxDir = join71(resolved.projectDir, ".nax");
98632
98754
  const configPath = resolved.configPath;
98633
98755
  const configFile = Bun.file(configPath);
98634
98756
  const config2 = await configFile.json();
@@ -98636,8 +98758,8 @@ async function logsCommand(options) {
98636
98758
  if (!featureName) {
98637
98759
  throw new Error("No feature specified in config.json");
98638
98760
  }
98639
- const featureDir = join70(naxDir, "features", featureName);
98640
- const runsDir = join70(featureDir, "runs");
98761
+ const featureDir = join71(naxDir, "features", featureName);
98762
+ const runsDir = join71(featureDir, "runs");
98641
98763
  if (!existsSync28(runsDir)) {
98642
98764
  throw new Error(`No runs directory found for feature: ${featureName}`);
98643
98765
  }
@@ -98663,7 +98785,7 @@ init_prd();
98663
98785
  init_precheck();
98664
98786
  init_common();
98665
98787
  import { existsSync as existsSync29 } from "fs";
98666
- import { join as join71 } from "path";
98788
+ import { join as join72 } from "path";
98667
98789
  async function precheckCommand(options) {
98668
98790
  const resolved = resolveProject({
98669
98791
  dir: options.dir,
@@ -98685,9 +98807,9 @@ async function precheckCommand(options) {
98685
98807
  process.exit(1);
98686
98808
  }
98687
98809
  }
98688
- const naxDir = join71(resolved.projectDir, ".nax");
98689
- const featureDir = join71(naxDir, "features", featureName);
98690
- const prdPath = join71(featureDir, "prd.json");
98810
+ const naxDir = join72(resolved.projectDir, ".nax");
98811
+ const featureDir = join72(naxDir, "features", featureName);
98812
+ const prdPath = join72(featureDir, "prd.json");
98691
98813
  if (!existsSync29(featureDir)) {
98692
98814
  console.error(source_default.red(`Feature not found: ${featureName}`));
98693
98815
  process.exit(1);
@@ -98710,7 +98832,7 @@ async function precheckCommand(options) {
98710
98832
  init_source();
98711
98833
  init_paths3();
98712
98834
  import { readdir as readdir4 } from "fs/promises";
98713
- import { join as join72 } from "path";
98835
+ import { join as join73 } from "path";
98714
98836
  var DEFAULT_LIMIT = 20;
98715
98837
  var _runsCmdDeps = {
98716
98838
  getRunsDir
@@ -98765,7 +98887,7 @@ async function runsCommand(options = {}) {
98765
98887
  }
98766
98888
  const rows = [];
98767
98889
  for (const entry of entries) {
98768
- const metaPath = join72(runsDir, entry, "meta.json");
98890
+ const metaPath = join73(runsDir, entry, "meta.json");
98769
98891
  let meta3;
98770
98892
  try {
98771
98893
  meta3 = await Bun.file(metaPath).json();
@@ -98842,7 +98964,7 @@ async function runsCommand(options = {}) {
98842
98964
 
98843
98965
  // src/commands/unlock.ts
98844
98966
  init_source();
98845
- import { join as join73 } from "path";
98967
+ import { join as join74 } from "path";
98846
98968
  function isProcessAlive2(pid) {
98847
98969
  try {
98848
98970
  process.kill(pid, 0);
@@ -98857,7 +98979,7 @@ function formatLockAge(ageMs) {
98857
98979
  }
98858
98980
  async function unlockCommand(options) {
98859
98981
  const workdir = options.dir ?? process.cwd();
98860
- const lockPath = join73(workdir, "nax.lock");
98982
+ const lockPath = join74(workdir, "nax.lock");
98861
98983
  const lockFile = Bun.file(lockPath);
98862
98984
  const exists = await lockFile.exists();
98863
98985
  if (!exists) {
@@ -107295,7 +107417,7 @@ Next: nax generate --package ${options.package}`));
107295
107417
  }
107296
107418
  return;
107297
107419
  }
107298
- const naxDir = join87(workdir, ".nax");
107420
+ const naxDir = join88(workdir, ".nax");
107299
107421
  if (existsSync35(naxDir) && !options.force) {
107300
107422
  console.log(source_default.yellow("nax already initialized. Use --force to overwrite."));
107301
107423
  return;
@@ -107324,11 +107446,11 @@ Next: nax generate --package ${options.package}`));
107324
107446
  }
107325
107447
  }
107326
107448
  }
107327
- mkdirSync7(join87(naxDir, "features"), { recursive: true });
107328
- mkdirSync7(join87(naxDir, "hooks"), { recursive: true });
107449
+ mkdirSync7(join88(naxDir, "features"), { recursive: true });
107450
+ mkdirSync7(join88(naxDir, "hooks"), { recursive: true });
107329
107451
  const initConfig = options.name ? { ...DEFAULT_CONFIG, name: options.name } : DEFAULT_CONFIG;
107330
- await Bun.write(join87(naxDir, "config.json"), JSON.stringify(initConfig, null, 2));
107331
- await Bun.write(join87(naxDir, "hooks.json"), JSON.stringify({
107452
+ await Bun.write(join88(naxDir, "config.json"), JSON.stringify(initConfig, null, 2));
107453
+ await Bun.write(join88(naxDir, "hooks.json"), JSON.stringify({
107332
107454
  hooks: {
107333
107455
  "on-start": { command: 'echo "nax started: $NAX_FEATURE"', enabled: false },
107334
107456
  "on-complete": { command: 'echo "nax complete: $NAX_FEATURE"', enabled: false },
@@ -107336,12 +107458,12 @@ Next: nax generate --package ${options.package}`));
107336
107458
  "on-error": { command: 'echo "nax error: $NAX_REASON"', enabled: false }
107337
107459
  }
107338
107460
  }, null, 2));
107339
- await Bun.write(join87(naxDir, ".gitignore"), `# nax temp files
107461
+ await Bun.write(join88(naxDir, ".gitignore"), `# nax temp files
107340
107462
  *.tmp
107341
107463
  .paused.json
107342
107464
  .nax-verifier-verdict.json
107343
107465
  `);
107344
- await Bun.write(join87(naxDir, "context.md"), `# Project Context
107466
+ await Bun.write(join88(naxDir, "context.md"), `# Project Context
107345
107467
 
107346
107468
  This document defines coding standards, architectural decisions, and forbidden patterns for this project.
107347
107469
  Run \`nax generate\` to regenerate agent config files (CLAUDE.md, AGENTS.md, .cursorrules, etc.) from this file.
@@ -107483,7 +107605,7 @@ program2.command("run").description("Run the orchestration loop for a feature").
107483
107605
  const cliOverrides = {};
107484
107606
  const cliProfiles = options.profile ?? [];
107485
107607
  const profileOverride = naxDir ? await resolveRunProfileOverride({
107486
- prdPath: join87(naxDir, "features", options.feature, "prd.json"),
107608
+ prdPath: join88(naxDir, "features", options.feature, "prd.json"),
107487
107609
  projectRoot: workdir,
107488
107610
  cliProfile: cliProfiles,
107489
107611
  envProfile: process.env.NAX_PROFILE
@@ -107496,8 +107618,8 @@ program2.command("run").description("Run the orchestration loop for a feature").
107496
107618
  console.error(source_default.red("nax not initialized. Run: nax init"));
107497
107619
  process.exit(1);
107498
107620
  }
107499
- const featureDir = join87(naxDir, "features", options.feature);
107500
- const prdPath = join87(featureDir, "prd.json");
107621
+ const featureDir = join88(naxDir, "features", options.feature);
107622
+ const prdPath = join88(featureDir, "prd.json");
107501
107623
  if (options.plan && options.from) {
107502
107624
  if (existsSync35(prdPath) && !options.force) {
107503
107625
  console.error(source_default.red(`Error: prd.json already exists for feature "${options.feature}".`));
@@ -107519,10 +107641,10 @@ program2.command("run").description("Run the orchestration loop for a feature").
107519
107641
  }
107520
107642
  }
107521
107643
  try {
107522
- const planLogDir = join87(featureDir, "plan");
107644
+ const planLogDir = join88(featureDir, "plan");
107523
107645
  mkdirSync7(planLogDir, { recursive: true });
107524
107646
  const planLogId = new Date().toISOString().replace(/:/g, "-").replace(/\..+/, "");
107525
- const planLogPath = join87(planLogDir, `${planLogId}.jsonl`);
107647
+ const planLogPath = join88(planLogDir, `${planLogId}.jsonl`);
107526
107648
  initLogger({ level: "info", filePath: planLogPath, useChalk: false, headless: true });
107527
107649
  console.log(source_default.dim(` [Plan log: ${planLogPath}]`));
107528
107650
  console.log(source_default.dim(" [Planning phase: generating PRD from spec]"));
@@ -107568,10 +107690,10 @@ program2.command("run").description("Run the orchestration loop for a feature").
107568
107690
  resetLogger();
107569
107691
  const projectKey = config2.name?.trim() || basename16(workdir);
107570
107692
  const outputDir = projectOutputDir(projectKey, config2.outputDir);
107571
- const runsDir = join87(outputDir, "features", options.feature, "runs");
107693
+ const runsDir = join88(outputDir, "features", options.feature, "runs");
107572
107694
  mkdirSync7(runsDir, { recursive: true });
107573
107695
  const runId = new Date().toISOString().replace(/:/g, "-").replace(/\..+/, "");
107574
- const logFilePath = join87(runsDir, `${runId}.jsonl`);
107696
+ const logFilePath = join88(runsDir, `${runId}.jsonl`);
107575
107697
  const isTTY = process.stdout.isTTY ?? false;
107576
107698
  const headlessFlag = options.headless ?? false;
107577
107699
  const headlessEnv = process.env.NAX_HEADLESS === "1";
@@ -107589,7 +107711,7 @@ program2.command("run").description("Run the orchestration loop for a feature").
107589
107711
  config2.agent.default = options.agent;
107590
107712
  }
107591
107713
  config2.execution.maxIterations = Number.parseInt(options.maxIterations, 10);
107592
- const globalNaxDir = join87(homedir3(), ".nax");
107714
+ const globalNaxDir = join88(homedir3(), ".nax");
107593
107715
  const hooks = await loadHooksConfig(naxDir, globalNaxDir);
107594
107716
  const eventEmitter = new PipelineEventEmitter;
107595
107717
  const agentStreamEvents = useHeadless ? undefined : new AgentStreamEventBus;
@@ -107609,12 +107731,12 @@ program2.command("run").description("Run the orchestration loop for a feature").
107609
107731
  events: eventEmitter,
107610
107732
  ptyOptions: null,
107611
107733
  agentStreamEvents,
107612
- queueFilePath: join87(workdir, ".queue.txt")
107734
+ queueFilePath: join88(workdir, ".queue.txt")
107613
107735
  });
107614
107736
  } else {
107615
107737
  console.log(source_default.dim(" [Headless mode \u2014 pipe output]"));
107616
107738
  }
107617
- const statusFilePath = join87(outputDir, "status.json");
107739
+ const statusFilePath = join88(outputDir, "status.json");
107618
107740
  let parallel;
107619
107741
  if (options.parallel !== undefined) {
107620
107742
  parallel = Number.parseInt(options.parallel, 10);
@@ -107641,7 +107763,7 @@ program2.command("run").description("Run the orchestration loop for a feature").
107641
107763
  skipPrecheck: options.skipPrecheck ?? false,
107642
107764
  agentStreamEvents
107643
107765
  });
107644
- const latestSymlink = join87(runsDir, "latest.jsonl");
107766
+ const latestSymlink = join88(runsDir, "latest.jsonl");
107645
107767
  try {
107646
107768
  if (existsSync35(latestSymlink)) {
107647
107769
  Bun.spawnSync(["rm", latestSymlink]);
@@ -107702,9 +107824,9 @@ features.command("create <name>").description("Create a new feature").option("-d
107702
107824
  console.error(source_default.red("nax not initialized. Run: nax init"));
107703
107825
  process.exit(1);
107704
107826
  }
107705
- const featureDir = join87(naxDir, "features", name);
107827
+ const featureDir = join88(naxDir, "features", name);
107706
107828
  mkdirSync7(featureDir, { recursive: true });
107707
- await Bun.write(join87(featureDir, "spec.md"), `# Feature: ${name}
107829
+ await Bun.write(join88(featureDir, "spec.md"), `# Feature: ${name}
107708
107830
 
107709
107831
  ## Overview
107710
107832
 
@@ -107737,7 +107859,7 @@ features.command("create <name>").description("Create a new feature").option("-d
107737
107859
 
107738
107860
  <!-- What this feature explicitly does NOT cover. -->
107739
107861
  `);
107740
- await Bun.write(join87(featureDir, "progress.txt"), `# Progress: ${name}
107862
+ await Bun.write(join88(featureDir, "progress.txt"), `# Progress: ${name}
107741
107863
 
107742
107864
  Created: ${new Date().toISOString()}
107743
107865
 
@@ -107763,7 +107885,7 @@ features.command("list").description("List all features").option("-d, --dir <pat
107763
107885
  console.error(source_default.red("nax not initialized."));
107764
107886
  process.exit(1);
107765
107887
  }
107766
- const featuresDir = join87(naxDir, "features");
107888
+ const featuresDir = join88(naxDir, "features");
107767
107889
  if (!existsSync35(featuresDir)) {
107768
107890
  console.log(source_default.dim("No features yet."));
107769
107891
  return;
@@ -107778,7 +107900,7 @@ features.command("list").description("List all features").option("-d, --dir <pat
107778
107900
  Features:
107779
107901
  `));
107780
107902
  for (const name of entries) {
107781
- const prdPath = join87(featuresDir, name, "prd.json");
107903
+ const prdPath = join88(featuresDir, name, "prd.json");
107782
107904
  if (existsSync35(prdPath)) {
107783
107905
  const prd = await loadPRD(prdPath);
107784
107906
  const c = countStories(prd);
@@ -107814,10 +107936,10 @@ Use: nax plan -f <feature> --from <spec>`));
107814
107936
  cliOverrides.profile = cliProfiles;
107815
107937
  }
107816
107938
  const config2 = await loadConfig(workdir, cliOverrides);
107817
- const featureLogDir = join87(naxDir, "features", options.feature, "plan");
107939
+ const featureLogDir = join88(naxDir, "features", options.feature, "plan");
107818
107940
  mkdirSync7(featureLogDir, { recursive: true });
107819
107941
  const planLogId = new Date().toISOString().replace(/:/g, "-").replace(/\..+/, "");
107820
- const planLogPath = join87(featureLogDir, `${planLogId}.jsonl`);
107942
+ const planLogPath = join88(featureLogDir, `${planLogId}.jsonl`);
107821
107943
  initLogger({ level: "info", filePath: planLogPath, useChalk: false, headless: true });
107822
107944
  console.log(source_default.dim(` [Plan log: ${planLogPath}]`));
107823
107945
  try {