@nathapp/nax 0.69.0 → 0.69.1

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 +1028 -274
  2. package/package.json +1 -1
package/dist/nax.js CHANGED
@@ -17221,6 +17221,12 @@ var init_schemas_review = __esm(() => {
17221
17221
  substantiation: exports_external.object({
17222
17222
  requote: exports_external.boolean().default(true),
17223
17223
  maxRequotes: exports_external.number().int().min(0).default(5)
17224
+ }).optional(),
17225
+ nonBlockingFix: exports_external.object({
17226
+ enabled: exports_external.boolean().default(false),
17227
+ scope: exports_external.enum(["source", "both"]).default("both"),
17228
+ regressionAttempts: exports_external.number().int().min(0).default(1),
17229
+ verifierGuard: exports_external.boolean().default(true)
17224
17230
  }).optional()
17225
17231
  });
17226
17232
  ReviewConfigSchema = exports_external.object({
@@ -19004,6 +19010,106 @@ var init_loader = __esm(() => {
19004
19010
  init_schema();
19005
19011
  _rootConfigCache = new Map;
19006
19012
  });
19013
+
19014
+ // src/config/validate.ts
19015
+ function validateConfig(config2) {
19016
+ const errors3 = [];
19017
+ if (config2.version !== 1) {
19018
+ errors3.push(`Invalid version: expected 1, got ${config2.version}`);
19019
+ }
19020
+ const requiredTiers = ["fast", "balanced", "powerful"];
19021
+ if (!config2.models) {
19022
+ errors3.push("models mapping is required");
19023
+ } else {
19024
+ const defaultAgent = config2.agent?.default ?? "claude";
19025
+ const agentModels = config2.models[defaultAgent];
19026
+ if (!agentModels) {
19027
+ errors3.push(`models.${defaultAgent} is required (default agent has no model map)`);
19028
+ } else {
19029
+ for (const tier of requiredTiers) {
19030
+ const entry = agentModels[tier];
19031
+ if (!entry) {
19032
+ errors3.push(`models.${defaultAgent}.${tier} is required`);
19033
+ } else if (typeof entry === "string") {
19034
+ if (entry.trim() === "") {
19035
+ errors3.push(`models.${defaultAgent}.${tier} must be a non-empty model identifier`);
19036
+ }
19037
+ } else {
19038
+ if (!entry.provider || entry.provider.trim() === "") {
19039
+ errors3.push(`models.${defaultAgent}.${tier}.provider must be non-empty`);
19040
+ }
19041
+ if (!entry.model || entry.model.trim() === "") {
19042
+ errors3.push(`models.${defaultAgent}.${tier}.model must be non-empty`);
19043
+ }
19044
+ }
19045
+ }
19046
+ }
19047
+ }
19048
+ if (config2.execution.maxIterations <= 0) {
19049
+ errors3.push(`maxIterations must be > 0, got ${config2.execution.maxIterations}`);
19050
+ }
19051
+ if (config2.execution.costLimit <= 0) {
19052
+ errors3.push(`costLimit must be > 0, got ${config2.execution.costLimit}`);
19053
+ }
19054
+ if (config2.execution.sessionTimeoutSeconds <= 0) {
19055
+ errors3.push(`sessionTimeoutSeconds must be > 0, got ${config2.execution.sessionTimeoutSeconds}`);
19056
+ }
19057
+ const agentDefault = config2.agent?.default;
19058
+ if (!agentDefault || agentDefault.trim() === "") {
19059
+ errors3.push("agent.default must be non-empty");
19060
+ }
19061
+ if (!config2.autoMode.escalation.tierOrder || config2.autoMode.escalation.tierOrder.length === 0) {
19062
+ errors3.push("escalation.tierOrder must have at least one tier");
19063
+ } else {
19064
+ for (const tc of config2.autoMode.escalation.tierOrder) {
19065
+ if (tc.attempts < 1 || tc.attempts > 20) {
19066
+ errors3.push(`escalation.tierOrder: tier "${tc.tier}" attempts must be 1-20, got ${tc.attempts}`);
19067
+ }
19068
+ }
19069
+ }
19070
+ if (config2.models && config2.agent?.fallback?.map) {
19071
+ const modelKeys = Object.keys(config2.models);
19072
+ const fallbackAgents = new Set;
19073
+ for (const [primary, candidates] of Object.entries(config2.agent.fallback.map)) {
19074
+ fallbackAgents.add(primary);
19075
+ for (const c of candidates)
19076
+ fallbackAgents.add(c);
19077
+ }
19078
+ for (const agent of fallbackAgents) {
19079
+ if (!modelKeys.includes(agent)) {
19080
+ errors3.push(`agent.fallback.map: agent "${agent}" is not a key in models (available: ${modelKeys.join(", ")})`);
19081
+ } else {
19082
+ for (const tier of requiredTiers) {
19083
+ if (!config2.models[agent]?.[tier]) {
19084
+ errors3.push(`models.${agent}.${tier} is required (fallback agent "${agent}" in agent.fallback.map)`);
19085
+ }
19086
+ }
19087
+ }
19088
+ }
19089
+ }
19090
+ if (config2.models && config2.autoMode?.escalation?.tierOrder) {
19091
+ const modelKeys = Object.keys(config2.models);
19092
+ for (const tc of config2.autoMode.escalation.tierOrder) {
19093
+ if (tc.agent !== undefined && !modelKeys.includes(tc.agent)) {
19094
+ errors3.push(`autoMode.escalation.tierOrder: tier "${tc.tier}" agent "${tc.agent}" is not a key in models (available: ${modelKeys.join(", ")})`);
19095
+ }
19096
+ }
19097
+ }
19098
+ const defaultAgentKey = config2.agent?.default ?? "claude";
19099
+ const configuredTiers = Object.keys(config2.models[defaultAgentKey] ?? {});
19100
+ const complexities = ["simple", "medium", "complex", "expert"];
19101
+ for (const complexity of complexities) {
19102
+ const tier = config2.autoMode.complexityRouting[complexity];
19103
+ if (!configuredTiers.includes(tier)) {
19104
+ errors3.push(`complexityRouting.${complexity} must be one of: ${configuredTiers.join(", ")} (got '${tier}')`);
19105
+ }
19106
+ }
19107
+ return {
19108
+ valid: errors3.length === 0,
19109
+ errors: errors3
19110
+ };
19111
+ }
19112
+
19007
19113
  // src/config/selector.ts
19008
19114
  function pickSelector(name, ...keys) {
19009
19115
  return {
@@ -19286,6 +19392,73 @@ var init_test_strategy = __esm(() => {
19286
19392
  });
19287
19393
 
19288
19394
  // src/config/index.ts
19395
+ var exports_config = {};
19396
+ __export(exports_config, {
19397
+ verifyConfigSelector: () => verifyConfigSelector,
19398
+ validateFilePath: () => validateFilePath,
19399
+ validateDirectory: () => validateDirectory,
19400
+ validateConfig: () => validateConfig,
19401
+ testPatternConfigSelector: () => testPatternConfigSelector,
19402
+ tddConfigSelector: () => tddConfigSelector,
19403
+ routingConfigSelector: () => routingConfigSelector,
19404
+ reviewConfigSelector: () => reviewConfigSelector,
19405
+ resolveTestStrategy: () => resolveTestStrategy,
19406
+ resolveProfileName: () => resolveProfileName,
19407
+ resolveModelForAgent: () => resolveModelForAgent,
19408
+ resolveModel: () => resolveModel,
19409
+ resolveConfiguredModel: () => resolveConfiguredModel,
19410
+ reshapeSelector: () => reshapeSelector,
19411
+ rectifyConfigSelector: () => rectifyConfigSelector,
19412
+ rectificationGateConfigSelector: () => rectificationGateConfigSelector,
19413
+ qualityConfigSelector: () => qualityConfigSelector,
19414
+ promptLoaderConfigSelector: () => promptLoaderConfigSelector,
19415
+ projectConfigDir: () => projectConfigDir,
19416
+ precheckConfigSelector: () => precheckConfigSelector,
19417
+ planConfigSelector: () => planConfigSelector,
19418
+ pickSelector: () => pickSelector,
19419
+ loadProfileEnv: () => loadProfileEnv,
19420
+ loadProfile: () => loadProfile,
19421
+ loadConfigForWorkdir: () => loadConfigForWorkdir,
19422
+ loadConfig: () => loadConfig,
19423
+ llmRoutingConfigSelector: () => llmRoutingConfigSelector,
19424
+ listProfiles: () => listProfiles,
19425
+ isWithinDirectory: () => isWithinDirectory,
19426
+ isThreeSessionStrategy: () => isThreeSessionStrategy,
19427
+ isSingleSessionTestOwningStrategy: () => isSingleSessionTestOwningStrategy,
19428
+ interactionConfigSelector: () => interactionConfigSelector,
19429
+ globalConfigPath: () => globalConfigPath,
19430
+ globalConfigDir: () => globalConfigDir,
19431
+ getAcQualityRules: () => getAcQualityRules,
19432
+ findProjectDir: () => findProjectDir,
19433
+ executionGatesConfigSelector: () => executionGatesConfigSelector,
19434
+ deepMergeConfig: () => deepMergeConfig,
19435
+ decomposeConfigSelector: () => decomposeConfigSelector,
19436
+ debateConfigSelector: () => debateConfigSelector,
19437
+ createConfigLoader: () => createConfigLoader,
19438
+ contextToolRuntimeConfigSelector: () => contextToolRuntimeConfigSelector,
19439
+ autofixConfigSelector: () => autofixConfigSelector,
19440
+ agentManagerConfigSelector: () => agentManagerConfigSelector,
19441
+ acceptanceGenConfigSelector: () => acceptanceGenConfigSelector,
19442
+ acceptanceFixConfigSelector: () => acceptanceFixConfigSelector,
19443
+ acceptanceConfigSelector: () => acceptanceConfigSelector,
19444
+ VALID_TEST_STRATEGIES: () => VALID_TEST_STRATEGIES,
19445
+ THREE_SESSION_STRATEGIES: () => THREE_SESSION_STRATEGIES,
19446
+ TEST_STRATEGY_GUIDE: () => TEST_STRATEGY_GUIDE,
19447
+ SPEC_ANCHOR_RULES: () => SPEC_ANCHOR_RULES,
19448
+ SINGLE_SESSION_TEST_OWNING_STRATEGIES: () => SINGLE_SESSION_TEST_OWNING_STRATEGIES,
19449
+ PlanConfigSchema: () => PlanConfigSchema,
19450
+ NaxConfigSchema: () => NaxConfigSchema,
19451
+ ModelTierSchema: () => ModelTierSchema,
19452
+ MAX_DIRECTORY_DEPTH: () => MAX_DIRECTORY_DEPTH,
19453
+ GROUPING_RULES: () => GROUPING_RULES,
19454
+ DebateConfigSchema: () => DebateConfigSchema,
19455
+ DESCRIPTION_QUALITY_RULES: () => DESCRIPTION_QUALITY_RULES,
19456
+ DEFAULT_CONFIG: () => DEFAULT_CONFIG,
19457
+ ConfiguredModelSchema: () => ConfiguredModelSchema,
19458
+ COMPLEXITY_GUIDE: () => COMPLEXITY_GUIDE,
19459
+ AcceptanceConfigSchema: () => AcceptanceConfigSchema,
19460
+ AC_QUALITY_RULES: () => AC_QUALITY_RULES
19461
+ });
19289
19462
  var init_config = __esm(() => {
19290
19463
  init_schema();
19291
19464
  init_schemas_model();
@@ -32641,12 +32814,14 @@ var init_adversarial_review = __esm(() => {
32641
32814
  });
32642
32815
  const { accepted, dropped } = filterByAcQuote(substantiated, input.story.acceptanceCriteria);
32643
32816
  const blocking = accepted.filter((f) => isBlockingSeverity(f.severity, threshold));
32817
+ const advisory = accepted.filter((f) => !isBlockingSeverity(f.severity, threshold));
32644
32818
  const passed = parsed.passed && blocking.length === 0;
32645
32819
  return {
32646
32820
  ...parsed,
32647
32821
  passed,
32648
32822
  findings: accepted,
32649
32823
  normalizedFindings: toAdversarialReviewFindings(blocking),
32824
+ advisoryFindings: toAdversarialReviewFindings(advisory),
32650
32825
  acDropped: dropped
32651
32826
  };
32652
32827
  }
@@ -38840,6 +39015,159 @@ async function validateMockStructureFiles(declarations, resolvedTestPatterns, pa
38840
39015
  var defaultFileExists = (p) => Bun.file(p).exists();
38841
39016
  var init_validate_mock_structure_files = () => {};
38842
39017
 
39018
+ // src/prompts/builders/setup-builder.ts
39019
+ function formatPackageFacts(pkg) {
39020
+ const lines = [` Package: ${pkg.relativeDir || "(root)"}`];
39021
+ if (pkg.testFramework)
39022
+ lines.push(` Test framework: ${pkg.testFramework}`);
39023
+ if (pkg.testFilePatterns.length > 0) {
39024
+ lines.push(` Test patterns: ${pkg.testFilePatterns.slice(0, 4).join(", ")}`);
39025
+ }
39026
+ if (pkg.missingScripts.length > 0) {
39027
+ lines.push(` Missing scripts: ${pkg.missingScripts.join(", ")}`);
39028
+ } else {
39029
+ lines.push(" Missing scripts: (none \u2014 all canonical scripts present)");
39030
+ }
39031
+ return lines.join(`
39032
+ `);
39033
+ }
39034
+
39035
+ class SetupPromptBuilder {
39036
+ build(analysis) {
39037
+ const isMonoRepo = analysis.shape === "mono";
39038
+ const packageFacts = analysis.packages.map(formatPackageFacts).join(`
39039
+
39040
+ `);
39041
+ return {
39042
+ role: {
39043
+ id: "setup-role",
39044
+ content: "You are an expert nax configuration generator. Generate a valid nax config JSON based on the repository analysis provided. Only reference scripts that actually exist \u2014 any script listed under 'Missing scripts' must NOT appear in quality.commands.",
39045
+ overridable: false
39046
+ },
39047
+ task: {
39048
+ id: "setup-task",
39049
+ content: [
39050
+ `Generate a nax configuration for this ${isMonoRepo ? "monorepo" : "single-package"} repository.`,
39051
+ "",
39052
+ "Repository facts:",
39053
+ `- Shape: ${analysis.shape}`,
39054
+ `- Package manager: ${analysis.pmRunPrefix}`,
39055
+ `- DLX runner: ${analysis.pmDlx}`,
39056
+ `- Orchestrator: ${analysis.orchestrator}`,
39057
+ "",
39058
+ "Per-package facts:",
39059
+ packageFacts,
39060
+ "",
39061
+ "IMPORTANT: A script listed under 'Missing scripts' does NOT exist in that package's package.json.",
39062
+ "Do NOT include commands for missing scripts in quality.commands.",
39063
+ "",
39064
+ isMonoRepo ? 'Respond with a JSON code block: { "config": <root NaxConfig>, "monoConfigs": [{ "relativeDir": "<pkg>", "config": <partial NaxConfig> }] }' : 'Respond with a JSON code block: { "config": <NaxConfig> }'
39065
+ ].join(`
39066
+ `),
39067
+ overridable: false
39068
+ }
39069
+ };
39070
+ }
39071
+ }
39072
+
39073
+ // src/operations/setup-generate.ts
39074
+ function throwSetupPlanError(message) {
39075
+ throw new NaxError(`[setup-generate] ${message}`, "SETUP_PLAN_INVALID");
39076
+ }
39077
+ function validateSetupOutput(parsed) {
39078
+ const config2 = parsed?.config ?? parsed;
39079
+ return NaxConfigSchema.safeParse(config2).success;
39080
+ }
39081
+ function crossCheckCommands(config2, analysis) {
39082
+ const rootPkg = analysis.packages.find((p) => p.relativeDir === "") ?? analysis.packages[0];
39083
+ const missing = new Set(rootPkg?.missingScripts ?? []);
39084
+ if (missing.size === 0)
39085
+ return { config: config2, gaps: [] };
39086
+ const quality = config2.quality;
39087
+ if (!quality?.commands)
39088
+ return { config: config2, gaps: [] };
39089
+ const gaps = [];
39090
+ const commands = {};
39091
+ for (const [key, value] of Object.entries(quality.commands)) {
39092
+ if (missing.has(key)) {
39093
+ gaps.push(`Script "${key}" in quality.commands.${key} is missing from package.json`);
39094
+ } else {
39095
+ commands[key] = value;
39096
+ }
39097
+ }
39098
+ if (gaps.length === 0)
39099
+ return { config: config2, gaps: [] };
39100
+ return { config: { ...config2, quality: { ...quality, commands } }, gaps };
39101
+ }
39102
+ function buildMonoConfigs(parsed, analysis) {
39103
+ if (analysis.shape !== "mono")
39104
+ return [];
39105
+ const rawMonoConfigs = parsed.monoConfigs ?? [];
39106
+ return analysis.packages.map((pkg) => {
39107
+ const rawMono = rawMonoConfigs.find((m) => m.relativeDir === pkg.relativeDir);
39108
+ const validated = rawMono ? NaxConfigSchema.safeParse(rawMono.config) : undefined;
39109
+ if (rawMono && !validated?.success) {
39110
+ getLogger().warn("setup-generate", "Per-package config failed schema validation \u2014 using empty config", {
39111
+ storyId: "setup",
39112
+ relativeDir: pkg.relativeDir
39113
+ });
39114
+ }
39115
+ const config2 = (validated?.success ? validated.data : undefined) ?? {};
39116
+ return { relativeDir: pkg.relativeDir, config: config2 };
39117
+ });
39118
+ }
39119
+ var MAX_SETUP_LLM_ATTEMPTS = 2, SetupPlanError, setupRetryStrategy, setupGenerateOp;
39120
+ var init_setup_generate = __esm(() => {
39121
+ init_retry();
39122
+ init_schemas3();
39123
+ init_errors();
39124
+ init_logger2();
39125
+ SetupPlanError = class SetupPlanError extends ParseValidationError {
39126
+ code = "SETUP_PLAN_INVALID";
39127
+ };
39128
+ setupRetryStrategy = makeParseRetryStrategy({
39129
+ reviewerKind: "setup-generate",
39130
+ maxAttempts: MAX_SETUP_LLM_ATTEMPTS,
39131
+ validate: validateSetupOutput,
39132
+ prompts: {
39133
+ invalid: () => "The response was not valid JSON or failed schema validation. Please respond with a valid JSON object.",
39134
+ truncated: () => "The response was truncated. Please provide the complete JSON config."
39135
+ },
39136
+ exhaustedFallback: () => {
39137
+ throwSetupPlanError("LLM failed to generate a valid setup plan after exhausting retries");
39138
+ }
39139
+ });
39140
+ setupGenerateOp = {
39141
+ kind: "run",
39142
+ name: "setup-generate",
39143
+ stage: "setup",
39144
+ session: { role: "setup", lifetime: "fresh" },
39145
+ noFallback: true,
39146
+ config: ["quality"],
39147
+ retry: setupRetryStrategy,
39148
+ build(analysis, _ctx) {
39149
+ return new SetupPromptBuilder().build(analysis);
39150
+ },
39151
+ parse(output, analysis, _ctx) {
39152
+ let parsedRaw;
39153
+ try {
39154
+ parsedRaw = parseLLMJson(output);
39155
+ } catch {
39156
+ throw new SetupPlanError("Failed to parse LLM output as JSON");
39157
+ }
39158
+ const parsedObj = parsedRaw;
39159
+ const rawConfig = parsedObj?.config ?? parsedRaw;
39160
+ const result = NaxConfigSchema.safeParse(rawConfig);
39161
+ if (!result.success) {
39162
+ throw new SetupPlanError(`Config failed NaxConfigSchema: ${result.error.message}`);
39163
+ }
39164
+ const { config: config2, gaps } = crossCheckCommands(result.data, analysis);
39165
+ const monoConfigs = buildMonoConfigs(parsedObj ?? { config: rawConfig }, analysis);
39166
+ return { config: config2, monoConfigs, gaps };
39167
+ }
39168
+ };
39169
+ });
39170
+
38843
39171
  // src/operations/declaration-sink.ts
38844
39172
  function makeDeclarationSink() {
38845
39173
  return { testEdits: [], mockHandoffs: [] };
@@ -39441,6 +39769,7 @@ var init_operations = __esm(() => {
39441
39769
  init_autofix_test_writer_strategy();
39442
39770
  init_apply_test_edit_declarations();
39443
39771
  init_validate_mock_structure_files();
39772
+ init_setup_generate();
39444
39773
  init__finding_to_check();
39445
39774
  init_mechanical_lintfix_strategy();
39446
39775
  init_mechanical_formatfix_strategy();
@@ -44655,7 +44984,8 @@ var init_session_role = __esm(() => {
44655
44984
  "fix-gen",
44656
44985
  "auto",
44657
44986
  "synthesis",
44658
- "judge"
44987
+ "judge",
44988
+ "setup"
44659
44989
  ];
44660
44990
  });
44661
44991
 
@@ -53532,6 +53862,108 @@ var init_context2 = __esm(() => {
53532
53862
  };
53533
53863
  });
53534
53864
 
53865
+ // src/tdd/rollback.ts
53866
+ async function rollbackToRef(workdir, ref) {
53867
+ const logger = getLogger();
53868
+ logger.warn("tdd", "Rolling back git changes", { ref });
53869
+ const resetProc = _rollbackDeps.spawn(["git", "reset", "--hard", ref], {
53870
+ cwd: workdir,
53871
+ stdout: "pipe",
53872
+ stderr: "pipe"
53873
+ });
53874
+ const exitCode = await resetProc.exited;
53875
+ if (exitCode !== 0) {
53876
+ const stderr = await new Response(resetProc.stderr).text();
53877
+ logger.error("tdd", "Failed to rollback git changes", { ref, stderr });
53878
+ throw new Error(`Git rollback failed: ${stderr}`);
53879
+ }
53880
+ const cleanProc = _rollbackDeps.spawn(["git", "clean", "-fd"], {
53881
+ cwd: workdir,
53882
+ stdout: "pipe",
53883
+ stderr: "pipe"
53884
+ });
53885
+ const cleanExitCode = await cleanProc.exited;
53886
+ if (cleanExitCode !== 0) {
53887
+ const stderr = await new Response(cleanProc.stderr).text();
53888
+ logger.warn("tdd", "Failed to clean untracked files", { stderr });
53889
+ }
53890
+ logger.info("tdd", "Successfully rolled back git changes", { ref });
53891
+ }
53892
+ async function captureSnapshotRef(workdir, storyId) {
53893
+ await _rollbackDeps.autoCommitIfDirty(workdir, "non-blocking-fix-snapshot", "snapshot", storyId);
53894
+ const proc = _rollbackDeps.spawn(["git", "rev-parse", "HEAD"], { cwd: workdir, stdout: "pipe", stderr: "pipe" });
53895
+ const sha = (await new Response(proc.stdout).text()).trim();
53896
+ const exitCode = await proc.exited;
53897
+ if (exitCode !== 0) {
53898
+ throw new NaxError("git rev-parse HEAD failed in non-blocking-fix snapshot", "SNAPSHOT_REF_FAILED", {
53899
+ storyId,
53900
+ workdir,
53901
+ stage: "non-blocking-fix-snapshot"
53902
+ });
53903
+ }
53904
+ return sha;
53905
+ }
53906
+ var _rollbackDeps;
53907
+ var init_rollback = __esm(() => {
53908
+ init_errors();
53909
+ init_logger2();
53910
+ init_git();
53911
+ _rollbackDeps = {
53912
+ spawn: Bun.spawn,
53913
+ autoCommitIfDirty
53914
+ };
53915
+ });
53916
+
53917
+ // src/execution/non-blocking-fix.ts
53918
+ function shouldRunNonBlockingFix(cfg, advisoryCount) {
53919
+ return cfg?.enabled === true && advisoryCount > 0;
53920
+ }
53921
+ function nonBlockingExcludePhases() {
53922
+ return REVIEW_PHASE_KINDS;
53923
+ }
53924
+ function nonBlockingExtraPhases(cfg) {
53925
+ return cfg.scope === "both" && cfg.verifierGuard ? ["verifier"] : [];
53926
+ }
53927
+ async function runNonBlockingFix(args, _deps = DEFAULT_DEPS) {
53928
+ const logger = getSafeLogger();
53929
+ if (!shouldRunNonBlockingFix(args.cfg, args.advisoryFindings.length)) {
53930
+ return { ran: false, kept: false, restored: false };
53931
+ }
53932
+ const phaseOutputsSnapshot = { ...args.phaseOutputs };
53933
+ const restoreRef = await _deps.captureSnapshotRef(args.workdir, args.storyId);
53934
+ const maxAttempts = 1 + args.cfg.regressionAttempts;
53935
+ let exhausted = false;
53936
+ try {
53937
+ const result = await args.runRectify(maxAttempts);
53938
+ exhausted = result.rectificationExhausted === true;
53939
+ } catch (err) {
53940
+ logger?.warn("non-blocking-fix", "best-effort pass threw \u2014 restoring", {
53941
+ storyId: args.storyId,
53942
+ error: err instanceof Error ? err.message : String(err)
53943
+ });
53944
+ exhausted = true;
53945
+ }
53946
+ if (!exhausted) {
53947
+ logger?.info("non-blocking-fix", "best-effort fix kept", { storyId: args.storyId });
53948
+ return { ran: true, kept: true, restored: false };
53949
+ }
53950
+ await _deps.rollbackToRef(args.workdir, restoreRef);
53951
+ for (const key of Object.keys(args.phaseOutputs))
53952
+ delete args.phaseOutputs[key];
53953
+ Object.assign(args.phaseOutputs, phaseOutputsSnapshot);
53954
+ logger?.info("non-blocking-fix", "best-effort fix exhausted \u2014 restored to adversarial-passed", {
53955
+ storyId: args.storyId
53956
+ });
53957
+ return { ran: true, kept: false, restored: true };
53958
+ }
53959
+ var REVIEW_PHASE_KINDS, DEFAULT_DEPS;
53960
+ var init_non_blocking_fix = __esm(() => {
53961
+ init_logger2();
53962
+ init_rollback();
53963
+ REVIEW_PHASE_KINDS = ["semantic-review", "adversarial-review"];
53964
+ DEFAULT_DEPS = { captureSnapshotRef, rollbackToRef };
53965
+ });
53966
+
53535
53967
  // src/execution/story-orchestrator.ts
53536
53968
  async function refreshReviewInputForDispatch(opName, input) {
53537
53969
  if (opName !== "semantic-review" && opName !== "adversarial-review")
@@ -53971,16 +54403,17 @@ function withIncreasingFailuresBail(strategies, enabled, consecutiveIncreases) {
53971
54403
  }
53972
54404
  }));
53973
54405
  }
53974
- async function runRectification(ctx, state, phaseCosts, phaseOutputs) {
54406
+ async function runRectification(ctx, state, phaseCosts, phaseOutputs, overrides) {
53975
54407
  const rectification = state.rectification;
53976
- const validationPhases = collectRectificationPhases(state);
54408
+ const baseValidationPhases = collectRectificationPhases(state);
54409
+ const validationPhases = overrides?.excludePhaseKinds ? baseValidationPhases.filter((p) => !overrides.excludePhaseKinds?.includes(p.kind)) : baseValidationPhases;
53977
54410
  if (!rectification || validationPhases.length === 0) {
53978
54411
  return {};
53979
54412
  }
53980
54413
  if (ctx.runtime.signal?.aborted) {
53981
54414
  return {};
53982
54415
  }
53983
- const initialFindings = gatherRectificationFindings(phaseOutputs, validationPhases, state);
54416
+ const initialFindings = overrides?.initialFindings ? [...overrides.initialFindings] : gatherRectificationFindings(phaseOutputs, validationPhases, state);
53984
54417
  if (initialFindings.length === 0) {
53985
54418
  return {};
53986
54419
  }
@@ -53995,14 +54428,16 @@ async function runRectification(ctx, state, phaseCosts, phaseOutputs) {
53995
54428
  const cycle = {
53996
54429
  findings: [...initialFindings],
53997
54430
  iterations: [],
53998
- strategies: withIncreasingFailuresBail(rectification.strategies, rectification.abortOnIncreasingFailures, rectification.consecutiveIncreasesToBail ?? 1),
53999
- config: { maxAttemptsTotal: rectification.maxAttempts, validatorRetries: 1 },
54431
+ strategies: withIncreasingFailuresBail(overrides?.strategies ?? rectification.strategies, rectification.abortOnIncreasingFailures, rectification.consecutiveIncreasesToBail ?? 1),
54432
+ config: { maxAttemptsTotal: overrides?.maxAttempts ?? rectification.maxAttempts, validatorRetries: 1 },
54000
54433
  validate: async (_validateCtx, opts) => {
54001
54434
  if (ctx.runtime.signal?.aborted)
54002
54435
  return { findings: [], shortCircuited: false };
54003
54436
  const lite = (opts?.mode ?? "full") === "lite";
54004
54437
  const selected = phasesToRevalidate(opts?.strategiesRun, validationPhases);
54005
- const phases = lite ? orderGateLast(selected) : selected;
54438
+ const extra = overrides?.extraRevalidationKinds ? validationPhases.filter((p) => overrides.extraRevalidationKinds?.includes(p.kind) && !selected.some((s) => s.kind === p.kind)) : [];
54439
+ const selectedWithExtra = [...selected, ...extra];
54440
+ const phases = lite ? orderGateLast(selectedWithExtra) : selectedWithExtra;
54006
54441
  getSafeLogger()?.debug("story-orchestrator", "rectification validate scope", {
54007
54442
  storyId: ctx.storyId,
54008
54443
  mode: opts?.mode ?? "full",
@@ -54154,6 +54589,25 @@ class ExecutionPlan {
54154
54589
  }
54155
54590
  }
54156
54591
  }
54592
+ const advCfg = this.state.adversarialReview ? this.state.nonBlockingFix : undefined;
54593
+ const advisoryOut = phaseOutputs["adversarial-review"];
54594
+ const advisoryFindings = advisoryOut?.advisoryFindings ?? [];
54595
+ if (advCfg && this.state.rectification && this.ctx.storyId && shouldRunNonBlockingFix(advCfg, advisoryFindings.length)) {
54596
+ await runNonBlockingFix({
54597
+ workdir: this.ctx.packageDir,
54598
+ storyId: this.ctx.storyId,
54599
+ advisoryFindings,
54600
+ cfg: advCfg,
54601
+ phaseOutputs,
54602
+ runRectify: (maxAttempts) => runRectification(this.ctx, this.state, phaseCosts, phaseOutputs, {
54603
+ initialFindings: advisoryFindings,
54604
+ strategies: this.state.nonBlockingFixStrategies ?? [],
54605
+ excludePhaseKinds: nonBlockingExcludePhases(),
54606
+ extraRevalidationKinds: nonBlockingExtraPhases(advCfg),
54607
+ maxAttempts
54608
+ })
54609
+ });
54610
+ }
54157
54611
  const verifierName = this.state.verifier?.slot.op.name;
54158
54612
  const gateName = this.state.fullSuiteGate?.slot.op.name;
54159
54613
  const verifierPassedSsot = verifierName !== undefined && phaseExplicitlyPassed(phaseOutputs[verifierName]);
@@ -54246,6 +54700,11 @@ class StoryOrchestratorBuilder {
54246
54700
  this.state.rectification = opts;
54247
54701
  return this;
54248
54702
  }
54703
+ addNonBlockingFix(cfg, strategies) {
54704
+ this.state.nonBlockingFix = cfg;
54705
+ this.state.nonBlockingFixStrategies = strategies;
54706
+ return this;
54707
+ }
54249
54708
  build(ctx, opts = {}) {
54250
54709
  if (!this.state.implementer) {
54251
54710
  throw new NaxError("StoryOrchestratorBuilder.build(): addImplementer() must be called before build()", "ORCHESTRATOR_NO_IMPLEMENTER", { stage: "execution" });
@@ -54263,6 +54722,7 @@ var init_story_orchestrator = __esm(() => {
54263
54722
  init_event_bus();
54264
54723
  init_prepare_inputs();
54265
54724
  init_git();
54725
+ init_non_blocking_fix();
54266
54726
  _storyOrchestratorDeps = {
54267
54727
  callOp,
54268
54728
  runFixCycle,
@@ -54415,6 +54875,22 @@ async function buildPlanForStrategy(ctx, story, config2, testStrategy, inputs) {
54415
54875
  };
54416
54876
  builder.addRectification(rectOpts);
54417
54877
  }
54878
+ const nbf = config2.review?.adversarial?.nonBlockingFix;
54879
+ const nbStrategies = [];
54880
+ if (nbf?.enabled && inputs.adversarialReview) {
54881
+ const nbSink = makeDeclarationSink();
54882
+ if (nbf.scope === "source") {
54883
+ nbStrategies.push(makeAutofixImplementerStrategy(story, config2, nbSink, {
54884
+ includeAdversarialReview: true
54885
+ }));
54886
+ } else {
54887
+ nbStrategies.push(makeAutofixImplementerStrategy(story, config2, nbSink, {
54888
+ includeAdversarialReview: false
54889
+ }), makeAutofixTestWriterStrategy(story, config2, nbSink));
54890
+ }
54891
+ nbStrategies.push(makeFullSuiteRectifyStrategy(story, config2));
54892
+ builder.addNonBlockingFix(nbf, nbStrategies);
54893
+ }
54418
54894
  return builder.build(ctx, { isThreeSession });
54419
54895
  }
54420
54896
  var init_build_plan_for_strategy = __esm(() => {
@@ -54678,41 +55154,6 @@ var init_execution_helpers = __esm(() => {
54678
55154
  init_errors();
54679
55155
  });
54680
55156
 
54681
- // src/tdd/rollback.ts
54682
- async function rollbackToRef(workdir, ref) {
54683
- const logger = getLogger();
54684
- logger.warn("tdd", "Rolling back git changes", { ref });
54685
- const resetProc = _rollbackDeps.spawn(["git", "reset", "--hard", ref], {
54686
- cwd: workdir,
54687
- stdout: "pipe",
54688
- stderr: "pipe"
54689
- });
54690
- const exitCode = await resetProc.exited;
54691
- if (exitCode !== 0) {
54692
- const stderr = await new Response(resetProc.stderr).text();
54693
- logger.error("tdd", "Failed to rollback git changes", { ref, stderr });
54694
- throw new Error(`Git rollback failed: ${stderr}`);
54695
- }
54696
- const cleanProc = _rollbackDeps.spawn(["git", "clean", "-fd"], {
54697
- cwd: workdir,
54698
- stdout: "pipe",
54699
- stderr: "pipe"
54700
- });
54701
- const cleanExitCode = await cleanProc.exited;
54702
- if (cleanExitCode !== 0) {
54703
- const stderr = await new Response(cleanProc.stderr).text();
54704
- logger.warn("tdd", "Failed to clean untracked files", { stderr });
54705
- }
54706
- logger.info("tdd", "Successfully rolled back git changes", { ref });
54707
- }
54708
- var _rollbackDeps;
54709
- var init_rollback = __esm(() => {
54710
- init_logger2();
54711
- _rollbackDeps = {
54712
- spawn: Bun.spawn
54713
- };
54714
- });
54715
-
54716
55157
  // src/execution/session-manager-runtime.ts
54717
55158
  async function closePhysicalSession(descriptor, agentGetFn, force) {
54718
55159
  if (!descriptor.handle)
@@ -56287,9 +56728,14 @@ __export(exports_init_context, {
56287
56728
  generateContextTemplate: () => generateContextTemplate,
56288
56729
  _initContextDeps: () => _initContextDeps
56289
56730
  });
56290
- import { existsSync as existsSync22 } from "fs";
56291
- import { mkdir as mkdir8 } from "fs/promises";
56292
56731
  import { basename as basename9, join as join51 } from "path";
56732
+ async function bunFileExists(path13) {
56733
+ return Bun.file(path13).exists();
56734
+ }
56735
+ async function bunMkdirp(path13) {
56736
+ const proc = Bun.spawn(["mkdir", "-p", path13]);
56737
+ await proc.exited;
56738
+ }
56293
56739
  async function findFiles(dir, maxFiles = 200) {
56294
56740
  try {
56295
56741
  const proc = Bun.spawnSync([
@@ -56318,7 +56764,7 @@ async function findFiles(dir, maxFiles = 200) {
56318
56764
  }
56319
56765
  async function readPackageManifest(projectRoot) {
56320
56766
  const packageJsonPath = join51(projectRoot, "package.json");
56321
- if (!existsSync22(packageJsonPath)) {
56767
+ if (!await bunFileExists(packageJsonPath)) {
56322
56768
  return null;
56323
56769
  }
56324
56770
  try {
@@ -56336,7 +56782,7 @@ async function readPackageManifest(projectRoot) {
56336
56782
  }
56337
56783
  async function readReadmeSnippet(projectRoot) {
56338
56784
  const readmePath = join51(projectRoot, "README.md");
56339
- if (!existsSync22(readmePath)) {
56785
+ if (!await bunFileExists(readmePath)) {
56340
56786
  return null;
56341
56787
  }
56342
56788
  try {
@@ -56354,7 +56800,7 @@ async function detectEntryPoints(projectRoot) {
56354
56800
  const found = [];
56355
56801
  for (const candidate of candidates) {
56356
56802
  const path13 = join51(projectRoot, candidate);
56357
- if (existsSync22(path13)) {
56803
+ if (await bunFileExists(path13)) {
56358
56804
  found.push(candidate);
56359
56805
  }
56360
56806
  }
@@ -56365,7 +56811,7 @@ async function detectConfigFiles(projectRoot) {
56365
56811
  const found = [];
56366
56812
  for (const candidate of candidates) {
56367
56813
  const path13 = join51(projectRoot, candidate);
56368
- if (existsSync22(path13)) {
56814
+ if (await bunFileExists(path13)) {
56369
56815
  found.push(candidate);
56370
56816
  }
56371
56817
  }
@@ -56495,10 +56941,10 @@ Keep it under 2000 tokens. Use markdown formatting. Be specific to the detected
56495
56941
  `;
56496
56942
  try {
56497
56943
  const result = await _initContextDeps.callLLM(prompt);
56498
- logger.info("init", "Generated context.md with LLM");
56944
+ logger.info("init", "Generated context.md with LLM", { storyId: "init-context" });
56499
56945
  return result;
56500
56946
  } catch (err) {
56501
- logger.warn("init", `LLM context generation failed, falling back to template: ${err instanceof Error ? err.message : String(err)}`);
56947
+ logger.warn("init", `LLM context generation failed, falling back to template: ${err instanceof Error ? err.message : String(err)}`, { storyId: "init-context" });
56502
56948
  return generateContextTemplate(scan);
56503
56949
  }
56504
56950
  }
@@ -56527,27 +56973,33 @@ async function initPackage(repoRoot, packagePath, force = false) {
56527
56973
  const logger = getLogger();
56528
56974
  const naxDir = join51(repoRoot, ".nax", "mono", packagePath);
56529
56975
  const contextPath = join51(naxDir, "context.md");
56530
- if (existsSync22(contextPath) && !force) {
56531
- logger.info("init", "Package context.md already exists (use --force to overwrite)", { path: contextPath });
56976
+ if (await bunFileExists(contextPath) && !force) {
56977
+ logger.info("init", "Package context.md already exists (use --force to overwrite)", {
56978
+ storyId: "init-context",
56979
+ path: contextPath
56980
+ });
56532
56981
  return;
56533
56982
  }
56534
- if (!existsSync22(naxDir)) {
56535
- await mkdir8(naxDir, { recursive: true });
56983
+ if (!await bunFileExists(naxDir)) {
56984
+ await bunMkdirp(naxDir);
56536
56985
  }
56537
56986
  const content = generatePackageContextTemplate(packagePath);
56538
56987
  await Bun.write(contextPath, content);
56539
- logger.info("init", "Created package context.md", { path: contextPath });
56988
+ logger.info("init", "Created package context.md", { storyId: "init-context", path: contextPath });
56540
56989
  }
56541
56990
  async function initContext(projectRoot, options = {}) {
56542
56991
  const logger = getLogger();
56543
56992
  const naxDir = join51(projectRoot, ".nax");
56544
56993
  const contextPath = join51(naxDir, "context.md");
56545
- if (existsSync22(contextPath) && !options.force) {
56546
- logger.info("init", "context.md already exists, skipping (use --force to overwrite)", { path: contextPath });
56994
+ if (await bunFileExists(contextPath) && !options.force) {
56995
+ logger.info("init", "context.md already exists, skipping (use --force to overwrite)", {
56996
+ storyId: "init-context",
56997
+ path: contextPath
56998
+ });
56547
56999
  return;
56548
57000
  }
56549
- if (!existsSync22(naxDir)) {
56550
- await mkdir8(naxDir, { recursive: true });
57001
+ if (!await bunFileExists(naxDir)) {
57002
+ await bunMkdirp(naxDir);
56551
57003
  }
56552
57004
  const scan = await scanProject(projectRoot);
56553
57005
  let content;
@@ -56557,7 +57009,10 @@ async function initContext(projectRoot, options = {}) {
56557
57009
  content = generateContextTemplate(scan);
56558
57010
  }
56559
57011
  await Bun.write(contextPath, content);
56560
- logger.info("init", "Generated .nax/context.md template from project scan", { path: contextPath });
57012
+ logger.info("init", "Generated .nax/context.md template from project scan", {
57013
+ storyId: "init-context",
57014
+ path: contextPath
57015
+ });
56561
57016
  }
56562
57017
  var _initContextDeps;
56563
57018
  var init_init_context = __esm(() => {
@@ -56570,11 +57025,11 @@ var init_init_context = __esm(() => {
56570
57025
  });
56571
57026
 
56572
57027
  // src/cli/init-detect.ts
56573
- import { existsSync as existsSync23, readFileSync } from "fs";
57028
+ import { existsSync as existsSync22, readFileSync } from "fs";
56574
57029
  import { join as join52 } from "path";
56575
57030
  function readPackageJson(projectRoot) {
56576
57031
  const pkgPath = join52(projectRoot, "package.json");
56577
- if (!existsSync23(pkgPath))
57032
+ if (!existsSync22(pkgPath))
56578
57033
  return;
56579
57034
  try {
56580
57035
  return JSON.parse(readFileSync(pkgPath, "utf-8"));
@@ -56616,41 +57071,41 @@ function detectStack(projectRoot) {
56616
57071
  };
56617
57072
  }
56618
57073
  function detectRuntime(projectRoot) {
56619
- if (existsSync23(join52(projectRoot, "bun.lockb")) || existsSync23(join52(projectRoot, "bunfig.toml"))) {
57074
+ if (existsSync22(join52(projectRoot, "bun.lockb")) || existsSync22(join52(projectRoot, "bunfig.toml"))) {
56620
57075
  return "bun";
56621
57076
  }
56622
- if (existsSync23(join52(projectRoot, "package-lock.json")) || existsSync23(join52(projectRoot, "yarn.lock")) || existsSync23(join52(projectRoot, "pnpm-lock.yaml"))) {
57077
+ if (existsSync22(join52(projectRoot, "package-lock.json")) || existsSync22(join52(projectRoot, "yarn.lock")) || existsSync22(join52(projectRoot, "pnpm-lock.yaml"))) {
56623
57078
  return "node";
56624
57079
  }
56625
57080
  return "unknown";
56626
57081
  }
56627
57082
  function detectLanguage2(projectRoot) {
56628
- if (existsSync23(join52(projectRoot, "tsconfig.json")))
57083
+ if (existsSync22(join52(projectRoot, "tsconfig.json")))
56629
57084
  return "typescript";
56630
- if (existsSync23(join52(projectRoot, "pyproject.toml")) || existsSync23(join52(projectRoot, "setup.py"))) {
57085
+ if (existsSync22(join52(projectRoot, "pyproject.toml")) || existsSync22(join52(projectRoot, "setup.py"))) {
56631
57086
  return "python";
56632
57087
  }
56633
- if (existsSync23(join52(projectRoot, "Cargo.toml")))
57088
+ if (existsSync22(join52(projectRoot, "Cargo.toml")))
56634
57089
  return "rust";
56635
- if (existsSync23(join52(projectRoot, "go.mod")))
57090
+ if (existsSync22(join52(projectRoot, "go.mod")))
56636
57091
  return "go";
56637
57092
  return "unknown";
56638
57093
  }
56639
57094
  function detectLinter(projectRoot) {
56640
- if (existsSync23(join52(projectRoot, "biome.json")) || existsSync23(join52(projectRoot, "biome.jsonc"))) {
57095
+ if (existsSync22(join52(projectRoot, "biome.json")) || existsSync22(join52(projectRoot, "biome.jsonc"))) {
56641
57096
  return "biome";
56642
57097
  }
56643
- if (existsSync23(join52(projectRoot, ".eslintrc.json")) || existsSync23(join52(projectRoot, ".eslintrc.js")) || existsSync23(join52(projectRoot, "eslint.config.js"))) {
57098
+ if (existsSync22(join52(projectRoot, ".eslintrc.json")) || existsSync22(join52(projectRoot, ".eslintrc.js")) || existsSync22(join52(projectRoot, "eslint.config.js"))) {
56644
57099
  return "eslint";
56645
57100
  }
56646
57101
  return "unknown";
56647
57102
  }
56648
57103
  function detectMonorepo(projectRoot) {
56649
- if (existsSync23(join52(projectRoot, "turbo.json")))
57104
+ if (existsSync22(join52(projectRoot, "turbo.json")))
56650
57105
  return "turborepo";
56651
- if (existsSync23(join52(projectRoot, "nx.json")))
57106
+ if (existsSync22(join52(projectRoot, "nx.json")))
56652
57107
  return "nx";
56653
- if (existsSync23(join52(projectRoot, "pnpm-workspace.yaml")))
57108
+ if (existsSync22(join52(projectRoot, "pnpm-workspace.yaml")))
56654
57109
  return "pnpm-workspaces";
56655
57110
  const pkg = readPackageJson(projectRoot);
56656
57111
  if (pkg?.workspaces)
@@ -56792,8 +57247,8 @@ __export(exports_init, {
56792
57247
  checkInitCollision: () => checkInitCollision,
56793
57248
  _initDeps: () => _initDeps
56794
57249
  });
56795
- import { existsSync as existsSync24 } from "fs";
56796
- import { mkdir as mkdir9 } from "fs/promises";
57250
+ import { existsSync as existsSync23 } from "fs";
57251
+ import { mkdir as mkdir8 } from "fs/promises";
56797
57252
  import { join as join53 } from "path";
56798
57253
  function validateProjectName(name) {
56799
57254
  if (!name)
@@ -56832,7 +57287,7 @@ async function updateGitignore(projectRoot) {
56832
57287
  const logger = getLogger();
56833
57288
  const gitignorePath = join53(projectRoot, ".gitignore");
56834
57289
  let existing = "";
56835
- if (existsSync24(gitignorePath)) {
57290
+ if (existsSync23(gitignorePath)) {
56836
57291
  existing = await Bun.file(gitignorePath).text();
56837
57292
  }
56838
57293
  const missingEntries = NAX_GITIGNORE_ENTRIES.filter((entry) => !existing.includes(entry));
@@ -56912,12 +57367,12 @@ function buildConstitution(stack) {
56912
57367
  async function initGlobal() {
56913
57368
  const logger = getLogger();
56914
57369
  const globalDir = globalConfigDir();
56915
- if (!existsSync24(globalDir)) {
56916
- await mkdir9(globalDir, { recursive: true });
57370
+ if (!existsSync23(globalDir)) {
57371
+ await mkdir8(globalDir, { recursive: true });
56917
57372
  logger.info("init", "Created global config directory", { path: globalDir });
56918
57373
  }
56919
57374
  const configPath = join53(globalDir, "config.json");
56920
- if (!existsSync24(configPath)) {
57375
+ if (!existsSync23(configPath)) {
56921
57376
  await Bun.write(configPath, `${JSON.stringify(MINIMAL_GLOBAL_CONFIG, null, 2)}
56922
57377
  `);
56923
57378
  logger.info("init", "Created global config", { path: configPath });
@@ -56925,15 +57380,15 @@ async function initGlobal() {
56925
57380
  logger.info("init", "Global config already exists", { path: configPath });
56926
57381
  }
56927
57382
  const constitutionPath = join53(globalDir, "constitution.md");
56928
- if (!existsSync24(constitutionPath)) {
57383
+ if (!existsSync23(constitutionPath)) {
56929
57384
  await Bun.write(constitutionPath, buildConstitution({ runtime: "unknown", language: "unknown", linter: "unknown", monorepo: "none" }));
56930
57385
  logger.info("init", "Created global constitution", { path: constitutionPath });
56931
57386
  } else {
56932
57387
  logger.info("init", "Global constitution already exists", { path: constitutionPath });
56933
57388
  }
56934
57389
  const hooksDir = join53(globalDir, "hooks");
56935
- if (!existsSync24(hooksDir)) {
56936
- await mkdir9(hooksDir, { recursive: true });
57390
+ if (!existsSync23(hooksDir)) {
57391
+ await mkdir8(hooksDir, { recursive: true });
56937
57392
  logger.info("init", "Created global hooks directory", { path: hooksDir });
56938
57393
  } else {
56939
57394
  logger.info("init", "Global hooks directory already exists", { path: hooksDir });
@@ -56977,8 +57432,8 @@ async function initProject(projectRoot, options) {
56977
57432
  `), "INIT_NAME_COLLISION", { stage: "init", name: detectedName });
56978
57433
  }
56979
57434
  }
56980
- if (!existsSync24(projectDir)) {
56981
- await mkdir9(projectDir, { recursive: true });
57435
+ if (!existsSync23(projectDir)) {
57436
+ await mkdir8(projectDir, { recursive: true });
56982
57437
  logger.info("init", "Created project config directory", { path: projectDir });
56983
57438
  }
56984
57439
  const stack = detectStack(projectRoot);
@@ -56993,7 +57448,7 @@ async function initProject(projectRoot, options) {
56993
57448
  monorepo: stack.monorepo
56994
57449
  });
56995
57450
  const configPath = join53(projectDir, "config.json");
56996
- if (!existsSync24(configPath)) {
57451
+ if (!existsSync23(configPath)) {
56997
57452
  await Bun.write(configPath, `${JSON.stringify(projectConfig, null, 2)}
56998
57453
  `);
56999
57454
  logger.info("init", "Created project config", { path: configPath });
@@ -57002,15 +57457,15 @@ async function initProject(projectRoot, options) {
57002
57457
  }
57003
57458
  await initContext(projectRoot, { ai: options?.ai, force: options?.force });
57004
57459
  const constitutionPath = join53(projectDir, "constitution.md");
57005
- if (!existsSync24(constitutionPath) || options?.force) {
57460
+ if (!existsSync23(constitutionPath) || options?.force) {
57006
57461
  await Bun.write(constitutionPath, buildConstitution(stack));
57007
57462
  logger.info("init", "Created project constitution", { path: constitutionPath });
57008
57463
  } else {
57009
57464
  logger.info("init", "Project constitution already exists", { path: constitutionPath });
57010
57465
  }
57011
57466
  const hooksDir = join53(projectDir, "hooks");
57012
- if (!existsSync24(hooksDir)) {
57013
- await mkdir9(hooksDir, { recursive: true });
57467
+ if (!existsSync23(hooksDir)) {
57468
+ await mkdir8(hooksDir, { recursive: true });
57014
57469
  logger.info("init", "Created project hooks directory", { path: hooksDir });
57015
57470
  } else {
57016
57471
  logger.info("init", "Project hooks directory already exists", { path: hooksDir });
@@ -57069,6 +57524,286 @@ var init_init2 = __esm(() => {
57069
57524
  };
57070
57525
  });
57071
57526
 
57527
+ // src/cli/setup-analyze.ts
57528
+ import { join as join54 } from "path";
57529
+ async function detectPackageManager(workdir) {
57530
+ const { fileExists } = _analyzeRepoDeps;
57531
+ if (await fileExists(join54(workdir, "bun.lock")))
57532
+ return { pmRunPrefix: "bun run", pmDlx: "bunx" };
57533
+ if (await fileExists(join54(workdir, "bun.lockb")))
57534
+ return { pmRunPrefix: "bun run", pmDlx: "bunx" };
57535
+ if (await fileExists(join54(workdir, "package-lock.json")))
57536
+ return { pmRunPrefix: "npm run", pmDlx: "npx" };
57537
+ if (await fileExists(join54(workdir, "yarn.lock")))
57538
+ return { pmRunPrefix: "yarn", pmDlx: "yarn dlx" };
57539
+ if (await fileExists(join54(workdir, "pnpm-lock.yaml")))
57540
+ return { pmRunPrefix: "pnpm run", pmDlx: "pnpx" };
57541
+ return { pmRunPrefix: "npm run", pmDlx: "npx" };
57542
+ }
57543
+ async function detectOrchestrator(workdir) {
57544
+ const { fileExists } = _analyzeRepoDeps;
57545
+ if (await fileExists(join54(workdir, "turbo.json")))
57546
+ return "turbo";
57547
+ if (await fileExists(join54(workdir, "nx.json")))
57548
+ return "nx";
57549
+ return "none";
57550
+ }
57551
+ async function getMissingScripts(packageDir) {
57552
+ const pkg = await _analyzeRepoDeps.readJson(join54(packageDir, "package.json"));
57553
+ const scripts = pkg?.scripts ?? {};
57554
+ return CANONICAL_SCRIPTS.filter((s) => !(s in scripts));
57555
+ }
57556
+ async function buildPackageFacts(workdir, relativeDir, testPatternMap) {
57557
+ const packageDir = relativeDir === "" ? workdir : join54(workdir, relativeDir);
57558
+ const [profile, missingScripts] = await Promise.all([
57559
+ _analyzeRepoDeps.detectProjectProfile(packageDir, {}),
57560
+ getMissingScripts(packageDir)
57561
+ ]);
57562
+ const detection = testPatternMap[relativeDir] ?? {
57563
+ patterns: [],
57564
+ confidence: "empty",
57565
+ sources: []
57566
+ };
57567
+ return {
57568
+ relativeDir,
57569
+ testFramework: profile.testFramework,
57570
+ testFilePatterns: detection.patterns,
57571
+ missingScripts
57572
+ };
57573
+ }
57574
+ async function analyzeRepo(workdir) {
57575
+ const { discoverWorkspacePackages: discover, detectTestFilePatternsForWorkspace: detectPatterns } = _analyzeRepoDeps;
57576
+ const [pmInfo, orchestrator, packageDirs] = await Promise.all([
57577
+ detectPackageManager(workdir),
57578
+ detectOrchestrator(workdir),
57579
+ discover(workdir)
57580
+ ]);
57581
+ const shape = packageDirs.length > 0 ? "mono" : "single";
57582
+ const dirsToAnalyze = shape === "single" ? [""] : packageDirs;
57583
+ const testPatternMap = await detectPatterns(workdir, packageDirs);
57584
+ const packages = await Promise.all(dirsToAnalyze.map((dir) => buildPackageFacts(workdir, dir, testPatternMap)));
57585
+ return {
57586
+ shape,
57587
+ packages,
57588
+ pmRunPrefix: pmInfo.pmRunPrefix,
57589
+ pmDlx: pmInfo.pmDlx,
57590
+ orchestrator
57591
+ };
57592
+ }
57593
+ var CANONICAL_SCRIPTS, _analyzeRepoDeps;
57594
+ var init_setup_analyze = __esm(() => {
57595
+ init_project();
57596
+ init_detect2();
57597
+ init_workspace();
57598
+ CANONICAL_SCRIPTS = ["build", "test", "lint", "type-check", "lint:fix"];
57599
+ _analyzeRepoDeps = {
57600
+ fileExists: async (path13) => Bun.file(path13).exists(),
57601
+ readJson: async (path13) => {
57602
+ try {
57603
+ const f = Bun.file(path13);
57604
+ if (!await f.exists())
57605
+ return null;
57606
+ return JSON.parse(await f.text());
57607
+ } catch {
57608
+ return null;
57609
+ }
57610
+ },
57611
+ discoverWorkspacePackages,
57612
+ detectProjectProfile,
57613
+ detectTestFilePatternsForWorkspace
57614
+ };
57615
+ });
57616
+
57617
+ // src/cli/setup-fill.ts
57618
+ import { join as join55 } from "path";
57619
+ async function addScriptToPackageJson(pkgJsonPath, key, value) {
57620
+ const pkg = await _fillScriptsDeps.readJson(pkgJsonPath) ?? {};
57621
+ const scripts = pkg.scripts ?? {};
57622
+ if (key in scripts)
57623
+ return;
57624
+ const updated = { ...pkg, scripts: { ...scripts, [key]: value } };
57625
+ await _fillScriptsDeps.writeFile(pkgJsonPath, JSON.stringify(updated, null, 2));
57626
+ }
57627
+ async function addTurboTask(turboJsonPath, taskKey) {
57628
+ const turbo = await _fillScriptsDeps.readJson(turboJsonPath) ?? {};
57629
+ const field = "pipeline" in turbo ? "pipeline" : "tasks";
57630
+ const existing = turbo[field] ?? {};
57631
+ if (taskKey in existing)
57632
+ return;
57633
+ const updated = { ...turbo, [field]: { ...existing, [taskKey]: {} } };
57634
+ await _fillScriptsDeps.writeFile(turboJsonPath, JSON.stringify(updated, null, 2));
57635
+ }
57636
+ async function fillScripts(workdir, analysis) {
57637
+ const { shape, packages, orchestrator } = analysis;
57638
+ if (shape === "single") {
57639
+ const rootPkg = packages[0];
57640
+ if (rootPkg?.missingScripts.includes(TYPE_CHECK_KEY)) {
57641
+ await addScriptToPackageJson(join55(workdir, "package.json"), TYPE_CHECK_KEY, TYPE_CHECK_SCRIPT);
57642
+ }
57643
+ return;
57644
+ }
57645
+ for (const pkg of packages) {
57646
+ if (!pkg.missingScripts.includes(TYPE_CHECK_KEY))
57647
+ continue;
57648
+ await addScriptToPackageJson(join55(workdir, pkg.relativeDir, "package.json"), TYPE_CHECK_KEY, TYPE_CHECK_SCRIPT);
57649
+ }
57650
+ if (orchestrator === "turbo" && packages.some((p) => p.missingScripts.includes(TYPE_CHECK_KEY))) {
57651
+ await addTurboTask(join55(workdir, "turbo.json"), TYPE_CHECK_KEY);
57652
+ await addScriptToPackageJson(join55(workdir, "package.json"), TYPE_CHECK_KEY, TYPE_CHECK_TURBO_PASSTHROUGH);
57653
+ }
57654
+ }
57655
+ var TYPE_CHECK_KEY = "type-check", TYPE_CHECK_SCRIPT = "tsc --noEmit -p tsconfig.json", TYPE_CHECK_TURBO_PASSTHROUGH = "turbo run type-check", _fillScriptsDeps;
57656
+ var init_setup_fill = __esm(() => {
57657
+ _fillScriptsDeps = {
57658
+ readJson: async (path13) => {
57659
+ try {
57660
+ const f = Bun.file(path13);
57661
+ if (!await f.exists())
57662
+ return null;
57663
+ return JSON.parse(await f.text());
57664
+ } catch {
57665
+ return null;
57666
+ }
57667
+ },
57668
+ writeFile: async (path13, content) => {
57669
+ await Bun.write(path13, content);
57670
+ }
57671
+ };
57672
+ });
57673
+
57674
+ // src/cli/setup-llm.ts
57675
+ var generateSetupPlan = (ctx, analysis) => callOp(ctx, setupGenerateOp, analysis);
57676
+ var init_setup_llm = __esm(() => {
57677
+ init_operations();
57678
+ init_setup_generate();
57679
+ });
57680
+
57681
+ // src/cli/setup-verify.ts
57682
+ async function runSetupGate(workdir, config2) {
57683
+ const logger = getLogger();
57684
+ const quality = config2.quality;
57685
+ const testCmd = quality?.commands?.test;
57686
+ if (!testCmd) {
57687
+ logger.info("setup-verify", "No test command configured \u2014 skipping verification gate", {
57688
+ storyId: "setup"
57689
+ });
57690
+ return 0;
57691
+ }
57692
+ logger.info("setup-verify", "Running verification gate", { storyId: "setup", cmd: testCmd });
57693
+ const parts = testCmd.trim().split(/\s+/).filter(Boolean);
57694
+ if (parts.length === 0)
57695
+ return 0;
57696
+ const proc = _setupVerifyDeps.spawn(parts, { cwd: workdir });
57697
+ return await proc.exited;
57698
+ }
57699
+ var _setupVerifyDeps;
57700
+ var init_setup_verify = __esm(() => {
57701
+ init_logger2();
57702
+ _setupVerifyDeps = {
57703
+ spawn: Bun.spawn.bind(Bun)
57704
+ };
57705
+ });
57706
+
57707
+ // src/cli/setup.ts
57708
+ var exports_setup = {};
57709
+ __export(exports_setup, {
57710
+ setupCommand: () => setupCommand,
57711
+ _setupDeps: () => _setupDeps
57712
+ });
57713
+ import { join as join56 } from "path";
57714
+ async function setupCommand(options = {}) {
57715
+ const workdir = options.dir ?? process.cwd();
57716
+ const naxDir = join56(workdir, ".nax");
57717
+ const naxConfigPath = join56(naxDir, "config.json");
57718
+ const exists = await _setupDeps.fileExists(naxConfigPath);
57719
+ if (exists && !options.force) {
57720
+ _setupDeps.stderr("[setup] .nax/config.json already exists. Use --force to overwrite.");
57721
+ return 1;
57722
+ }
57723
+ const analysis = await _setupDeps.analyzeRepo(workdir);
57724
+ const { ctx, close } = await _setupDeps.buildCallContext(workdir, options.agent);
57725
+ let plan;
57726
+ try {
57727
+ try {
57728
+ plan = await _setupDeps.generateSetupPlan(ctx, analysis);
57729
+ } catch (err) {
57730
+ if (err instanceof NaxError && err.code === "SETUP_PLAN_INVALID") {
57731
+ _setupDeps.stderr(`[setup] ${err.message}`);
57732
+ return 1;
57733
+ }
57734
+ throw err;
57735
+ }
57736
+ if (options.dryRun) {
57737
+ _setupDeps.stdout(`[setup] Dry run \u2014 planned root config:
57738
+ ${JSON.stringify(plan.config, null, 2)}`);
57739
+ return 0;
57740
+ }
57741
+ for (const gap of plan.gaps) {
57742
+ _setupDeps.stderr(`[setup] gap: ${gap}`);
57743
+ }
57744
+ if (options.fillScripts) {
57745
+ await _setupDeps.fillScripts(workdir, analysis);
57746
+ }
57747
+ await _setupDeps.mkdir(naxDir);
57748
+ await _setupDeps.writeFile(naxConfigPath, JSON.stringify(plan.config, null, 2));
57749
+ for (const mc of plan.monoConfigs) {
57750
+ const monoDir = join56(naxDir, "mono", mc.relativeDir);
57751
+ await _setupDeps.mkdir(monoDir);
57752
+ await _setupDeps.writeFile(join56(monoDir, "config.json"), JSON.stringify(mc.config, null, 2));
57753
+ }
57754
+ const gateResult = await _setupDeps.runGate(workdir, plan.config);
57755
+ if (gateResult !== 0) {
57756
+ return gateResult;
57757
+ }
57758
+ return 0;
57759
+ } finally {
57760
+ await close();
57761
+ }
57762
+ }
57763
+ var _setupDeps;
57764
+ var init_setup = __esm(() => {
57765
+ init_errors();
57766
+ init_setup_analyze();
57767
+ init_setup_fill();
57768
+ init_setup_llm();
57769
+ init_setup_verify();
57770
+ _setupDeps = {
57771
+ analyzeRepo,
57772
+ fillScripts,
57773
+ buildCallContext: async (workdir, agentName) => {
57774
+ const { loadConfig: loadConfig2 } = await Promise.resolve().then(() => (init_config(), exports_config));
57775
+ const { createRuntime: createRuntime2 } = await Promise.resolve().then(() => (init_runtime(), exports_runtime));
57776
+ const config2 = await loadConfig2(workdir);
57777
+ const rt = createRuntime2(config2, workdir);
57778
+ return {
57779
+ ctx: {
57780
+ runtime: rt,
57781
+ packageView: rt.packages.resolve(),
57782
+ packageDir: workdir,
57783
+ agentName: agentName ?? rt.agentManager.getDefault()
57784
+ },
57785
+ close: () => rt.close()
57786
+ };
57787
+ },
57788
+ generateSetupPlan: (ctx, analysis) => generateSetupPlan(ctx, analysis),
57789
+ runGate: (workdir, config2) => runSetupGate(workdir, config2),
57790
+ fileExists: (path13) => Bun.file(path13).exists(),
57791
+ writeFile: (path13, content) => Bun.write(path13, content).then(() => {}),
57792
+ mkdir: async (path13) => {
57793
+ const proc = Bun.spawn(["mkdir", "-p", path13]);
57794
+ await proc.exited;
57795
+ },
57796
+ stdout: (msg) => {
57797
+ process.stdout.write(`${msg}
57798
+ `);
57799
+ },
57800
+ stderr: (msg) => {
57801
+ process.stderr.write(`${msg}
57802
+ `);
57803
+ }
57804
+ };
57805
+ });
57806
+
57072
57807
  // src/plugins/builtin/curator/collect.ts
57073
57808
  import * as path13 from "path";
57074
57809
  function now() {
@@ -57770,12 +58505,12 @@ function renderProposals(proposals, runId, observationCount) {
57770
58505
  }
57771
58506
 
57772
58507
  // src/plugins/builtin/curator/rollup.ts
57773
- import { appendFile as appendFile3, mkdir as mkdir10, writeFile } from "fs/promises";
58508
+ import { appendFile as appendFile3, mkdir as mkdir9, writeFile } from "fs/promises";
57774
58509
  import * as path14 from "path";
57775
58510
  async function appendToRollup(observations, rollupPath) {
57776
58511
  try {
57777
58512
  const dir = path14.dirname(rollupPath);
57778
- await mkdir10(dir, { recursive: true });
58513
+ await mkdir9(dir, { recursive: true });
57779
58514
  if (observations.length === 0) {
57780
58515
  const f = Bun.file(rollupPath);
57781
58516
  if (!await f.exists()) {
@@ -57792,7 +58527,7 @@ async function appendToRollup(observations, rollupPath) {
57792
58527
  var init_rollup = () => {};
57793
58528
 
57794
58529
  // src/plugins/builtin/curator/index.ts
57795
- import { mkdir as mkdir11 } from "fs/promises";
58530
+ import { mkdir as mkdir10 } from "fs/promises";
57796
58531
  import * as path15 from "path";
57797
58532
  function getCuratorEnabled(context) {
57798
58533
  const cfg = context.config;
@@ -57867,7 +58602,7 @@ var init_curator = __esm(() => {
57867
58602
  if (context.outputDir) {
57868
58603
  const { observationsPath, rollupPath } = resolveCuratorOutputs(curatorContext);
57869
58604
  const runDir = path15.dirname(observationsPath);
57870
- await mkdir11(runDir, { recursive: true });
58605
+ await mkdir10(runDir, { recursive: true });
57871
58606
  await Bun.write(observationsPath, observations.map((o) => JSON.stringify(o)).join(`
57872
58607
  `) + (observations.length > 0 ? `
57873
58608
  ` : ""));
@@ -58443,12 +59178,12 @@ var init_loader4 = __esm(() => {
58443
59178
  });
58444
59179
 
58445
59180
  // src/utils/paths.ts
58446
- import { join as join64 } from "path";
59181
+ import { join as join67 } from "path";
58447
59182
  function getRunsDir() {
58448
- return process.env.NAX_RUNS_DIR ?? join64(globalConfigDir(), "runs");
59183
+ return process.env.NAX_RUNS_DIR ?? join67(globalConfigDir(), "runs");
58449
59184
  }
58450
59185
  function getEventsRootDir() {
58451
- return join64(globalConfigDir(), "events");
59186
+ return join67(globalConfigDir(), "events");
58452
59187
  }
58453
59188
  var init_paths3 = __esm(() => {
58454
59189
  init_paths();
@@ -58508,7 +59243,7 @@ var init_command_argv = __esm(() => {
58508
59243
  });
58509
59244
 
58510
59245
  // src/hooks/runner.ts
58511
- import { join as join71 } from "path";
59246
+ import { join as join74 } from "path";
58512
59247
  function createDrainDeadline2(deadlineMs) {
58513
59248
  let timeoutId;
58514
59249
  const promise2 = new Promise((resolve16) => {
@@ -58527,14 +59262,14 @@ async function loadHooksConfig(projectDir, globalDir) {
58527
59262
  let globalHooks = { hooks: {} };
58528
59263
  let projectHooks = { hooks: {} };
58529
59264
  let skipGlobal = false;
58530
- const projectPath = join71(projectDir, "hooks.json");
59265
+ const projectPath = join74(projectDir, "hooks.json");
58531
59266
  const projectData = await loadJsonFile(projectPath, "hooks");
58532
59267
  if (projectData) {
58533
59268
  projectHooks = projectData;
58534
59269
  skipGlobal = projectData.skipGlobal ?? false;
58535
59270
  }
58536
59271
  if (!skipGlobal && globalDir) {
58537
- const globalPath = join71(globalDir, "hooks.json");
59272
+ const globalPath = join74(globalDir, "hooks.json");
58538
59273
  const globalData = await loadJsonFile(globalPath, "hooks");
58539
59274
  if (globalData) {
58540
59275
  globalHooks = globalData;
@@ -58704,7 +59439,7 @@ var package_default;
58704
59439
  var init_package = __esm(() => {
58705
59440
  package_default = {
58706
59441
  name: "@nathapp/nax",
58707
- version: "0.69.0",
59442
+ version: "0.69.1",
58708
59443
  description: "AI Coding Agent Orchestrator \u2014 loops until done",
58709
59444
  type: "module",
58710
59445
  bin: {
@@ -58799,8 +59534,8 @@ var init_version = __esm(() => {
58799
59534
  NAX_VERSION = package_default.version;
58800
59535
  NAX_COMMIT = (() => {
58801
59536
  try {
58802
- if (/^[0-9a-f]{6,10}$/.test("ce4d8f0e"))
58803
- return "ce4d8f0e";
59537
+ if (/^[0-9a-f]{6,10}$/.test("3b2af55b"))
59538
+ return "3b2af55b";
58804
59539
  } catch {}
58805
59540
  try {
58806
59541
  const result = Bun.spawnSync(["git", "rev-parse", "--short", "HEAD"], {
@@ -59676,16 +60411,16 @@ var init_acceptance_loop = __esm(() => {
59676
60411
  });
59677
60412
 
59678
60413
  // src/session/scratch-purge.ts
59679
- import { mkdir as mkdir13, rename, rm } from "fs/promises";
59680
- import { dirname as dirname12, join as join72 } from "path";
60414
+ import { mkdir as mkdir12, rename, rm } from "fs/promises";
60415
+ import { dirname as dirname12, join as join75 } from "path";
59681
60416
  async function purgeStaleScratch(projectDir, featureName, retentionDays, archiveInsteadOfDelete = false) {
59682
- const sessionsDir = join72(projectDir, ".nax", "features", featureName, "sessions");
60417
+ const sessionsDir = join75(projectDir, ".nax", "features", featureName, "sessions");
59683
60418
  const sessionIds = await _scratchPurgeDeps.listSessionDirs(sessionsDir);
59684
60419
  const cutoffMs = _scratchPurgeDeps.now() - retentionDays * 86400000;
59685
60420
  let purged = 0;
59686
60421
  for (const sessionId of sessionIds) {
59687
- const sessionDir = join72(sessionsDir, sessionId);
59688
- const descriptorPath = join72(sessionDir, "descriptor.json");
60422
+ const sessionDir = join75(sessionsDir, sessionId);
60423
+ const descriptorPath = join75(sessionDir, "descriptor.json");
59689
60424
  if (!await _scratchPurgeDeps.fileExists(descriptorPath))
59690
60425
  continue;
59691
60426
  let lastActivityAt;
@@ -59701,7 +60436,7 @@ async function purgeStaleScratch(projectDir, featureName, retentionDays, archive
59701
60436
  if (new Date(lastActivityAt).getTime() >= cutoffMs)
59702
60437
  continue;
59703
60438
  if (archiveInsteadOfDelete) {
59704
- const archiveDest = join72(projectDir, ".nax", "features", featureName, "_archive", "sessions", sessionId);
60439
+ const archiveDest = join75(projectDir, ".nax", "features", featureName, "_archive", "sessions", sessionId);
59705
60440
  await _scratchPurgeDeps.move(sessionDir, archiveDest);
59706
60441
  } else {
59707
60442
  await _scratchPurgeDeps.remove(sessionDir);
@@ -59728,7 +60463,7 @@ var init_scratch_purge = __esm(() => {
59728
60463
  readFile: (path19) => Bun.file(path19).text(),
59729
60464
  remove: (path19) => rm(path19, { recursive: true, force: true }),
59730
60465
  move: async (src, dest) => {
59731
- await mkdir13(dirname12(dest), { recursive: true });
60466
+ await mkdir12(dirname12(dest), { recursive: true });
59732
60467
  await rename(src, dest);
59733
60468
  },
59734
60469
  now: () => Date.now()
@@ -60438,19 +61173,19 @@ function precomputeBatchPlan(stories, maxBatchSize = DEFAULT_MAX_BATCH_SIZE) {
60438
61173
  var DEFAULT_MAX_BATCH_SIZE = 4;
60439
61174
 
60440
61175
  // src/pipeline/subscribers/events-writer.ts
60441
- import { appendFile as appendFile4, mkdir as mkdir14 } from "fs/promises";
60442
- import { basename as basename13, join as join73 } from "path";
61176
+ import { appendFile as appendFile4, mkdir as mkdir13 } from "fs/promises";
61177
+ import { basename as basename13, join as join76 } from "path";
60443
61178
  function wireEventsWriter(bus, feature, runId, workdir) {
60444
61179
  const logger = getSafeLogger();
60445
61180
  const project = basename13(workdir);
60446
- const eventsDir = join73(getEventsRootDir(), project);
60447
- const eventsFile = join73(eventsDir, "events.jsonl");
61181
+ const eventsDir = join76(getEventsRootDir(), project);
61182
+ const eventsFile = join76(eventsDir, "events.jsonl");
60448
61183
  let dirReady = false;
60449
61184
  const write = (line) => {
60450
61185
  return (async () => {
60451
61186
  try {
60452
61187
  if (!dirReady) {
60453
- await mkdir14(eventsDir, { recursive: true });
61188
+ await mkdir13(eventsDir, { recursive: true });
60454
61189
  dirReady = true;
60455
61190
  }
60456
61191
  await appendFile4(eventsFile, `${JSON.stringify(line)}
@@ -60624,24 +61359,24 @@ var init_interaction2 = __esm(() => {
60624
61359
  });
60625
61360
 
60626
61361
  // src/pipeline/subscribers/registry.ts
60627
- import { mkdir as mkdir15, writeFile as writeFile2 } from "fs/promises";
60628
- import { basename as basename14, join as join74 } from "path";
61362
+ import { mkdir as mkdir14, writeFile as writeFile2 } from "fs/promises";
61363
+ import { basename as basename14, join as join77 } from "path";
60629
61364
  function wireRegistry(bus, feature, runId, workdir, outputDir) {
60630
61365
  const logger = getSafeLogger();
60631
61366
  const project = basename14(workdir);
60632
- const runDir = join74(getRunsDir(), `${project}-${feature}-${runId}`);
60633
- const metaFile = join74(runDir, "meta.json");
61367
+ const runDir = join77(getRunsDir(), `${project}-${feature}-${runId}`);
61368
+ const metaFile = join77(runDir, "meta.json");
60634
61369
  const unsub = bus.on("run:started", (_ev) => {
60635
61370
  return (async () => {
60636
61371
  try {
60637
- await mkdir15(runDir, { recursive: true });
61372
+ await mkdir14(runDir, { recursive: true });
60638
61373
  const meta3 = {
60639
61374
  runId,
60640
61375
  project,
60641
61376
  feature,
60642
61377
  workdir,
60643
- statusPath: join74(outputDir, "features", feature, "status.json"),
60644
- eventsDir: join74(outputDir, "features", feature, "runs"),
61378
+ statusPath: join77(outputDir, "features", feature, "status.json"),
61379
+ eventsDir: join77(outputDir, "features", feature, "runs"),
60645
61380
  registeredAt: new Date().toISOString()
60646
61381
  };
60647
61382
  await writeFile2(metaFile, JSON.stringify(meta3, null, 2));
@@ -60886,8 +61621,8 @@ var init_types9 = __esm(() => {
60886
61621
  });
60887
61622
 
60888
61623
  // src/worktree/dependencies.ts
60889
- import { existsSync as existsSync31 } from "fs";
60890
- import { join as join75 } from "path";
61624
+ import { existsSync as existsSync30 } from "fs";
61625
+ import { join as join78 } from "path";
60891
61626
  async function prepareWorktreeDependencies(options) {
60892
61627
  const mode = options.config.execution.worktreeDependencies.mode;
60893
61628
  const resolvedCwd = resolveDependencyCwd(options);
@@ -60901,7 +61636,7 @@ async function prepareWorktreeDependencies(options) {
60901
61636
  }
60902
61637
  }
60903
61638
  function resolveDependencyCwd(options) {
60904
- return options.storyWorkdir ? join75(options.worktreeRoot, options.storyWorkdir) : options.worktreeRoot;
61639
+ return options.storyWorkdir ? join78(options.worktreeRoot, options.storyWorkdir) : options.worktreeRoot;
60905
61640
  }
60906
61641
  function resolveInheritedDependencies(options, resolvedCwd) {
60907
61642
  if (hasDependencyManifests(options.worktreeRoot, resolvedCwd)) {
@@ -60911,14 +61646,14 @@ function resolveInheritedDependencies(options, resolvedCwd) {
60911
61646
  }
60912
61647
  function hasDependencyManifests(worktreeRoot, resolvedCwd) {
60913
61648
  const directories = resolvedCwd === worktreeRoot ? [worktreeRoot] : [worktreeRoot, resolvedCwd];
60914
- return directories.some((directory) => PHASE_ONE_INHERIT_UNSUPPORTED_FILES.some((filename) => _worktreeDependencyDeps.existsSync(join75(directory, filename))));
61649
+ return directories.some((directory) => PHASE_ONE_INHERIT_UNSUPPORTED_FILES.some((filename) => _worktreeDependencyDeps.existsSync(join78(directory, filename))));
60915
61650
  }
60916
61651
  async function provisionDependencies(config2, worktreeRoot, resolvedCwd) {
60917
- const setupCommand = config2.execution.worktreeDependencies.setupCommand;
60918
- if (!setupCommand) {
61652
+ const setupCommand2 = config2.execution.worktreeDependencies.setupCommand;
61653
+ if (!setupCommand2) {
60919
61654
  throw new WorktreeDependencyPreparationError("[worktree-deps] provision mode requires execution.worktreeDependencies.setupCommand in phase 1.", "provision");
60920
61655
  }
60921
- const argv = parseCommandToArgv(setupCommand);
61656
+ const argv = parseCommandToArgv(setupCommand2);
60922
61657
  if (argv.length === 0) {
60923
61658
  throw new WorktreeDependencyPreparationError("[worktree-deps] setupCommand cannot be empty.", "provision");
60924
61659
  }
@@ -60962,7 +61697,7 @@ var init_dependencies = __esm(() => {
60962
61697
  "build.gradle.kts"
60963
61698
  ];
60964
61699
  _worktreeDependencyDeps = {
60965
- existsSync: existsSync31,
61700
+ existsSync: existsSync30,
60966
61701
  spawn
60967
61702
  };
60968
61703
  });
@@ -60973,19 +61708,19 @@ __export(exports_manager, {
60973
61708
  _managerDeps: () => _managerDeps,
60974
61709
  WorktreeManager: () => WorktreeManager
60975
61710
  });
60976
- import { existsSync as existsSync32, symlinkSync } from "fs";
60977
- import { mkdir as mkdir16 } from "fs/promises";
60978
- import { join as join76 } from "path";
61711
+ import { existsSync as existsSync31, symlinkSync } from "fs";
61712
+ import { mkdir as mkdir15 } from "fs/promises";
61713
+ import { join as join79 } from "path";
60979
61714
 
60980
61715
  class WorktreeManager {
60981
61716
  async ensureGitExcludes(projectRoot) {
60982
61717
  const logger = getSafeLogger();
60983
- const infoDir = join76(projectRoot, ".git", "info");
60984
- const excludePath = join76(infoDir, "exclude");
61718
+ const infoDir = join79(projectRoot, ".git", "info");
61719
+ const excludePath = join79(infoDir, "exclude");
60985
61720
  try {
60986
- await mkdir16(infoDir, { recursive: true });
61721
+ await mkdir15(infoDir, { recursive: true });
60987
61722
  let existing = "";
60988
- if (existsSync32(excludePath)) {
61723
+ if (existsSync31(excludePath)) {
60989
61724
  existing = await Bun.file(excludePath).text();
60990
61725
  }
60991
61726
  const missing = NAX_GITIGNORE_ENTRIES.filter((entry) => !existing.includes(entry));
@@ -61008,7 +61743,7 @@ ${missing.join(`
61008
61743
  }
61009
61744
  async create(projectRoot, storyId) {
61010
61745
  validateStoryId(storyId);
61011
- const worktreePath = join76(projectRoot, ".nax-wt", storyId);
61746
+ const worktreePath = join79(projectRoot, ".nax-wt", storyId);
61012
61747
  const branchName = `nax/${storyId}`;
61013
61748
  try {
61014
61749
  const pruneProc = _managerDeps.spawn(["git", "worktree", "prune"], {
@@ -61049,9 +61784,9 @@ ${missing.join(`
61049
61784
  }
61050
61785
  throw new Error(`Failed to create worktree: ${String(error48)}`);
61051
61786
  }
61052
- const envSource = join76(projectRoot, ".env");
61053
- if (existsSync32(envSource)) {
61054
- const envTarget = join76(worktreePath, ".env");
61787
+ const envSource = join79(projectRoot, ".env");
61788
+ if (existsSync31(envSource)) {
61789
+ const envTarget = join79(worktreePath, ".env");
61055
61790
  try {
61056
61791
  symlinkSync(envSource, envTarget, "file");
61057
61792
  } catch (error48) {
@@ -61062,7 +61797,7 @@ ${missing.join(`
61062
61797
  }
61063
61798
  async remove(projectRoot, storyId) {
61064
61799
  validateStoryId(storyId);
61065
- const worktreePath = join76(projectRoot, ".nax-wt", storyId);
61800
+ const worktreePath = join79(projectRoot, ".nax-wt", storyId);
61066
61801
  const branchName = `nax/${storyId}`;
61067
61802
  try {
61068
61803
  const proc = _managerDeps.spawn(["git", "worktree", "remove", worktreePath, "--force"], {
@@ -61874,10 +62609,10 @@ var init_merge_conflict_rectify = __esm(() => {
61874
62609
  });
61875
62610
 
61876
62611
  // src/execution/pipeline-result-handler.ts
61877
- import { join as join77 } from "path";
62612
+ import { join as join80 } from "path";
61878
62613
  async function removeWorktreeDirectory(projectRoot, storyId) {
61879
62614
  const logger = getSafeLogger();
61880
- const worktreePath = join77(projectRoot, ".nax-wt", storyId);
62615
+ const worktreePath = join80(projectRoot, ".nax-wt", storyId);
61881
62616
  try {
61882
62617
  const proc = _resultHandlerDeps.spawn(["git", "worktree", "remove", worktreePath, "--force"], {
61883
62618
  cwd: projectRoot,
@@ -62093,8 +62828,8 @@ var init_pipeline_result_handler = __esm(() => {
62093
62828
  });
62094
62829
 
62095
62830
  // src/execution/iteration-runner.ts
62096
- import { existsSync as existsSync33 } from "fs";
62097
- import { join as join78 } from "path";
62831
+ import { existsSync as existsSync32 } from "fs";
62832
+ import { join as join81 } from "path";
62098
62833
  async function runIteration(ctx, prd, selection, iterations, totalCost, allStoryMetrics) {
62099
62834
  const { story, storiesToExecute, routing, isBatchExecution } = selection;
62100
62835
  if (ctx.dryRun) {
@@ -62119,7 +62854,7 @@ async function runIteration(ctx, prd, selection, iterations, totalCost, allStory
62119
62854
  const storyStartTime = Date.now();
62120
62855
  let effectiveWorkdir = ctx.workdir;
62121
62856
  if (ctx.config.execution.storyIsolation === "worktree") {
62122
- const worktreePath = join78(ctx.workdir, ".nax-wt", story.id);
62857
+ const worktreePath = join81(ctx.workdir, ".nax-wt", story.id);
62123
62858
  const worktreeExists = _iterationRunnerDeps.existsSync(worktreePath);
62124
62859
  if (!worktreeExists) {
62125
62860
  await _iterationRunnerDeps.worktreeManager.ensureGitExcludes(ctx.workdir);
@@ -62139,7 +62874,7 @@ async function runIteration(ctx, prd, selection, iterations, totalCost, allStory
62139
62874
  }
62140
62875
  const accumulatedAttemptCost = (story.priorFailures || []).reduce((sum, f) => sum + (f.cost || 0), 0);
62141
62876
  const profileOverride = ctx.config.profile && ctx.config.profile !== "default" ? { profile: ctx.config.profile } : undefined;
62142
- const effectiveConfig = story.workdir ? await _iterationRunnerDeps.loadConfigForWorkdir(join78(ctx.workdir, ".nax", "config.json"), story.workdir, profileOverride) : ctx.config;
62877
+ const effectiveConfig = story.workdir ? await _iterationRunnerDeps.loadConfigForWorkdir(join81(ctx.workdir, ".nax", "config.json"), story.workdir, profileOverride) : ctx.config;
62143
62878
  let dependencyContext;
62144
62879
  if (ctx.config.execution.storyIsolation === "worktree") {
62145
62880
  try {
@@ -62166,7 +62901,7 @@ async function runIteration(ctx, prd, selection, iterations, totalCost, allStory
62166
62901
  };
62167
62902
  }
62168
62903
  }
62169
- const resolvedWorkdir = dependencyContext?.cwd ? dependencyContext.cwd : ctx.config.execution.storyIsolation === "worktree" ? story.workdir ? join78(effectiveWorkdir, story.workdir) : effectiveWorkdir : story.workdir ? join78(ctx.workdir, story.workdir) : ctx.workdir;
62904
+ 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;
62170
62905
  const pipelineContext = {
62171
62906
  config: effectiveConfig,
62172
62907
  rootConfig: ctx.config,
@@ -62298,7 +63033,7 @@ var init_iteration_runner = __esm(() => {
62298
63033
  loadConfigForWorkdir,
62299
63034
  prepareWorktreeDependencies,
62300
63035
  runPipeline,
62301
- existsSync: existsSync33,
63036
+ existsSync: existsSync32,
62302
63037
  worktreeManager: new WorktreeManager
62303
63038
  };
62304
63039
  });
@@ -62368,7 +63103,7 @@ __export(exports_parallel_worker, {
62368
63103
  buildWorktreePipelineContext: () => buildWorktreePipelineContext,
62369
63104
  _parallelWorkerDeps: () => _parallelWorkerDeps
62370
63105
  });
62371
- import { join as join79 } from "path";
63106
+ import { join as join82 } from "path";
62372
63107
  function buildWorktreePipelineContext(base, _story) {
62373
63108
  return { ...base, prd: structuredClone(base.prd) };
62374
63109
  }
@@ -62391,7 +63126,7 @@ async function executeStoryInWorktree(story, worktreePath, dependencyContext, co
62391
63126
  story,
62392
63127
  stories: [story],
62393
63128
  projectDir: context.projectDir,
62394
- workdir: dependencyContext.cwd ?? (story.workdir ? join79(worktreePath, story.workdir) : worktreePath),
63129
+ workdir: dependencyContext.cwd ?? (story.workdir ? join82(worktreePath, story.workdir) : worktreePath),
62395
63130
  worktreeDependencyContext: dependencyContext,
62396
63131
  routing,
62397
63132
  storyGitRef: storyGitRef ?? undefined
@@ -63278,7 +64013,7 @@ async function writeStatusFile(filePath, status) {
63278
64013
  var init_status_file = () => {};
63279
64014
 
63280
64015
  // src/execution/status-writer.ts
63281
- import { join as join80 } from "path";
64016
+ import { join as join83 } from "path";
63282
64017
 
63283
64018
  class StatusWriter {
63284
64019
  statusFile;
@@ -63397,7 +64132,7 @@ class StatusWriter {
63397
64132
  if (!this._prd)
63398
64133
  return;
63399
64134
  const safeLogger = getSafeLogger();
63400
- const featureStatusPath = join80(featureDir, "status.json");
64135
+ const featureStatusPath = join83(featureDir, "status.json");
63401
64136
  const write = async () => {
63402
64137
  try {
63403
64138
  const base = this.getSnapshot(totalCost, iterations);
@@ -63428,11 +64163,11 @@ __export(exports_migrate, {
63428
64163
  migrateCommand: () => migrateCommand,
63429
64164
  detectGeneratedContent: () => detectGeneratedContent
63430
64165
  });
63431
- import { existsSync as existsSync34 } from "fs";
63432
- import { mkdir as mkdir17, readdir as readdir6, rename as rename3 } from "fs/promises";
64166
+ import { existsSync as existsSync33 } from "fs";
64167
+ import { mkdir as mkdir16, readdir as readdir6, rename as rename3 } from "fs/promises";
63433
64168
  import path22 from "path";
63434
64169
  async function detectGeneratedContent(naxDir) {
63435
- if (!existsSync34(naxDir))
64170
+ if (!existsSync33(naxDir))
63436
64171
  return [];
63437
64172
  const candidates = [];
63438
64173
  let entries = [];
@@ -63447,7 +64182,7 @@ async function detectGeneratedContent(naxDir) {
63447
64182
  }
63448
64183
  }
63449
64184
  const featuresDir = path22.join(naxDir, "features");
63450
- if (existsSync34(featuresDir)) {
64185
+ if (existsSync33(featuresDir)) {
63451
64186
  let featureDirs = [];
63452
64187
  try {
63453
64188
  featureDirs = await readdir6(featuresDir);
@@ -63509,7 +64244,7 @@ async function migrateCommand(options) {
63509
64244
  });
63510
64245
  }
63511
64246
  const src = path22.join(globalConfigDir(), options.reclaim);
63512
- if (!existsSync34(src)) {
64247
+ if (!existsSync33(src)) {
63513
64248
  throw new NaxError(`Nothing to reclaim: ~/.nax/${options.reclaim} does not exist`, "MIGRATE_RECLAIM_NOT_FOUND", {
63514
64249
  stage: "migrate",
63515
64250
  name: options.reclaim
@@ -63517,7 +64252,7 @@ async function migrateCommand(options) {
63517
64252
  }
63518
64253
  const archiveBase = path22.join(globalConfigDir(), "_archive");
63519
64254
  const archiveDest = path22.join(archiveBase, `${options.reclaim}-${Date.now()}`);
63520
- await mkdir17(archiveBase, { recursive: true });
64255
+ await mkdir16(archiveBase, { recursive: true });
63521
64256
  await rename3(src, archiveDest);
63522
64257
  logger.info("migrate", `Reclaimed: archived to ${archiveDest}`, { storyId: "_migrate" });
63523
64258
  return;
@@ -63555,7 +64290,7 @@ async function migrateCommand(options) {
63555
64290
  }
63556
64291
  const naxDir = path22.join(options.workdir, ".nax");
63557
64292
  const configPath = path22.join(naxDir, "config.json");
63558
- if (!existsSync34(configPath)) {
64293
+ if (!existsSync33(configPath)) {
63559
64294
  throw new NaxError("No .nax/config.json found \u2014 run nax init first", "MIGRATE_NO_CONFIG", {
63560
64295
  stage: "migrate",
63561
64296
  workdir: options.workdir
@@ -63585,12 +64320,12 @@ async function migrateCommand(options) {
63585
64320
  }
63586
64321
  return;
63587
64322
  }
63588
- await mkdir17(destBase, { recursive: true });
64323
+ await mkdir16(destBase, { recursive: true });
63589
64324
  let moved = 0;
63590
64325
  for (const candidate of candidates) {
63591
64326
  const dest = path22.join(destBase, candidate.name);
63592
- await mkdir17(path22.dirname(dest), { recursive: true });
63593
- if (existsSync34(dest)) {
64327
+ await mkdir16(path22.dirname(dest), { recursive: true });
64328
+ if (existsSync33(dest)) {
63594
64329
  throw new NaxError(`Migration conflict: destination already exists.
63595
64330
  Source: ${candidate.srcPath}
63596
64331
  Destination: ${dest}
@@ -63831,7 +64566,7 @@ __export(exports_run_initialization, {
63831
64566
  initializeRun: () => initializeRun,
63832
64567
  _reconcileDeps: () => _reconcileDeps
63833
64568
  });
63834
- import { join as join81 } from "path";
64569
+ import { join as join84 } from "path";
63835
64570
  async function reconcileState(prd, prdPath, workdir, config2) {
63836
64571
  const logger = getSafeLogger();
63837
64572
  let reconciledCount = 0;
@@ -63848,7 +64583,7 @@ async function reconcileState(prd, prdPath, workdir, config2) {
63848
64583
  });
63849
64584
  continue;
63850
64585
  }
63851
- const effectiveWorkdir = story.workdir ? join81(workdir, story.workdir) : workdir;
64586
+ const effectiveWorkdir = story.workdir ? join84(workdir, story.workdir) : workdir;
63852
64587
  try {
63853
64588
  const reviewResult = await _reconcileDeps.runReview(config2.review, effectiveWorkdir, config2.execution);
63854
64589
  if (!reviewResult.success) {
@@ -93641,7 +94376,7 @@ __export(exports_curator, {
93641
94376
  });
93642
94377
  import { readdirSync as readdirSync8 } from "fs";
93643
94378
  import { unlink as unlink4 } from "fs/promises";
93644
- import { basename as basename15, join as join83 } from "path";
94379
+ import { basename as basename15, join as join86 } from "path";
93645
94380
  function getProjectKey(config2, projectDir) {
93646
94381
  return config2.name?.trim() || basename15(projectDir);
93647
94382
  }
@@ -93724,7 +94459,7 @@ async function curatorStatus(options) {
93724
94459
  const config2 = await _curatorCmdDeps.loadConfig(resolved.projectDir);
93725
94460
  const projectKey = getProjectKey(config2, resolved.projectDir);
93726
94461
  const outputDir = _curatorCmdDeps.projectOutputDir(projectKey, config2.outputDir);
93727
- const runsDir = join83(outputDir, "runs");
94462
+ const runsDir = join86(outputDir, "runs");
93728
94463
  const runIds = listRunIds(runsDir);
93729
94464
  let runId;
93730
94465
  if (options.run) {
@@ -93741,8 +94476,8 @@ async function curatorStatus(options) {
93741
94476
  runId = runIds[runIds.length - 1];
93742
94477
  }
93743
94478
  console.log(`Run: ${runId}`);
93744
- const runDir = join83(runsDir, runId);
93745
- const observationsPath = join83(runDir, "observations.jsonl");
94479
+ const runDir = join86(runsDir, runId);
94480
+ const observationsPath = join86(runDir, "observations.jsonl");
93746
94481
  const observations = await parseObservations(observationsPath);
93747
94482
  const counts = new Map;
93748
94483
  for (const obs of observations) {
@@ -93752,7 +94487,7 @@ async function curatorStatus(options) {
93752
94487
  for (const [kind, count] of counts.entries()) {
93753
94488
  console.log(` ${kind}: ${count}`);
93754
94489
  }
93755
- const proposalsPath = join83(runDir, "curator-proposals.md");
94490
+ const proposalsPath = join86(runDir, "curator-proposals.md");
93756
94491
  const proposalText = await _curatorCmdDeps.readFile(proposalsPath).catch(() => null);
93757
94492
  if (proposalText !== null) {
93758
94493
  console.log("");
@@ -93766,8 +94501,8 @@ async function curatorCommit(options) {
93766
94501
  const config2 = await _curatorCmdDeps.loadConfig(resolved.projectDir);
93767
94502
  const projectKey = getProjectKey(config2, resolved.projectDir);
93768
94503
  const outputDir = _curatorCmdDeps.projectOutputDir(projectKey, config2.outputDir);
93769
- const runDir = join83(outputDir, "runs", options.runId);
93770
- const proposalsPath = join83(runDir, "curator-proposals.md");
94504
+ const runDir = join86(outputDir, "runs", options.runId);
94505
+ const proposalsPath = join86(runDir, "curator-proposals.md");
93771
94506
  const proposalText = await _curatorCmdDeps.readFile(proposalsPath).catch(() => null);
93772
94507
  if (proposalText === null) {
93773
94508
  console.log(`curator-proposals.md not found for run ${options.runId}.`);
@@ -93783,7 +94518,7 @@ async function curatorCommit(options) {
93783
94518
  const dropFileState = new Map;
93784
94519
  const skippedDrops = new Set;
93785
94520
  for (const drop2 of drops) {
93786
- const targetPath = join83(resolved.projectDir, drop2.canonicalFile);
94521
+ const targetPath = join86(resolved.projectDir, drop2.canonicalFile);
93787
94522
  if (!dropFileState.has(targetPath)) {
93788
94523
  const fileExists2 = await Bun.file(targetPath).exists();
93789
94524
  const existing = fileExists2 ? await _curatorCmdDeps.readFile(targetPath).catch(() => "") : "";
@@ -93817,7 +94552,7 @@ async function curatorCommit(options) {
93817
94552
  if (skippedDrops.has(drop2)) {
93818
94553
  continue;
93819
94554
  }
93820
- const targetPath = join83(resolved.projectDir, drop2.canonicalFile);
94555
+ const targetPath = join86(resolved.projectDir, drop2.canonicalFile);
93821
94556
  const existing = await _curatorCmdDeps.readFile(targetPath).catch(() => "");
93822
94557
  const filtered = filterDropContent(existing, drop2.description);
93823
94558
  await _curatorCmdDeps.writeFile(targetPath, filtered);
@@ -93826,7 +94561,7 @@ async function curatorCommit(options) {
93826
94561
  }
93827
94562
  const adds = proposals.filter((p) => p.action === "add" || p.action === "advisory");
93828
94563
  for (const add2 of adds) {
93829
- const targetPath = join83(resolved.projectDir, add2.canonicalFile);
94564
+ const targetPath = join86(resolved.projectDir, add2.canonicalFile);
93830
94565
  const content = buildAddContent(add2);
93831
94566
  await _curatorCmdDeps.appendFile(targetPath, content);
93832
94567
  modifiedFiles.add(targetPath);
@@ -93863,7 +94598,7 @@ async function curatorDryrun(options) {
93863
94598
  const config2 = await _curatorCmdDeps.loadConfig(resolved.projectDir);
93864
94599
  const projectKey = getProjectKey(config2, resolved.projectDir);
93865
94600
  const outputDir = _curatorCmdDeps.projectOutputDir(projectKey, config2.outputDir);
93866
- const runsDir = join83(outputDir, "runs");
94601
+ const runsDir = join86(outputDir, "runs");
93867
94602
  const runIds = listRunIds(runsDir);
93868
94603
  if (runIds.length === 0) {
93869
94604
  console.log("No runs found.");
@@ -93874,7 +94609,7 @@ async function curatorDryrun(options) {
93874
94609
  console.log(`Run ${options.run} not found in ${runsDir}.`);
93875
94610
  return;
93876
94611
  }
93877
- const observationsPath = join83(runsDir, runId, "observations.jsonl");
94612
+ const observationsPath = join86(runsDir, runId, "observations.jsonl");
93878
94613
  const observations = await parseObservations(observationsPath);
93879
94614
  const thresholds = getThresholds(config2);
93880
94615
  const proposals = runHeuristics(observations, thresholds);
@@ -93915,12 +94650,12 @@ async function curatorGc(options) {
93915
94650
  await _curatorCmdDeps.writeFile(rollupPath, newContent);
93916
94651
  const projectKey = getProjectKey(config2, resolved.projectDir);
93917
94652
  const outputDir = _curatorCmdDeps.projectOutputDir(projectKey, config2.outputDir);
93918
- const perRunsDir = join83(outputDir, "runs");
94653
+ const perRunsDir = join86(outputDir, "runs");
93919
94654
  for (const runId of uniqueRunIds) {
93920
94655
  if (!keepSet.has(runId)) {
93921
- const runDir = join83(perRunsDir, runId);
93922
- await _curatorCmdDeps.removeFile(join83(runDir, "observations.jsonl"));
93923
- await _curatorCmdDeps.removeFile(join83(runDir, "curator-proposals.md"));
94656
+ const runDir = join86(perRunsDir, runId);
94657
+ await _curatorCmdDeps.removeFile(join86(runDir, "observations.jsonl"));
94658
+ await _curatorCmdDeps.removeFile(join86(runDir, "curator-proposals.md"));
93924
94659
  }
93925
94660
  }
93926
94661
  console.log(`[gc] Pruned rollup to ${keep} most recent runs (was ${uniqueRunIds.length}).`);
@@ -93963,9 +94698,9 @@ var init_curator2 = __esm(() => {
93963
94698
 
93964
94699
  // bin/nax.ts
93965
94700
  init_source();
93966
- import { existsSync as existsSync36, mkdirSync as mkdirSync7 } from "fs";
94701
+ import { existsSync as existsSync35, mkdirSync as mkdirSync7 } from "fs";
93967
94702
  import { homedir as homedir3 } from "os";
93968
- import { basename as basename16, join as join84 } from "path";
94703
+ import { basename as basename16, join as join87 } from "path";
93969
94704
 
93970
94705
  // node_modules/commander/esm.mjs
93971
94706
  var import__ = __toESM(require_commander(), 1);
@@ -95055,6 +95790,7 @@ async function runsShowCommand(options) {
95055
95790
  // src/cli/index.ts
95056
95791
  init_prompts2();
95057
95792
  init_init2();
95793
+ init_setup();
95058
95794
 
95059
95795
  // src/cli/plugins.ts
95060
95796
  init_paths();
@@ -95129,8 +95865,8 @@ init_interaction();
95129
95865
  init_source();
95130
95866
  init_loader();
95131
95867
  init_generator2();
95132
- import { existsSync as existsSync25 } from "fs";
95133
- import { join as join58 } from "path";
95868
+ import { existsSync as existsSync24 } from "fs";
95869
+ import { join as join61 } from "path";
95134
95870
  var VALID_AGENTS = ["claude", "codex", "opencode", "cursor", "windsurf", "aider", "gemini"];
95135
95871
  async function generateCommand(options) {
95136
95872
  const workdir = options.dir ?? process.cwd();
@@ -95173,7 +95909,7 @@ async function generateCommand(options) {
95173
95909
  return;
95174
95910
  }
95175
95911
  if (options.package) {
95176
- const packageDir = join58(workdir, options.package);
95912
+ const packageDir = join61(workdir, options.package);
95177
95913
  if (dryRun) {
95178
95914
  console.log(source_default.yellow("\u26A0 Dry run \u2014 no files will be written"));
95179
95915
  }
@@ -95193,10 +95929,10 @@ async function generateCommand(options) {
95193
95929
  process.exit(1);
95194
95930
  return;
95195
95931
  }
95196
- const contextPath = options.context ? join58(workdir, options.context) : join58(workdir, ".nax/context.md");
95197
- const outputDir = options.output ? join58(workdir, options.output) : workdir;
95932
+ const contextPath = options.context ? join61(workdir, options.context) : join61(workdir, ".nax/context.md");
95933
+ const outputDir = options.output ? join61(workdir, options.output) : workdir;
95198
95934
  const autoInject = !options.noAutoInject;
95199
- if (!existsSync25(contextPath)) {
95935
+ if (!existsSync24(contextPath)) {
95200
95936
  console.error(source_default.red(`\u2717 Context file not found: ${contextPath}`));
95201
95937
  console.error(source_default.yellow(" Create .nax/context.md first, or run `nax init` to scaffold it."));
95202
95938
  process.exit(1);
@@ -95299,8 +96035,8 @@ async function generateCommand(options) {
95299
96035
  }
95300
96036
  // src/cli/config-display.ts
95301
96037
  init_loader();
95302
- import { existsSync as existsSync27 } from "fs";
95303
- import { join as join60 } from "path";
96038
+ import { existsSync as existsSync26 } from "fs";
96039
+ import { join as join63 } from "path";
95304
96040
 
95305
96041
  // src/cli/config-descriptions.ts
95306
96042
  var FIELD_DESCRIPTIONS = {
@@ -95551,10 +96287,10 @@ function deepEqual(a, b) {
95551
96287
  // src/cli/config-get.ts
95552
96288
  init_defaults();
95553
96289
  init_loader();
95554
- import { existsSync as existsSync26 } from "fs";
95555
- import { join as join59 } from "path";
96290
+ import { existsSync as existsSync25 } from "fs";
96291
+ import { join as join62 } from "path";
95556
96292
  async function loadConfigFile(path18) {
95557
- if (!existsSync26(path18))
96293
+ if (!existsSync25(path18))
95558
96294
  return null;
95559
96295
  try {
95560
96296
  return await Bun.file(path18).json();
@@ -95574,7 +96310,7 @@ async function loadProjectConfig() {
95574
96310
  const projectDir = findProjectDir();
95575
96311
  if (!projectDir)
95576
96312
  return null;
95577
- const projectPath = join59(projectDir, "config.json");
96313
+ const projectPath = join62(projectDir, "config.json");
95578
96314
  return await loadConfigFile(projectPath);
95579
96315
  }
95580
96316
 
@@ -95634,14 +96370,14 @@ async function configCommand(config2, options = {}) {
95634
96370
  function determineConfigSources() {
95635
96371
  const globalPath = globalConfigPath();
95636
96372
  const projectDir = findProjectDir();
95637
- const projectPath = projectDir ? join60(projectDir, "config.json") : null;
96373
+ const projectPath = projectDir ? join63(projectDir, "config.json") : null;
95638
96374
  return {
95639
96375
  global: fileExists(globalPath) ? globalPath : null,
95640
96376
  project: projectPath && fileExists(projectPath) ? projectPath : null
95641
96377
  };
95642
96378
  }
95643
96379
  function fileExists(path18) {
95644
- return existsSync27(path18);
96380
+ return existsSync26(path18);
95645
96381
  }
95646
96382
  function displayConfigWithDescriptions(obj, path18, sources, indent = 0) {
95647
96383
  const indentStr = " ".repeat(indent);
@@ -95783,15 +96519,15 @@ init_paths();
95783
96519
  init_profile();
95784
96520
  import { mkdirSync as mkdirSync5 } from "fs";
95785
96521
  import { readdirSync as readdirSync5 } from "fs";
95786
- import { join as join61 } from "path";
96522
+ import { join as join64 } from "path";
95787
96523
  var _profileCLIDeps = {
95788
96524
  env: process.env
95789
96525
  };
95790
96526
  var SENSITIVE_KEY_PATTERN = /key|token|secret|password|credential/i;
95791
96527
  var VAR_PATTERN = /\$[A-Za-z_][A-Za-z0-9_]*/;
95792
96528
  async function profileListCommand(startDir) {
95793
- const globalProfilesDir = join61(globalConfigDir(), "profiles");
95794
- const projectProfilesDir = join61(projectConfigDir(startDir), "profiles");
96529
+ const globalProfilesDir = join64(globalConfigDir(), "profiles");
96530
+ const projectProfilesDir = join64(projectConfigDir(startDir), "profiles");
95795
96531
  const globalProfiles = scanProfileDir(globalProfilesDir);
95796
96532
  const projectProfiles = scanProfileDir(projectProfilesDir);
95797
96533
  const activeProfile = await resolveProfileName({}, _profileCLIDeps.env, startDir);
@@ -95850,7 +96586,7 @@ function maskProfileValues(obj) {
95850
96586
  return result;
95851
96587
  }
95852
96588
  async function profileUseCommand(profileName, startDir) {
95853
- const configPath = join61(projectConfigDir(startDir), "config.json");
96589
+ const configPath = join64(projectConfigDir(startDir), "config.json");
95854
96590
  const configFile = Bun.file(configPath);
95855
96591
  let existing = {};
95856
96592
  if (await configFile.exists()) {
@@ -95869,8 +96605,8 @@ async function profileCurrentCommand(startDir) {
95869
96605
  return resolveProfileName({}, _profileCLIDeps.env, startDir);
95870
96606
  }
95871
96607
  async function profileCreateCommand(profileName, startDir) {
95872
- const profilesDir = join61(projectConfigDir(startDir), "profiles");
95873
- const profilePath = join61(profilesDir, `${profileName}.json`);
96608
+ const profilesDir = join64(projectConfigDir(startDir), "profiles");
96609
+ const profilePath = join64(profilesDir, `${profileName}.json`);
95874
96610
  const profileFile = Bun.file(profilePath);
95875
96611
  if (await profileFile.exists()) {
95876
96612
  throw new Error(`Profile "${profileName}" already exists at ${profilePath}`);
@@ -95991,8 +96727,8 @@ async function contextInspectCommand(options) {
95991
96727
  // src/cli/rules.ts
95992
96728
  init_canonical_loader();
95993
96729
  init_errors();
95994
- import { mkdir as mkdir12 } from "fs/promises";
95995
- import { basename as basename12, join as join62 } from "path";
96730
+ import { mkdir as mkdir11 } from "fs/promises";
96731
+ import { basename as basename12, join as join65 } from "path";
95996
96732
  var _rulesCLIDeps = {
95997
96733
  readFile: async (path18) => Bun.file(path18).text(),
95998
96734
  writeFile: async (path18, content) => {
@@ -96001,13 +96737,13 @@ var _rulesCLIDeps = {
96001
96737
  fileExists: async (path18) => Bun.file(path18).exists(),
96002
96738
  globInDir: (dir) => {
96003
96739
  try {
96004
- return [...new Bun.Glob("*.md").scanSync({ cwd: dir })].sort().map((f) => join62(dir, f));
96740
+ return [...new Bun.Glob("*.md").scanSync({ cwd: dir })].sort().map((f) => join65(dir, f));
96005
96741
  } catch {
96006
96742
  return [];
96007
96743
  }
96008
96744
  },
96009
96745
  mkdir: async (path18) => {
96010
- await mkdir12(path18, { recursive: true });
96746
+ await mkdir11(path18, { recursive: true });
96011
96747
  },
96012
96748
  globCanonicalRuleFiles: (workdir) => {
96013
96749
  try {
@@ -96050,7 +96786,7 @@ ${r.content}`).join(`
96050
96786
  `);
96051
96787
  const shimContent = `${header + body}
96052
96788
  `;
96053
- const shimPath = join62(workdir, shimFileName);
96789
+ const shimPath = join65(workdir, shimFileName);
96054
96790
  if (options.dryRun) {
96055
96791
  console.log(`[dry-run] Would write ${shimPath} (${shimContent.length} bytes)`);
96056
96792
  return;
@@ -96079,14 +96815,14 @@ function neutralizeContent(content) {
96079
96815
  }
96080
96816
  async function collectMigrationSources(workdir) {
96081
96817
  const sources = [];
96082
- const claudeMdPath = join62(workdir, "CLAUDE.md");
96818
+ const claudeMdPath = join65(workdir, "CLAUDE.md");
96083
96819
  if (await _rulesCLIDeps.fileExists(claudeMdPath)) {
96084
96820
  const content = await _rulesCLIDeps.readFile(claudeMdPath);
96085
96821
  if (content.trim()) {
96086
96822
  sources.push({ sourcePath: claudeMdPath, targetFileName: "project-conventions.md", content });
96087
96823
  }
96088
96824
  }
96089
- const rulesDir = join62(workdir, ".claude", "rules");
96825
+ const rulesDir = join65(workdir, ".claude", "rules");
96090
96826
  const ruleFiles = _rulesCLIDeps.globInDir(rulesDir);
96091
96827
  for (const filePath of ruleFiles) {
96092
96828
  try {
@@ -96106,7 +96842,7 @@ async function rulesMigrateCommand(options) {
96106
96842
  console.log("[WARN] No source files found (checked CLAUDE.md and .claude/rules/*.md). Nothing to migrate.");
96107
96843
  return;
96108
96844
  }
96109
- const targetDir = join62(workdir, CANONICAL_RULES_DIR);
96845
+ const targetDir = join65(workdir, CANONICAL_RULES_DIR);
96110
96846
  if (!options.dryRun) {
96111
96847
  try {
96112
96848
  await _rulesCLIDeps.mkdir(targetDir);
@@ -96117,7 +96853,7 @@ async function rulesMigrateCommand(options) {
96117
96853
  let written = 0;
96118
96854
  let skipped = 0;
96119
96855
  for (const { sourcePath, targetFileName, content } of sources) {
96120
- const targetPath = join62(targetDir, targetFileName);
96856
+ const targetPath = join65(targetDir, targetFileName);
96121
96857
  if (!force && !options.dryRun && await _rulesCLIDeps.fileExists(targetPath)) {
96122
96858
  console.log(`[skip] ${targetFileName} already exists (use --force to overwrite)`);
96123
96859
  skipped++;
@@ -96156,7 +96892,7 @@ function collectCanonicalRuleRoots(workdir) {
96156
96892
  const packageRel = normalized.slice(0, idx);
96157
96893
  if (!packageRel)
96158
96894
  continue;
96159
- roots.add(join62(workdir, packageRel));
96895
+ roots.add(join65(workdir, packageRel));
96160
96896
  }
96161
96897
  return [...roots].sort();
96162
96898
  }
@@ -96178,7 +96914,7 @@ init_logger2();
96178
96914
  init_detect2();
96179
96915
  init_workspace();
96180
96916
  init_common();
96181
- import { join as join63 } from "path";
96917
+ import { join as join66 } from "path";
96182
96918
  function resolveEffective(detected, configPatterns) {
96183
96919
  if (configPatterns !== undefined)
96184
96920
  return "config";
@@ -96263,7 +96999,7 @@ async function detectCommand(options) {
96263
96999
  const rootDetected = detectionMap[""] ?? { patterns: [], confidence: "empty", sources: [] };
96264
97000
  const pkgEntries = await Promise.all(packageDirs.map(async (dir) => {
96265
97001
  const det = detectionMap[dir] ?? { patterns: [], confidence: "empty", sources: [] };
96266
- const pkgConfigPath = join63(workdir, ".nax", "mono", dir, "config.json");
97002
+ const pkgConfigPath = join66(workdir, ".nax", "mono", dir, "config.json");
96267
97003
  const pkgRaw = await loadRawConfig(pkgConfigPath);
96268
97004
  const pkgPatterns = deepGet(pkgRaw, TEST_PATTERNS_KEY);
96269
97005
  const effective = Array.isArray(pkgPatterns) ? pkgPatterns : undefined;
@@ -96317,13 +97053,13 @@ async function detectCommand(options) {
96317
97053
  if (rootDetected.confidence === "empty") {
96318
97054
  console.log(source_default.yellow(" root: skipped (empty detection)"));
96319
97055
  } else {
96320
- const rootConfigPath = join63(workdir, ".nax", "config.json");
97056
+ const rootConfigPath = join66(workdir, ".nax", "config.json");
96321
97057
  try {
96322
97058
  const status = await applyToConfig(rootConfigPath, rootDetected.patterns, options.force ?? false);
96323
97059
  if (status === "skipped") {
96324
97060
  console.log(source_default.dim(" root: skipped (testFilePatterns already set; use --force to overwrite)"));
96325
97061
  } else {
96326
- console.log(source_default.green(` root: ${status} \u2192 ${join63(".nax", "config.json")}`));
97062
+ console.log(source_default.green(` root: ${status} \u2192 ${join66(".nax", "config.json")}`));
96327
97063
  }
96328
97064
  } catch (err) {
96329
97065
  console.error(source_default.red(` root: write failed \u2014 ${err.message}`));
@@ -96336,13 +97072,13 @@ async function detectCommand(options) {
96336
97072
  console.log(source_default.dim(` ${dir}: skipped (empty detection)`));
96337
97073
  continue;
96338
97074
  }
96339
- const pkgConfigPath = join63(workdir, ".nax", "mono", dir, "config.json");
97075
+ const pkgConfigPath = join66(workdir, ".nax", "mono", dir, "config.json");
96340
97076
  try {
96341
97077
  const status = await applyToConfig(pkgConfigPath, det.patterns, options.force ?? false);
96342
97078
  if (status === "skipped") {
96343
97079
  console.log(source_default.dim(` ${dir}: skipped (already set)`));
96344
97080
  } else {
96345
- console.log(source_default.green(` ${dir}: ${status} \u2192 ${join63(".nax", "mono", dir, "config.json")}`));
97081
+ console.log(source_default.green(` ${dir}: ${status} \u2192 ${join66(".nax", "mono", dir, "config.json")}`));
96346
97082
  }
96347
97083
  } catch (err) {
96348
97084
  console.error(source_default.red(` ${dir}: write failed \u2014 ${err.message}`));
@@ -96359,20 +97095,20 @@ async function detectCommand(options) {
96359
97095
 
96360
97096
  // src/commands/logs.ts
96361
97097
  init_common();
96362
- import { existsSync as existsSync29 } from "fs";
96363
- import { join as join67 } from "path";
97098
+ import { existsSync as existsSync28 } from "fs";
97099
+ import { join as join70 } from "path";
96364
97100
 
96365
97101
  // src/commands/logs-formatter.ts
96366
97102
  init_source();
96367
97103
  init_formatter();
96368
97104
  import { readdirSync as readdirSync7 } from "fs";
96369
- import { join as join66 } from "path";
97105
+ import { join as join69 } from "path";
96370
97106
 
96371
97107
  // src/commands/logs-reader.ts
96372
97108
  init_paths3();
96373
- import { existsSync as existsSync28, readdirSync as readdirSync6 } from "fs";
97109
+ import { existsSync as existsSync27, readdirSync as readdirSync6 } from "fs";
96374
97110
  import { readdir as readdir4 } from "fs/promises";
96375
- import { join as join65 } from "path";
97111
+ import { join as join68 } from "path";
96376
97112
  var _logsReaderDeps = {
96377
97113
  getRunsDir
96378
97114
  };
@@ -96386,7 +97122,7 @@ async function resolveRunFileFromRegistry(runId) {
96386
97122
  }
96387
97123
  let matched = null;
96388
97124
  for (const entry of entries) {
96389
- const metaPath = join65(runsDir, entry, "meta.json");
97125
+ const metaPath = join68(runsDir, entry, "meta.json");
96390
97126
  try {
96391
97127
  const meta3 = await Bun.file(metaPath).json();
96392
97128
  if (meta3.runId === runId || meta3.runId.startsWith(runId)) {
@@ -96398,7 +97134,7 @@ async function resolveRunFileFromRegistry(runId) {
96398
97134
  if (!matched) {
96399
97135
  throw new Error(`Run not found in registry: ${runId}`);
96400
97136
  }
96401
- if (!existsSync28(matched.eventsDir)) {
97137
+ if (!existsSync27(matched.eventsDir)) {
96402
97138
  console.log(`Log directory unavailable for run: ${runId}`);
96403
97139
  return null;
96404
97140
  }
@@ -96408,14 +97144,14 @@ async function resolveRunFileFromRegistry(runId) {
96408
97144
  return null;
96409
97145
  }
96410
97146
  const specificFile = files.find((f) => f === `${matched.runId}.jsonl`);
96411
- return join65(matched.eventsDir, specificFile ?? files[0]);
97147
+ return join68(matched.eventsDir, specificFile ?? files[0]);
96412
97148
  }
96413
97149
  async function selectRunFile(runsDir) {
96414
97150
  const files = readdirSync6(runsDir).filter((f) => f.endsWith(".jsonl") && f !== "latest.jsonl").sort().reverse();
96415
97151
  if (files.length === 0) {
96416
97152
  return null;
96417
97153
  }
96418
- return join65(runsDir, files[0]);
97154
+ return join68(runsDir, files[0]);
96419
97155
  }
96420
97156
  async function extractRunSummary(filePath) {
96421
97157
  const file3 = Bun.file(filePath);
@@ -96501,7 +97237,7 @@ Runs:
96501
97237
  console.log(source_default.gray(" Timestamp Stories Duration Cost Status"));
96502
97238
  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"));
96503
97239
  for (const file3 of files) {
96504
- const filePath = join66(runsDir, file3);
97240
+ const filePath = join69(runsDir, file3);
96505
97241
  const summary = await extractRunSummary(filePath);
96506
97242
  const timestamp = file3.replace(".jsonl", "");
96507
97243
  const stories = summary ? `${summary.passed}/${summary.total}` : "?/?";
@@ -96615,7 +97351,7 @@ async function logsCommand(options) {
96615
97351
  return;
96616
97352
  }
96617
97353
  const resolved = resolveProject({ dir: options.dir });
96618
- const naxDir = join67(resolved.projectDir, ".nax");
97354
+ const naxDir = join70(resolved.projectDir, ".nax");
96619
97355
  const configPath = resolved.configPath;
96620
97356
  const configFile = Bun.file(configPath);
96621
97357
  const config2 = await configFile.json();
@@ -96623,9 +97359,9 @@ async function logsCommand(options) {
96623
97359
  if (!featureName) {
96624
97360
  throw new Error("No feature specified in config.json");
96625
97361
  }
96626
- const featureDir = join67(naxDir, "features", featureName);
96627
- const runsDir = join67(featureDir, "runs");
96628
- if (!existsSync29(runsDir)) {
97362
+ const featureDir = join70(naxDir, "features", featureName);
97363
+ const runsDir = join70(featureDir, "runs");
97364
+ if (!existsSync28(runsDir)) {
96629
97365
  throw new Error(`No runs directory found for feature: ${featureName}`);
96630
97366
  }
96631
97367
  if (options.list) {
@@ -96649,8 +97385,8 @@ init_config();
96649
97385
  init_prd();
96650
97386
  init_precheck();
96651
97387
  init_common();
96652
- import { existsSync as existsSync30 } from "fs";
96653
- import { join as join68 } from "path";
97388
+ import { existsSync as existsSync29 } from "fs";
97389
+ import { join as join71 } from "path";
96654
97390
  async function precheckCommand(options) {
96655
97391
  const resolved = resolveProject({
96656
97392
  dir: options.dir,
@@ -96672,14 +97408,14 @@ async function precheckCommand(options) {
96672
97408
  process.exit(1);
96673
97409
  }
96674
97410
  }
96675
- const naxDir = join68(resolved.projectDir, ".nax");
96676
- const featureDir = join68(naxDir, "features", featureName);
96677
- const prdPath = join68(featureDir, "prd.json");
96678
- if (!existsSync30(featureDir)) {
97411
+ const naxDir = join71(resolved.projectDir, ".nax");
97412
+ const featureDir = join71(naxDir, "features", featureName);
97413
+ const prdPath = join71(featureDir, "prd.json");
97414
+ if (!existsSync29(featureDir)) {
96679
97415
  console.error(source_default.red(`Feature not found: ${featureName}`));
96680
97416
  process.exit(1);
96681
97417
  }
96682
- if (!existsSync30(prdPath)) {
97418
+ if (!existsSync29(prdPath)) {
96683
97419
  console.error(source_default.red(`Missing prd.json for feature: ${featureName}`));
96684
97420
  console.error(source_default.dim(`Run: nax plan -f ${featureName} --from spec.md --auto`));
96685
97421
  process.exit(EXIT_CODES.INVALID_PRD);
@@ -96697,7 +97433,7 @@ async function precheckCommand(options) {
96697
97433
  init_source();
96698
97434
  init_paths3();
96699
97435
  import { readdir as readdir5 } from "fs/promises";
96700
- import { join as join69 } from "path";
97436
+ import { join as join72 } from "path";
96701
97437
  var DEFAULT_LIMIT = 20;
96702
97438
  var _runsCmdDeps = {
96703
97439
  getRunsDir
@@ -96752,7 +97488,7 @@ async function runsCommand(options = {}) {
96752
97488
  }
96753
97489
  const rows = [];
96754
97490
  for (const entry of entries) {
96755
- const metaPath = join69(runsDir, entry, "meta.json");
97491
+ const metaPath = join72(runsDir, entry, "meta.json");
96756
97492
  let meta3;
96757
97493
  try {
96758
97494
  meta3 = await Bun.file(metaPath).json();
@@ -96829,7 +97565,7 @@ async function runsCommand(options = {}) {
96829
97565
 
96830
97566
  // src/commands/unlock.ts
96831
97567
  init_source();
96832
- import { join as join70 } from "path";
97568
+ import { join as join73 } from "path";
96833
97569
  function isProcessAlive2(pid) {
96834
97570
  try {
96835
97571
  process.kill(pid, 0);
@@ -96844,7 +97580,7 @@ function formatLockAge(ageMs) {
96844
97580
  }
96845
97581
  async function unlockCommand(options) {
96846
97582
  const workdir = options.dir ?? process.cwd();
96847
- const lockPath = join70(workdir, "nax.lock");
97583
+ const lockPath = join73(workdir, "nax.lock");
96848
97584
  const lockFile = Bun.file(lockPath);
96849
97585
  const exists = await lockFile.exists();
96850
97586
  if (!exists) {
@@ -105268,8 +106004,8 @@ Next: nax generate --package ${options.package}`));
105268
106004
  }
105269
106005
  return;
105270
106006
  }
105271
- const naxDir = join84(workdir, ".nax");
105272
- if (existsSync36(naxDir) && !options.force) {
106007
+ const naxDir = join87(workdir, ".nax");
106008
+ if (existsSync35(naxDir) && !options.force) {
105273
106009
  console.log(source_default.yellow("nax already initialized. Use --force to overwrite."));
105274
106010
  return;
105275
106011
  }
@@ -105297,11 +106033,11 @@ Next: nax generate --package ${options.package}`));
105297
106033
  }
105298
106034
  }
105299
106035
  }
105300
- mkdirSync7(join84(naxDir, "features"), { recursive: true });
105301
- mkdirSync7(join84(naxDir, "hooks"), { recursive: true });
106036
+ mkdirSync7(join87(naxDir, "features"), { recursive: true });
106037
+ mkdirSync7(join87(naxDir, "hooks"), { recursive: true });
105302
106038
  const initConfig = options.name ? { ...DEFAULT_CONFIG, name: options.name } : DEFAULT_CONFIG;
105303
- await Bun.write(join84(naxDir, "config.json"), JSON.stringify(initConfig, null, 2));
105304
- await Bun.write(join84(naxDir, "hooks.json"), JSON.stringify({
106039
+ await Bun.write(join87(naxDir, "config.json"), JSON.stringify(initConfig, null, 2));
106040
+ await Bun.write(join87(naxDir, "hooks.json"), JSON.stringify({
105305
106041
  hooks: {
105306
106042
  "on-start": { command: 'echo "nax started: $NAX_FEATURE"', enabled: false },
105307
106043
  "on-complete": { command: 'echo "nax complete: $NAX_FEATURE"', enabled: false },
@@ -105309,12 +106045,12 @@ Next: nax generate --package ${options.package}`));
105309
106045
  "on-error": { command: 'echo "nax error: $NAX_REASON"', enabled: false }
105310
106046
  }
105311
106047
  }, null, 2));
105312
- await Bun.write(join84(naxDir, ".gitignore"), `# nax temp files
106048
+ await Bun.write(join87(naxDir, ".gitignore"), `# nax temp files
105313
106049
  *.tmp
105314
106050
  .paused.json
105315
106051
  .nax-verifier-verdict.json
105316
106052
  `);
105317
- await Bun.write(join84(naxDir, "context.md"), `# Project Context
106053
+ await Bun.write(join87(naxDir, "context.md"), `# Project Context
105318
106054
 
105319
106055
  This document defines coding standards, architectural decisions, and forbidden patterns for this project.
105320
106056
  Run \`nax generate\` to regenerate agent config files (CLAUDE.md, AGENTS.md, .cursorrules, etc.) from this file.
@@ -105399,6 +106135,24 @@ Run \`nax generate\` to regenerate agent config files (CLAUDE.md, AGENTS.md, .cu
105399
106135
  console.log(source_default.dim(`
105400
106136
  Next: nax features create <name>`));
105401
106137
  });
106138
+ program2.command("setup").description("Analyze repo and generate .nax/config.json via LLM").option("-d, --dir <path>", "Project directory", process.cwd()).option("-a, --agent <name>", "Force a specific agent").option("--fill-scripts", "Add missing quality-gate scripts to package.json", false).option("--dry-run", "Preview planned config without writing files", false).option("--force", "Overwrite existing .nax/config.json", false).action(async (options) => {
106139
+ let workdir;
106140
+ try {
106141
+ workdir = validateDirectory(options.dir);
106142
+ } catch (err) {
106143
+ console.error(source_default.red(`Invalid directory: ${err.message}`));
106144
+ process.exit(1);
106145
+ }
106146
+ const { setupCommand: setupCommand2 } = await Promise.resolve().then(() => (init_setup(), exports_setup));
106147
+ const exitCode = await setupCommand2({
106148
+ dir: workdir,
106149
+ agent: options.agent,
106150
+ fillScripts: options.fillScripts,
106151
+ dryRun: options.dryRun,
106152
+ force: options.force
106153
+ });
106154
+ process.exit(exitCode);
106155
+ });
105402
106156
  program2.command("run").description("Run the orchestration loop for a feature").requiredOption("-f, --feature <name>", "Feature name").option("-a, --agent <name>", "Force a specific agent").option("-m, --max-iterations <n>", "Max iterations", "20").option("--dry-run", "Show plan without executing", false).option("--no-context", "Disable context builder (skip file context in prompts)").option("--no-batch", "Disable story batching (execute all stories individually)").option("--parallel <n>", "Max parallel sessions (0=auto, omit=sequential)").option("--plan", "Run plan phase first before execution", false).option("--from <spec-path>", "Path to spec file (required when --plan is used)").option("--one-shot", "Skip interactive planning Q&A, use single LLM call (ACP only)", false).option("--force", "Force overwrite existing prd.json when using --plan", false).option("--headless", "Force headless mode (disable TUI, use pipe mode)", false).option("--verbose", "Enable verbose logging (debug level)", false).option("--quiet", "Quiet mode (warnings and errors only)", false).option("--silent", "Silent mode (errors only)", false).option("--json", "JSON mode (raw JSONL output to stdout)", false).option("-d, --dir <path>", "Working directory", process.cwd()).option("--skip-precheck", "Skip precheck validations (advanced users only)", false).option("--profile <name>", "Profile to use (overrides config.json profile)").action(async (options) => {
105403
106157
  let workdir;
105404
106158
  try {
@@ -105411,7 +106165,7 @@ program2.command("run").description("Run the orchestration loop for a feature").
105411
106165
  console.error(source_default.red("Error: --plan requires --from <spec-path>"));
105412
106166
  process.exit(1);
105413
106167
  }
105414
- if (options.from && !existsSync36(options.from)) {
106168
+ if (options.from && !existsSync35(options.from)) {
105415
106169
  console.error(source_default.red(`Error: File not found: ${options.from} (required with --plan)`));
105416
106170
  process.exit(1);
105417
106171
  }
@@ -105444,10 +106198,10 @@ program2.command("run").description("Run the orchestration loop for a feature").
105444
106198
  console.error(source_default.red("nax not initialized. Run: nax init"));
105445
106199
  process.exit(1);
105446
106200
  }
105447
- const featureDir = join84(naxDir, "features", options.feature);
105448
- const prdPath = join84(featureDir, "prd.json");
106201
+ const featureDir = join87(naxDir, "features", options.feature);
106202
+ const prdPath = join87(featureDir, "prd.json");
105449
106203
  if (options.plan && options.from) {
105450
- if (existsSync36(prdPath) && !options.force) {
106204
+ if (existsSync35(prdPath) && !options.force) {
105451
106205
  console.error(source_default.red(`Error: prd.json already exists for feature "${options.feature}".`));
105452
106206
  console.error(source_default.dim(" Use --force to overwrite, or run without --plan to use the existing PRD."));
105453
106207
  process.exit(1);
@@ -105467,10 +106221,10 @@ program2.command("run").description("Run the orchestration loop for a feature").
105467
106221
  }
105468
106222
  }
105469
106223
  try {
105470
- const planLogDir = join84(featureDir, "plan");
106224
+ const planLogDir = join87(featureDir, "plan");
105471
106225
  mkdirSync7(planLogDir, { recursive: true });
105472
106226
  const planLogId = new Date().toISOString().replace(/:/g, "-").replace(/\..+/, "");
105473
- const planLogPath = join84(planLogDir, `${planLogId}.jsonl`);
106227
+ const planLogPath = join87(planLogDir, `${planLogId}.jsonl`);
105474
106228
  initLogger({ level: "info", filePath: planLogPath, useChalk: false, headless: true });
105475
106229
  console.log(source_default.dim(` [Plan log: ${planLogPath}]`));
105476
106230
  console.log(source_default.dim(" [Planning phase: generating PRD from spec]"));
@@ -105509,17 +106263,17 @@ program2.command("run").description("Run the orchestration loop for a feature").
105509
106263
  process.exit(1);
105510
106264
  }
105511
106265
  }
105512
- if (!existsSync36(prdPath)) {
106266
+ if (!existsSync35(prdPath)) {
105513
106267
  console.error(source_default.red(`Feature "${options.feature}" not found or missing prd.json`));
105514
106268
  process.exit(1);
105515
106269
  }
105516
106270
  resetLogger();
105517
106271
  const projectKey = config2.name?.trim() || basename16(workdir);
105518
106272
  const outputDir = projectOutputDir(projectKey, config2.outputDir);
105519
- const runsDir = join84(outputDir, "features", options.feature, "runs");
106273
+ const runsDir = join87(outputDir, "features", options.feature, "runs");
105520
106274
  mkdirSync7(runsDir, { recursive: true });
105521
106275
  const runId = new Date().toISOString().replace(/:/g, "-").replace(/\..+/, "");
105522
- const logFilePath = join84(runsDir, `${runId}.jsonl`);
106276
+ const logFilePath = join87(runsDir, `${runId}.jsonl`);
105523
106277
  const isTTY = process.stdout.isTTY ?? false;
105524
106278
  const headlessFlag = options.headless ?? false;
105525
106279
  const headlessEnv = process.env.NAX_HEADLESS === "1";
@@ -105537,7 +106291,7 @@ program2.command("run").description("Run the orchestration loop for a feature").
105537
106291
  config2.agent.default = options.agent;
105538
106292
  }
105539
106293
  config2.execution.maxIterations = Number.parseInt(options.maxIterations, 10);
105540
- const globalNaxDir = join84(homedir3(), ".nax");
106294
+ const globalNaxDir = join87(homedir3(), ".nax");
105541
106295
  const hooks = await loadHooksConfig(naxDir, globalNaxDir);
105542
106296
  const eventEmitter = new PipelineEventEmitter;
105543
106297
  const agentStreamEvents = useHeadless ? undefined : new AgentStreamEventBus;
@@ -105557,12 +106311,12 @@ program2.command("run").description("Run the orchestration loop for a feature").
105557
106311
  events: eventEmitter,
105558
106312
  ptyOptions: null,
105559
106313
  agentStreamEvents,
105560
- queueFilePath: join84(workdir, ".queue.txt")
106314
+ queueFilePath: join87(workdir, ".queue.txt")
105561
106315
  });
105562
106316
  } else {
105563
106317
  console.log(source_default.dim(" [Headless mode \u2014 pipe output]"));
105564
106318
  }
105565
- const statusFilePath = join84(outputDir, "status.json");
106319
+ const statusFilePath = join87(outputDir, "status.json");
105566
106320
  let parallel;
105567
106321
  if (options.parallel !== undefined) {
105568
106322
  parallel = Number.parseInt(options.parallel, 10);
@@ -105589,9 +106343,9 @@ program2.command("run").description("Run the orchestration loop for a feature").
105589
106343
  skipPrecheck: options.skipPrecheck ?? false,
105590
106344
  agentStreamEvents
105591
106345
  });
105592
- const latestSymlink = join84(runsDir, "latest.jsonl");
106346
+ const latestSymlink = join87(runsDir, "latest.jsonl");
105593
106347
  try {
105594
- if (existsSync36(latestSymlink)) {
106348
+ if (existsSync35(latestSymlink)) {
105595
106349
  Bun.spawnSync(["rm", latestSymlink]);
105596
106350
  }
105597
106351
  Bun.spawnSync(["ln", "-s", `${runId}.jsonl`, latestSymlink], {
@@ -105650,9 +106404,9 @@ features.command("create <name>").description("Create a new feature").option("-d
105650
106404
  console.error(source_default.red("nax not initialized. Run: nax init"));
105651
106405
  process.exit(1);
105652
106406
  }
105653
- const featureDir = join84(naxDir, "features", name);
106407
+ const featureDir = join87(naxDir, "features", name);
105654
106408
  mkdirSync7(featureDir, { recursive: true });
105655
- await Bun.write(join84(featureDir, "spec.md"), `# Feature: ${name}
106409
+ await Bun.write(join87(featureDir, "spec.md"), `# Feature: ${name}
105656
106410
 
105657
106411
  ## Overview
105658
106412
 
@@ -105685,7 +106439,7 @@ features.command("create <name>").description("Create a new feature").option("-d
105685
106439
 
105686
106440
  <!-- What this feature explicitly does NOT cover. -->
105687
106441
  `);
105688
- await Bun.write(join84(featureDir, "progress.txt"), `# Progress: ${name}
106442
+ await Bun.write(join87(featureDir, "progress.txt"), `# Progress: ${name}
105689
106443
 
105690
106444
  Created: ${new Date().toISOString()}
105691
106445
 
@@ -105711,8 +106465,8 @@ features.command("list").description("List all features").option("-d, --dir <pat
105711
106465
  console.error(source_default.red("nax not initialized."));
105712
106466
  process.exit(1);
105713
106467
  }
105714
- const featuresDir = join84(naxDir, "features");
105715
- if (!existsSync36(featuresDir)) {
106468
+ const featuresDir = join87(naxDir, "features");
106469
+ if (!existsSync35(featuresDir)) {
105716
106470
  console.log(source_default.dim("No features yet."));
105717
106471
  return;
105718
106472
  }
@@ -105726,8 +106480,8 @@ features.command("list").description("List all features").option("-d, --dir <pat
105726
106480
  Features:
105727
106481
  `));
105728
106482
  for (const name of entries) {
105729
- const prdPath = join84(featuresDir, name, "prd.json");
105730
- if (existsSync36(prdPath)) {
106483
+ const prdPath = join87(featuresDir, name, "prd.json");
106484
+ if (existsSync35(prdPath)) {
105731
106485
  const prd = await loadPRD(prdPath);
105732
106486
  const c = countStories(prd);
105733
106487
  console.log(` ${name} \u2014 ${c.passed}/${c.total} stories done`);
@@ -105761,10 +106515,10 @@ Use: nax plan -f <feature> --from <spec>`));
105761
106515
  cliOverrides.profile = options.profile;
105762
106516
  }
105763
106517
  const config2 = await loadConfig(workdir, cliOverrides);
105764
- const featureLogDir = join84(naxDir, "features", options.feature, "plan");
106518
+ const featureLogDir = join87(naxDir, "features", options.feature, "plan");
105765
106519
  mkdirSync7(featureLogDir, { recursive: true });
105766
106520
  const planLogId = new Date().toISOString().replace(/:/g, "-").replace(/\..+/, "");
105767
- const planLogPath = join84(featureLogDir, `${planLogId}.jsonl`);
106521
+ const planLogPath = join87(featureLogDir, `${planLogId}.jsonl`);
105768
106522
  initLogger({ level: "info", filePath: planLogPath, useChalk: false, headless: true });
105769
106523
  console.log(source_default.dim(` [Plan log: ${planLogPath}]`));
105770
106524
  try {