@nathapp/nax 0.68.8 → 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 +1278 -397
  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();
@@ -20944,12 +21117,21 @@ function parseAcpxJsonLine(line, state) {
20944
21117
  }
20945
21118
  if (update.sessionUpdate === "usage_update") {
20946
21119
  const activity = { kind: "usage_update" };
20947
- if (typeof update.used === "number") {
21120
+ const metaUsage = update._meta != null && typeof update._meta === "object" ? update._meta.usage : undefined;
21121
+ if (metaUsage != null && typeof metaUsage === "object") {
21122
+ const inp = metaUsage.inputTokens ?? metaUsage.input_tokens;
21123
+ if (typeof inp === "number")
21124
+ activity.inputTokens = inp;
21125
+ const out = metaUsage.outputTokens ?? metaUsage.output_tokens;
21126
+ if (typeof out === "number")
21127
+ activity.outputTokens = out;
21128
+ }
21129
+ if (activity.outputTokens == null && typeof update.used === "number") {
20948
21130
  activity.outputTokens = update.used;
20949
21131
  }
20950
21132
  if (typeof update.cost?.amount === "number") {
20951
21133
  activity.costUsd = update.cost.amount;
20952
- state.exactCostUsd = update.cost.amount;
21134
+ state.exactCostUsd = activity.costUsd;
20953
21135
  }
20954
21136
  return activity;
20955
21137
  }
@@ -32632,12 +32814,14 @@ var init_adversarial_review = __esm(() => {
32632
32814
  });
32633
32815
  const { accepted, dropped } = filterByAcQuote(substantiated, input.story.acceptanceCriteria);
32634
32816
  const blocking = accepted.filter((f) => isBlockingSeverity(f.severity, threshold));
32817
+ const advisory = accepted.filter((f) => !isBlockingSeverity(f.severity, threshold));
32635
32818
  const passed = parsed.passed && blocking.length === 0;
32636
32819
  return {
32637
32820
  ...parsed,
32638
32821
  passed,
32639
32822
  findings: accepted,
32640
32823
  normalizedFindings: toAdversarialReviewFindings(blocking),
32824
+ advisoryFindings: toAdversarialReviewFindings(advisory),
32641
32825
  acDropped: dropped
32642
32826
  };
32643
32827
  }
@@ -34337,7 +34521,25 @@ async function readSpecDriftViolations(input) {
34337
34521
  return [];
34338
34522
  }
34339
34523
  }
34340
- async function normalizeStoryFiles(story, workdir, fileExists) {
34524
+ function collectUpstreamProducedFiles(story, byId) {
34525
+ const produced = new Set;
34526
+ const seen = new Set;
34527
+ const stack = [...story.dependencies ?? []];
34528
+ while (stack.length > 0) {
34529
+ const depId = stack.pop();
34530
+ if (!depId || seen.has(depId))
34531
+ continue;
34532
+ seen.add(depId);
34533
+ const dep = byId.get(depId);
34534
+ if (!dep)
34535
+ continue;
34536
+ for (const filePath of getExpectedFiles(dep))
34537
+ produced.add(filePath);
34538
+ stack.push(...dep.dependencies ?? []);
34539
+ }
34540
+ return produced;
34541
+ }
34542
+ async function normalizeStoryFiles(story, workdir, fileExists, upstreamProduced) {
34341
34543
  const contextFiles = story.contextFiles ?? [];
34342
34544
  if (contextFiles.length === 0)
34343
34545
  return { story, changed: false };
@@ -34352,6 +34554,14 @@ async function normalizeStoryFiles(story, workdir, fileExists) {
34352
34554
  kept.push(entry);
34353
34555
  continue;
34354
34556
  }
34557
+ if (upstreamProduced.has(filePath)) {
34558
+ kept.push(entry);
34559
+ logger?.debug("plan", "Kept cross-story produced file in contextFiles (upstream dependency creates it)", {
34560
+ storyId: story.id,
34561
+ filePath
34562
+ });
34563
+ continue;
34564
+ }
34355
34565
  if (factId) {
34356
34566
  logger?.warn("plan", "Context file cites a manifest fact but is absent on disk", {
34357
34567
  storyId: story.id,
@@ -34379,7 +34589,8 @@ async function normalizeStoryFiles(story, workdir, fileExists) {
34379
34589
  async function normalizeCreatedContextFiles(prd, workdir, fileExists) {
34380
34590
  if (!workdir)
34381
34591
  return prd;
34382
- const results = await Promise.all(prd.userStories.map((story) => normalizeStoryFiles(story, workdir, fileExists)));
34592
+ const byId = new Map(prd.userStories.map((story) => [story.id, story]));
34593
+ const results = await Promise.all(prd.userStories.map((story) => normalizeStoryFiles(story, workdir, fileExists, collectUpstreamProducedFiles(story, byId))));
34383
34594
  if (!results.some((r) => r.changed))
34384
34595
  return prd;
34385
34596
  return { ...prd, userStories: results.map((r) => r.story) };
@@ -38804,6 +39015,159 @@ async function validateMockStructureFiles(declarations, resolvedTestPatterns, pa
38804
39015
  var defaultFileExists = (p) => Bun.file(p).exists();
38805
39016
  var init_validate_mock_structure_files = () => {};
38806
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
+
38807
39171
  // src/operations/declaration-sink.ts
38808
39172
  function makeDeclarationSink() {
38809
39173
  return { testEdits: [], mockHandoffs: [] };
@@ -39405,6 +39769,7 @@ var init_operations = __esm(() => {
39405
39769
  init_autofix_test_writer_strategy();
39406
39770
  init_apply_test_edit_declarations();
39407
39771
  init_validate_mock_structure_files();
39772
+ init_setup_generate();
39408
39773
  init__finding_to_check();
39409
39774
  init_mechanical_lintfix_strategy();
39410
39775
  init_mechanical_formatfix_strategy();
@@ -42566,9 +42931,9 @@ ${rows.join(`
42566
42931
  `)}
42567
42932
  `;
42568
42933
  }
42569
- var CONTEXT_VS_EXPECTED_FILES_RULE = `**\`contextFiles\` rule \u2014 existing files only.** Only list paths that already exist in the repo today. The pipeline verifies every \`contextFiles\` entry against the filesystem; a path that does not exist is treated as a missing-context warning.
42934
+ var CONTEXT_VS_EXPECTED_FILES_RULE = `**\`contextFiles\` rule \u2014 files readable when this story runs.** List paths that already exist in the repo today, PLUS any file an UPSTREAM dependency story creates (it does not exist now but will exist by the time this story runs, because dependencies execute first). The pipeline verifies every \`contextFiles\` entry against the filesystem; a path that exists neither on disk nor in an upstream dependency's outputs is treated as a missing-context warning.
42570
42935
 
42571
- **\`expectedFiles\` rule \u2014 files this story CREATES.** List every NEW file the story authors (relative paths). A file the story will create belongs here, NEVER in \`contextFiles\` \u2014 these are the story's outputs, not files to read first. A single path may appear in \`contextFiles\` (an existing sibling to mirror) AND \`expectedFiles\` (the new file itself), but the same path must never be in both.`, EXPECTED_FILES_SCHEMA_FIELD = `"expectedFiles": ["string \u2014 NEW files this story creates (relative paths, omit if none)"],`;
42936
+ **\`expectedFiles\` rule \u2014 files THIS story CREATES.** List every NEW file this story authors (relative paths). A file this story creates belongs here, NEVER in \`contextFiles\` \u2014 these are the story's outputs, not files to read first. A file created by an upstream dependency and only read/modified here belongs in \`contextFiles\`, NOT here (this story does not author it). A single path may appear in \`contextFiles\` (an existing sibling to mirror) AND \`expectedFiles\` (the new file itself), but the same path must never be in both.`, EXPECTED_FILES_SCHEMA_FIELD = `"expectedFiles": ["string \u2014 NEW files this story creates (relative paths, omit if none)"],`;
42572
42937
  var init_plan_builder = __esm(() => {
42573
42938
  init_config();
42574
42939
  });
@@ -44619,7 +44984,8 @@ var init_session_role = __esm(() => {
44619
44984
  "fix-gen",
44620
44985
  "auto",
44621
44986
  "synthesis",
44622
- "judge"
44987
+ "judge",
44988
+ "setup"
44623
44989
  ];
44624
44990
  });
44625
44991
 
@@ -53496,6 +53862,108 @@ var init_context2 = __esm(() => {
53496
53862
  };
53497
53863
  });
53498
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
+
53499
53967
  // src/execution/story-orchestrator.ts
53500
53968
  async function refreshReviewInputForDispatch(opName, input) {
53501
53969
  if (opName !== "semantic-review" && opName !== "adversarial-review")
@@ -53935,16 +54403,17 @@ function withIncreasingFailuresBail(strategies, enabled, consecutiveIncreases) {
53935
54403
  }
53936
54404
  }));
53937
54405
  }
53938
- async function runRectification(ctx, state, phaseCosts, phaseOutputs) {
54406
+ async function runRectification(ctx, state, phaseCosts, phaseOutputs, overrides) {
53939
54407
  const rectification = state.rectification;
53940
- const validationPhases = collectRectificationPhases(state);
54408
+ const baseValidationPhases = collectRectificationPhases(state);
54409
+ const validationPhases = overrides?.excludePhaseKinds ? baseValidationPhases.filter((p) => !overrides.excludePhaseKinds?.includes(p.kind)) : baseValidationPhases;
53941
54410
  if (!rectification || validationPhases.length === 0) {
53942
54411
  return {};
53943
54412
  }
53944
54413
  if (ctx.runtime.signal?.aborted) {
53945
54414
  return {};
53946
54415
  }
53947
- const initialFindings = gatherRectificationFindings(phaseOutputs, validationPhases, state);
54416
+ const initialFindings = overrides?.initialFindings ? [...overrides.initialFindings] : gatherRectificationFindings(phaseOutputs, validationPhases, state);
53948
54417
  if (initialFindings.length === 0) {
53949
54418
  return {};
53950
54419
  }
@@ -53959,14 +54428,16 @@ async function runRectification(ctx, state, phaseCosts, phaseOutputs) {
53959
54428
  const cycle = {
53960
54429
  findings: [...initialFindings],
53961
54430
  iterations: [],
53962
- strategies: withIncreasingFailuresBail(rectification.strategies, rectification.abortOnIncreasingFailures, rectification.consecutiveIncreasesToBail ?? 1),
53963
- 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 },
53964
54433
  validate: async (_validateCtx, opts) => {
53965
54434
  if (ctx.runtime.signal?.aborted)
53966
54435
  return { findings: [], shortCircuited: false };
53967
54436
  const lite = (opts?.mode ?? "full") === "lite";
53968
54437
  const selected = phasesToRevalidate(opts?.strategiesRun, validationPhases);
53969
- 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;
53970
54441
  getSafeLogger()?.debug("story-orchestrator", "rectification validate scope", {
53971
54442
  storyId: ctx.storyId,
53972
54443
  mode: opts?.mode ?? "full",
@@ -54118,6 +54589,25 @@ class ExecutionPlan {
54118
54589
  }
54119
54590
  }
54120
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
+ }
54121
54611
  const verifierName = this.state.verifier?.slot.op.name;
54122
54612
  const gateName = this.state.fullSuiteGate?.slot.op.name;
54123
54613
  const verifierPassedSsot = verifierName !== undefined && phaseExplicitlyPassed(phaseOutputs[verifierName]);
@@ -54210,6 +54700,11 @@ class StoryOrchestratorBuilder {
54210
54700
  this.state.rectification = opts;
54211
54701
  return this;
54212
54702
  }
54703
+ addNonBlockingFix(cfg, strategies) {
54704
+ this.state.nonBlockingFix = cfg;
54705
+ this.state.nonBlockingFixStrategies = strategies;
54706
+ return this;
54707
+ }
54213
54708
  build(ctx, opts = {}) {
54214
54709
  if (!this.state.implementer) {
54215
54710
  throw new NaxError("StoryOrchestratorBuilder.build(): addImplementer() must be called before build()", "ORCHESTRATOR_NO_IMPLEMENTER", { stage: "execution" });
@@ -54227,6 +54722,7 @@ var init_story_orchestrator = __esm(() => {
54227
54722
  init_event_bus();
54228
54723
  init_prepare_inputs();
54229
54724
  init_git();
54725
+ init_non_blocking_fix();
54230
54726
  _storyOrchestratorDeps = {
54231
54727
  callOp,
54232
54728
  runFixCycle,
@@ -54379,6 +54875,22 @@ async function buildPlanForStrategy(ctx, story, config2, testStrategy, inputs) {
54379
54875
  };
54380
54876
  builder.addRectification(rectOpts);
54381
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
+ }
54382
54894
  return builder.build(ctx, { isThreeSession });
54383
54895
  }
54384
54896
  var init_build_plan_for_strategy = __esm(() => {
@@ -54642,41 +55154,6 @@ var init_execution_helpers = __esm(() => {
54642
55154
  init_errors();
54643
55155
  });
54644
55156
 
54645
- // src/tdd/rollback.ts
54646
- async function rollbackToRef(workdir, ref) {
54647
- const logger = getLogger();
54648
- logger.warn("tdd", "Rolling back git changes", { ref });
54649
- const resetProc = _rollbackDeps.spawn(["git", "reset", "--hard", ref], {
54650
- cwd: workdir,
54651
- stdout: "pipe",
54652
- stderr: "pipe"
54653
- });
54654
- const exitCode = await resetProc.exited;
54655
- if (exitCode !== 0) {
54656
- const stderr = await new Response(resetProc.stderr).text();
54657
- logger.error("tdd", "Failed to rollback git changes", { ref, stderr });
54658
- throw new Error(`Git rollback failed: ${stderr}`);
54659
- }
54660
- const cleanProc = _rollbackDeps.spawn(["git", "clean", "-fd"], {
54661
- cwd: workdir,
54662
- stdout: "pipe",
54663
- stderr: "pipe"
54664
- });
54665
- const cleanExitCode = await cleanProc.exited;
54666
- if (cleanExitCode !== 0) {
54667
- const stderr = await new Response(cleanProc.stderr).text();
54668
- logger.warn("tdd", "Failed to clean untracked files", { stderr });
54669
- }
54670
- logger.info("tdd", "Successfully rolled back git changes", { ref });
54671
- }
54672
- var _rollbackDeps;
54673
- var init_rollback = __esm(() => {
54674
- init_logger2();
54675
- _rollbackDeps = {
54676
- spawn: Bun.spawn
54677
- };
54678
- });
54679
-
54680
55157
  // src/execution/session-manager-runtime.ts
54681
55158
  async function closePhysicalSession(descriptor, agentGetFn, force) {
54682
55159
  if (!descriptor.handle)
@@ -56251,9 +56728,14 @@ __export(exports_init_context, {
56251
56728
  generateContextTemplate: () => generateContextTemplate,
56252
56729
  _initContextDeps: () => _initContextDeps
56253
56730
  });
56254
- import { existsSync as existsSync22 } from "fs";
56255
- import { mkdir as mkdir8 } from "fs/promises";
56256
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
+ }
56257
56739
  async function findFiles(dir, maxFiles = 200) {
56258
56740
  try {
56259
56741
  const proc = Bun.spawnSync([
@@ -56282,7 +56764,7 @@ async function findFiles(dir, maxFiles = 200) {
56282
56764
  }
56283
56765
  async function readPackageManifest(projectRoot) {
56284
56766
  const packageJsonPath = join51(projectRoot, "package.json");
56285
- if (!existsSync22(packageJsonPath)) {
56767
+ if (!await bunFileExists(packageJsonPath)) {
56286
56768
  return null;
56287
56769
  }
56288
56770
  try {
@@ -56300,7 +56782,7 @@ async function readPackageManifest(projectRoot) {
56300
56782
  }
56301
56783
  async function readReadmeSnippet(projectRoot) {
56302
56784
  const readmePath = join51(projectRoot, "README.md");
56303
- if (!existsSync22(readmePath)) {
56785
+ if (!await bunFileExists(readmePath)) {
56304
56786
  return null;
56305
56787
  }
56306
56788
  try {
@@ -56318,7 +56800,7 @@ async function detectEntryPoints(projectRoot) {
56318
56800
  const found = [];
56319
56801
  for (const candidate of candidates) {
56320
56802
  const path13 = join51(projectRoot, candidate);
56321
- if (existsSync22(path13)) {
56803
+ if (await bunFileExists(path13)) {
56322
56804
  found.push(candidate);
56323
56805
  }
56324
56806
  }
@@ -56329,7 +56811,7 @@ async function detectConfigFiles(projectRoot) {
56329
56811
  const found = [];
56330
56812
  for (const candidate of candidates) {
56331
56813
  const path13 = join51(projectRoot, candidate);
56332
- if (existsSync22(path13)) {
56814
+ if (await bunFileExists(path13)) {
56333
56815
  found.push(candidate);
56334
56816
  }
56335
56817
  }
@@ -56459,10 +56941,10 @@ Keep it under 2000 tokens. Use markdown formatting. Be specific to the detected
56459
56941
  `;
56460
56942
  try {
56461
56943
  const result = await _initContextDeps.callLLM(prompt);
56462
- logger.info("init", "Generated context.md with LLM");
56944
+ logger.info("init", "Generated context.md with LLM", { storyId: "init-context" });
56463
56945
  return result;
56464
56946
  } catch (err) {
56465
- 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" });
56466
56948
  return generateContextTemplate(scan);
56467
56949
  }
56468
56950
  }
@@ -56491,27 +56973,33 @@ async function initPackage(repoRoot, packagePath, force = false) {
56491
56973
  const logger = getLogger();
56492
56974
  const naxDir = join51(repoRoot, ".nax", "mono", packagePath);
56493
56975
  const contextPath = join51(naxDir, "context.md");
56494
- if (existsSync22(contextPath) && !force) {
56495
- 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
+ });
56496
56981
  return;
56497
56982
  }
56498
- if (!existsSync22(naxDir)) {
56499
- await mkdir8(naxDir, { recursive: true });
56983
+ if (!await bunFileExists(naxDir)) {
56984
+ await bunMkdirp(naxDir);
56500
56985
  }
56501
56986
  const content = generatePackageContextTemplate(packagePath);
56502
56987
  await Bun.write(contextPath, content);
56503
- logger.info("init", "Created package context.md", { path: contextPath });
56988
+ logger.info("init", "Created package context.md", { storyId: "init-context", path: contextPath });
56504
56989
  }
56505
56990
  async function initContext(projectRoot, options = {}) {
56506
56991
  const logger = getLogger();
56507
56992
  const naxDir = join51(projectRoot, ".nax");
56508
56993
  const contextPath = join51(naxDir, "context.md");
56509
- if (existsSync22(contextPath) && !options.force) {
56510
- 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
+ });
56511
56999
  return;
56512
57000
  }
56513
- if (!existsSync22(naxDir)) {
56514
- await mkdir8(naxDir, { recursive: true });
57001
+ if (!await bunFileExists(naxDir)) {
57002
+ await bunMkdirp(naxDir);
56515
57003
  }
56516
57004
  const scan = await scanProject(projectRoot);
56517
57005
  let content;
@@ -56521,7 +57009,10 @@ async function initContext(projectRoot, options = {}) {
56521
57009
  content = generateContextTemplate(scan);
56522
57010
  }
56523
57011
  await Bun.write(contextPath, content);
56524
- 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
+ });
56525
57016
  }
56526
57017
  var _initContextDeps;
56527
57018
  var init_init_context = __esm(() => {
@@ -56534,11 +57025,11 @@ var init_init_context = __esm(() => {
56534
57025
  });
56535
57026
 
56536
57027
  // src/cli/init-detect.ts
56537
- import { existsSync as existsSync23, readFileSync } from "fs";
57028
+ import { existsSync as existsSync22, readFileSync } from "fs";
56538
57029
  import { join as join52 } from "path";
56539
57030
  function readPackageJson(projectRoot) {
56540
57031
  const pkgPath = join52(projectRoot, "package.json");
56541
- if (!existsSync23(pkgPath))
57032
+ if (!existsSync22(pkgPath))
56542
57033
  return;
56543
57034
  try {
56544
57035
  return JSON.parse(readFileSync(pkgPath, "utf-8"));
@@ -56580,41 +57071,41 @@ function detectStack(projectRoot) {
56580
57071
  };
56581
57072
  }
56582
57073
  function detectRuntime(projectRoot) {
56583
- if (existsSync23(join52(projectRoot, "bun.lockb")) || existsSync23(join52(projectRoot, "bunfig.toml"))) {
57074
+ if (existsSync22(join52(projectRoot, "bun.lockb")) || existsSync22(join52(projectRoot, "bunfig.toml"))) {
56584
57075
  return "bun";
56585
57076
  }
56586
- 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"))) {
56587
57078
  return "node";
56588
57079
  }
56589
57080
  return "unknown";
56590
57081
  }
56591
57082
  function detectLanguage2(projectRoot) {
56592
- if (existsSync23(join52(projectRoot, "tsconfig.json")))
57083
+ if (existsSync22(join52(projectRoot, "tsconfig.json")))
56593
57084
  return "typescript";
56594
- if (existsSync23(join52(projectRoot, "pyproject.toml")) || existsSync23(join52(projectRoot, "setup.py"))) {
57085
+ if (existsSync22(join52(projectRoot, "pyproject.toml")) || existsSync22(join52(projectRoot, "setup.py"))) {
56595
57086
  return "python";
56596
57087
  }
56597
- if (existsSync23(join52(projectRoot, "Cargo.toml")))
57088
+ if (existsSync22(join52(projectRoot, "Cargo.toml")))
56598
57089
  return "rust";
56599
- if (existsSync23(join52(projectRoot, "go.mod")))
57090
+ if (existsSync22(join52(projectRoot, "go.mod")))
56600
57091
  return "go";
56601
57092
  return "unknown";
56602
57093
  }
56603
57094
  function detectLinter(projectRoot) {
56604
- if (existsSync23(join52(projectRoot, "biome.json")) || existsSync23(join52(projectRoot, "biome.jsonc"))) {
57095
+ if (existsSync22(join52(projectRoot, "biome.json")) || existsSync22(join52(projectRoot, "biome.jsonc"))) {
56605
57096
  return "biome";
56606
57097
  }
56607
- 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"))) {
56608
57099
  return "eslint";
56609
57100
  }
56610
57101
  return "unknown";
56611
57102
  }
56612
57103
  function detectMonorepo(projectRoot) {
56613
- if (existsSync23(join52(projectRoot, "turbo.json")))
57104
+ if (existsSync22(join52(projectRoot, "turbo.json")))
56614
57105
  return "turborepo";
56615
- if (existsSync23(join52(projectRoot, "nx.json")))
57106
+ if (existsSync22(join52(projectRoot, "nx.json")))
56616
57107
  return "nx";
56617
- if (existsSync23(join52(projectRoot, "pnpm-workspace.yaml")))
57108
+ if (existsSync22(join52(projectRoot, "pnpm-workspace.yaml")))
56618
57109
  return "pnpm-workspaces";
56619
57110
  const pkg = readPackageJson(projectRoot);
56620
57111
  if (pkg?.workspaces)
@@ -56756,8 +57247,8 @@ __export(exports_init, {
56756
57247
  checkInitCollision: () => checkInitCollision,
56757
57248
  _initDeps: () => _initDeps
56758
57249
  });
56759
- import { existsSync as existsSync24 } from "fs";
56760
- import { mkdir as mkdir9 } from "fs/promises";
57250
+ import { existsSync as existsSync23 } from "fs";
57251
+ import { mkdir as mkdir8 } from "fs/promises";
56761
57252
  import { join as join53 } from "path";
56762
57253
  function validateProjectName(name) {
56763
57254
  if (!name)
@@ -56796,7 +57287,7 @@ async function updateGitignore(projectRoot) {
56796
57287
  const logger = getLogger();
56797
57288
  const gitignorePath = join53(projectRoot, ".gitignore");
56798
57289
  let existing = "";
56799
- if (existsSync24(gitignorePath)) {
57290
+ if (existsSync23(gitignorePath)) {
56800
57291
  existing = await Bun.file(gitignorePath).text();
56801
57292
  }
56802
57293
  const missingEntries = NAX_GITIGNORE_ENTRIES.filter((entry) => !existing.includes(entry));
@@ -56876,12 +57367,12 @@ function buildConstitution(stack) {
56876
57367
  async function initGlobal() {
56877
57368
  const logger = getLogger();
56878
57369
  const globalDir = globalConfigDir();
56879
- if (!existsSync24(globalDir)) {
56880
- await mkdir9(globalDir, { recursive: true });
57370
+ if (!existsSync23(globalDir)) {
57371
+ await mkdir8(globalDir, { recursive: true });
56881
57372
  logger.info("init", "Created global config directory", { path: globalDir });
56882
57373
  }
56883
57374
  const configPath = join53(globalDir, "config.json");
56884
- if (!existsSync24(configPath)) {
57375
+ if (!existsSync23(configPath)) {
56885
57376
  await Bun.write(configPath, `${JSON.stringify(MINIMAL_GLOBAL_CONFIG, null, 2)}
56886
57377
  `);
56887
57378
  logger.info("init", "Created global config", { path: configPath });
@@ -56889,15 +57380,15 @@ async function initGlobal() {
56889
57380
  logger.info("init", "Global config already exists", { path: configPath });
56890
57381
  }
56891
57382
  const constitutionPath = join53(globalDir, "constitution.md");
56892
- if (!existsSync24(constitutionPath)) {
57383
+ if (!existsSync23(constitutionPath)) {
56893
57384
  await Bun.write(constitutionPath, buildConstitution({ runtime: "unknown", language: "unknown", linter: "unknown", monorepo: "none" }));
56894
57385
  logger.info("init", "Created global constitution", { path: constitutionPath });
56895
57386
  } else {
56896
57387
  logger.info("init", "Global constitution already exists", { path: constitutionPath });
56897
57388
  }
56898
57389
  const hooksDir = join53(globalDir, "hooks");
56899
- if (!existsSync24(hooksDir)) {
56900
- await mkdir9(hooksDir, { recursive: true });
57390
+ if (!existsSync23(hooksDir)) {
57391
+ await mkdir8(hooksDir, { recursive: true });
56901
57392
  logger.info("init", "Created global hooks directory", { path: hooksDir });
56902
57393
  } else {
56903
57394
  logger.info("init", "Global hooks directory already exists", { path: hooksDir });
@@ -56941,8 +57432,8 @@ async function initProject(projectRoot, options) {
56941
57432
  `), "INIT_NAME_COLLISION", { stage: "init", name: detectedName });
56942
57433
  }
56943
57434
  }
56944
- if (!existsSync24(projectDir)) {
56945
- await mkdir9(projectDir, { recursive: true });
57435
+ if (!existsSync23(projectDir)) {
57436
+ await mkdir8(projectDir, { recursive: true });
56946
57437
  logger.info("init", "Created project config directory", { path: projectDir });
56947
57438
  }
56948
57439
  const stack = detectStack(projectRoot);
@@ -56957,7 +57448,7 @@ async function initProject(projectRoot, options) {
56957
57448
  monorepo: stack.monorepo
56958
57449
  });
56959
57450
  const configPath = join53(projectDir, "config.json");
56960
- if (!existsSync24(configPath)) {
57451
+ if (!existsSync23(configPath)) {
56961
57452
  await Bun.write(configPath, `${JSON.stringify(projectConfig, null, 2)}
56962
57453
  `);
56963
57454
  logger.info("init", "Created project config", { path: configPath });
@@ -56966,15 +57457,15 @@ async function initProject(projectRoot, options) {
56966
57457
  }
56967
57458
  await initContext(projectRoot, { ai: options?.ai, force: options?.force });
56968
57459
  const constitutionPath = join53(projectDir, "constitution.md");
56969
- if (!existsSync24(constitutionPath) || options?.force) {
57460
+ if (!existsSync23(constitutionPath) || options?.force) {
56970
57461
  await Bun.write(constitutionPath, buildConstitution(stack));
56971
57462
  logger.info("init", "Created project constitution", { path: constitutionPath });
56972
57463
  } else {
56973
57464
  logger.info("init", "Project constitution already exists", { path: constitutionPath });
56974
57465
  }
56975
57466
  const hooksDir = join53(projectDir, "hooks");
56976
- if (!existsSync24(hooksDir)) {
56977
- await mkdir9(hooksDir, { recursive: true });
57467
+ if (!existsSync23(hooksDir)) {
57468
+ await mkdir8(hooksDir, { recursive: true });
56978
57469
  logger.info("init", "Created project hooks directory", { path: hooksDir });
56979
57470
  } else {
56980
57471
  logger.info("init", "Project hooks directory already exists", { path: hooksDir });
@@ -57033,6 +57524,286 @@ var init_init2 = __esm(() => {
57033
57524
  };
57034
57525
  });
57035
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
+
57036
57807
  // src/plugins/builtin/curator/collect.ts
57037
57808
  import * as path13 from "path";
57038
57809
  function now() {
@@ -57734,12 +58505,12 @@ function renderProposals(proposals, runId, observationCount) {
57734
58505
  }
57735
58506
 
57736
58507
  // src/plugins/builtin/curator/rollup.ts
57737
- import { appendFile as appendFile3, mkdir as mkdir10, writeFile } from "fs/promises";
58508
+ import { appendFile as appendFile3, mkdir as mkdir9, writeFile } from "fs/promises";
57738
58509
  import * as path14 from "path";
57739
58510
  async function appendToRollup(observations, rollupPath) {
57740
58511
  try {
57741
58512
  const dir = path14.dirname(rollupPath);
57742
- await mkdir10(dir, { recursive: true });
58513
+ await mkdir9(dir, { recursive: true });
57743
58514
  if (observations.length === 0) {
57744
58515
  const f = Bun.file(rollupPath);
57745
58516
  if (!await f.exists()) {
@@ -57756,7 +58527,7 @@ async function appendToRollup(observations, rollupPath) {
57756
58527
  var init_rollup = () => {};
57757
58528
 
57758
58529
  // src/plugins/builtin/curator/index.ts
57759
- import { mkdir as mkdir11 } from "fs/promises";
58530
+ import { mkdir as mkdir10 } from "fs/promises";
57760
58531
  import * as path15 from "path";
57761
58532
  function getCuratorEnabled(context) {
57762
58533
  const cfg = context.config;
@@ -57831,7 +58602,7 @@ var init_curator = __esm(() => {
57831
58602
  if (context.outputDir) {
57832
58603
  const { observationsPath, rollupPath } = resolveCuratorOutputs(curatorContext);
57833
58604
  const runDir = path15.dirname(observationsPath);
57834
- await mkdir11(runDir, { recursive: true });
58605
+ await mkdir10(runDir, { recursive: true });
57835
58606
  await Bun.write(observationsPath, observations.map((o) => JSON.stringify(o)).join(`
57836
58607
  `) + (observations.length > 0 ? `
57837
58608
  ` : ""));
@@ -58407,12 +59178,12 @@ var init_loader4 = __esm(() => {
58407
59178
  });
58408
59179
 
58409
59180
  // src/utils/paths.ts
58410
- import { join as join64 } from "path";
59181
+ import { join as join67 } from "path";
58411
59182
  function getRunsDir() {
58412
- return process.env.NAX_RUNS_DIR ?? join64(globalConfigDir(), "runs");
59183
+ return process.env.NAX_RUNS_DIR ?? join67(globalConfigDir(), "runs");
58413
59184
  }
58414
59185
  function getEventsRootDir() {
58415
- return join64(globalConfigDir(), "events");
59186
+ return join67(globalConfigDir(), "events");
58416
59187
  }
58417
59188
  var init_paths3 = __esm(() => {
58418
59189
  init_paths();
@@ -58472,7 +59243,7 @@ var init_command_argv = __esm(() => {
58472
59243
  });
58473
59244
 
58474
59245
  // src/hooks/runner.ts
58475
- import { join as join71 } from "path";
59246
+ import { join as join74 } from "path";
58476
59247
  function createDrainDeadline2(deadlineMs) {
58477
59248
  let timeoutId;
58478
59249
  const promise2 = new Promise((resolve16) => {
@@ -58491,14 +59262,14 @@ async function loadHooksConfig(projectDir, globalDir) {
58491
59262
  let globalHooks = { hooks: {} };
58492
59263
  let projectHooks = { hooks: {} };
58493
59264
  let skipGlobal = false;
58494
- const projectPath = join71(projectDir, "hooks.json");
59265
+ const projectPath = join74(projectDir, "hooks.json");
58495
59266
  const projectData = await loadJsonFile(projectPath, "hooks");
58496
59267
  if (projectData) {
58497
59268
  projectHooks = projectData;
58498
59269
  skipGlobal = projectData.skipGlobal ?? false;
58499
59270
  }
58500
59271
  if (!skipGlobal && globalDir) {
58501
- const globalPath = join71(globalDir, "hooks.json");
59272
+ const globalPath = join74(globalDir, "hooks.json");
58502
59273
  const globalData = await loadJsonFile(globalPath, "hooks");
58503
59274
  if (globalData) {
58504
59275
  globalHooks = globalData;
@@ -58668,7 +59439,7 @@ var package_default;
58668
59439
  var init_package = __esm(() => {
58669
59440
  package_default = {
58670
59441
  name: "@nathapp/nax",
58671
- version: "0.68.8",
59442
+ version: "0.69.1",
58672
59443
  description: "AI Coding Agent Orchestrator \u2014 loops until done",
58673
59444
  type: "module",
58674
59445
  bin: {
@@ -58763,8 +59534,8 @@ var init_version = __esm(() => {
58763
59534
  NAX_VERSION = package_default.version;
58764
59535
  NAX_COMMIT = (() => {
58765
59536
  try {
58766
- if (/^[0-9a-f]{6,10}$/.test("00515ea8"))
58767
- return "00515ea8";
59537
+ if (/^[0-9a-f]{6,10}$/.test("3b2af55b"))
59538
+ return "3b2af55b";
58768
59539
  } catch {}
58769
59540
  try {
58770
59541
  const result = Bun.spawnSync(["git", "rev-parse", "--short", "HEAD"], {
@@ -59640,16 +60411,16 @@ var init_acceptance_loop = __esm(() => {
59640
60411
  });
59641
60412
 
59642
60413
  // src/session/scratch-purge.ts
59643
- import { mkdir as mkdir13, rename, rm } from "fs/promises";
59644
- 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";
59645
60416
  async function purgeStaleScratch(projectDir, featureName, retentionDays, archiveInsteadOfDelete = false) {
59646
- const sessionsDir = join72(projectDir, ".nax", "features", featureName, "sessions");
60417
+ const sessionsDir = join75(projectDir, ".nax", "features", featureName, "sessions");
59647
60418
  const sessionIds = await _scratchPurgeDeps.listSessionDirs(sessionsDir);
59648
60419
  const cutoffMs = _scratchPurgeDeps.now() - retentionDays * 86400000;
59649
60420
  let purged = 0;
59650
60421
  for (const sessionId of sessionIds) {
59651
- const sessionDir = join72(sessionsDir, sessionId);
59652
- const descriptorPath = join72(sessionDir, "descriptor.json");
60422
+ const sessionDir = join75(sessionsDir, sessionId);
60423
+ const descriptorPath = join75(sessionDir, "descriptor.json");
59653
60424
  if (!await _scratchPurgeDeps.fileExists(descriptorPath))
59654
60425
  continue;
59655
60426
  let lastActivityAt;
@@ -59665,7 +60436,7 @@ async function purgeStaleScratch(projectDir, featureName, retentionDays, archive
59665
60436
  if (new Date(lastActivityAt).getTime() >= cutoffMs)
59666
60437
  continue;
59667
60438
  if (archiveInsteadOfDelete) {
59668
- const archiveDest = join72(projectDir, ".nax", "features", featureName, "_archive", "sessions", sessionId);
60439
+ const archiveDest = join75(projectDir, ".nax", "features", featureName, "_archive", "sessions", sessionId);
59669
60440
  await _scratchPurgeDeps.move(sessionDir, archiveDest);
59670
60441
  } else {
59671
60442
  await _scratchPurgeDeps.remove(sessionDir);
@@ -59692,7 +60463,7 @@ var init_scratch_purge = __esm(() => {
59692
60463
  readFile: (path19) => Bun.file(path19).text(),
59693
60464
  remove: (path19) => rm(path19, { recursive: true, force: true }),
59694
60465
  move: async (src, dest) => {
59695
- await mkdir13(dirname12(dest), { recursive: true });
60466
+ await mkdir12(dirname12(dest), { recursive: true });
59696
60467
  await rename(src, dest);
59697
60468
  },
59698
60469
  now: () => Date.now()
@@ -60402,19 +61173,19 @@ function precomputeBatchPlan(stories, maxBatchSize = DEFAULT_MAX_BATCH_SIZE) {
60402
61173
  var DEFAULT_MAX_BATCH_SIZE = 4;
60403
61174
 
60404
61175
  // src/pipeline/subscribers/events-writer.ts
60405
- import { appendFile as appendFile4, mkdir as mkdir14 } from "fs/promises";
60406
- 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";
60407
61178
  function wireEventsWriter(bus, feature, runId, workdir) {
60408
61179
  const logger = getSafeLogger();
60409
61180
  const project = basename13(workdir);
60410
- const eventsDir = join73(getEventsRootDir(), project);
60411
- const eventsFile = join73(eventsDir, "events.jsonl");
61181
+ const eventsDir = join76(getEventsRootDir(), project);
61182
+ const eventsFile = join76(eventsDir, "events.jsonl");
60412
61183
  let dirReady = false;
60413
61184
  const write = (line) => {
60414
61185
  return (async () => {
60415
61186
  try {
60416
61187
  if (!dirReady) {
60417
- await mkdir14(eventsDir, { recursive: true });
61188
+ await mkdir13(eventsDir, { recursive: true });
60418
61189
  dirReady = true;
60419
61190
  }
60420
61191
  await appendFile4(eventsFile, `${JSON.stringify(line)}
@@ -60588,24 +61359,24 @@ var init_interaction2 = __esm(() => {
60588
61359
  });
60589
61360
 
60590
61361
  // src/pipeline/subscribers/registry.ts
60591
- import { mkdir as mkdir15, writeFile as writeFile2 } from "fs/promises";
60592
- 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";
60593
61364
  function wireRegistry(bus, feature, runId, workdir, outputDir) {
60594
61365
  const logger = getSafeLogger();
60595
61366
  const project = basename14(workdir);
60596
- const runDir = join74(getRunsDir(), `${project}-${feature}-${runId}`);
60597
- const metaFile = join74(runDir, "meta.json");
61367
+ const runDir = join77(getRunsDir(), `${project}-${feature}-${runId}`);
61368
+ const metaFile = join77(runDir, "meta.json");
60598
61369
  const unsub = bus.on("run:started", (_ev) => {
60599
61370
  return (async () => {
60600
61371
  try {
60601
- await mkdir15(runDir, { recursive: true });
61372
+ await mkdir14(runDir, { recursive: true });
60602
61373
  const meta3 = {
60603
61374
  runId,
60604
61375
  project,
60605
61376
  feature,
60606
61377
  workdir,
60607
- statusPath: join74(outputDir, "features", feature, "status.json"),
60608
- eventsDir: join74(outputDir, "features", feature, "runs"),
61378
+ statusPath: join77(outputDir, "features", feature, "status.json"),
61379
+ eventsDir: join77(outputDir, "features", feature, "runs"),
60609
61380
  registeredAt: new Date().toISOString()
60610
61381
  };
60611
61382
  await writeFile2(metaFile, JSON.stringify(meta3, null, 2));
@@ -60850,8 +61621,8 @@ var init_types9 = __esm(() => {
60850
61621
  });
60851
61622
 
60852
61623
  // src/worktree/dependencies.ts
60853
- import { existsSync as existsSync31 } from "fs";
60854
- import { join as join75 } from "path";
61624
+ import { existsSync as existsSync30 } from "fs";
61625
+ import { join as join78 } from "path";
60855
61626
  async function prepareWorktreeDependencies(options) {
60856
61627
  const mode = options.config.execution.worktreeDependencies.mode;
60857
61628
  const resolvedCwd = resolveDependencyCwd(options);
@@ -60865,7 +61636,7 @@ async function prepareWorktreeDependencies(options) {
60865
61636
  }
60866
61637
  }
60867
61638
  function resolveDependencyCwd(options) {
60868
- return options.storyWorkdir ? join75(options.worktreeRoot, options.storyWorkdir) : options.worktreeRoot;
61639
+ return options.storyWorkdir ? join78(options.worktreeRoot, options.storyWorkdir) : options.worktreeRoot;
60869
61640
  }
60870
61641
  function resolveInheritedDependencies(options, resolvedCwd) {
60871
61642
  if (hasDependencyManifests(options.worktreeRoot, resolvedCwd)) {
@@ -60875,14 +61646,14 @@ function resolveInheritedDependencies(options, resolvedCwd) {
60875
61646
  }
60876
61647
  function hasDependencyManifests(worktreeRoot, resolvedCwd) {
60877
61648
  const directories = resolvedCwd === worktreeRoot ? [worktreeRoot] : [worktreeRoot, resolvedCwd];
60878
- 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))));
60879
61650
  }
60880
61651
  async function provisionDependencies(config2, worktreeRoot, resolvedCwd) {
60881
- const setupCommand = config2.execution.worktreeDependencies.setupCommand;
60882
- if (!setupCommand) {
61652
+ const setupCommand2 = config2.execution.worktreeDependencies.setupCommand;
61653
+ if (!setupCommand2) {
60883
61654
  throw new WorktreeDependencyPreparationError("[worktree-deps] provision mode requires execution.worktreeDependencies.setupCommand in phase 1.", "provision");
60884
61655
  }
60885
- const argv = parseCommandToArgv(setupCommand);
61656
+ const argv = parseCommandToArgv(setupCommand2);
60886
61657
  if (argv.length === 0) {
60887
61658
  throw new WorktreeDependencyPreparationError("[worktree-deps] setupCommand cannot be empty.", "provision");
60888
61659
  }
@@ -60926,7 +61697,7 @@ var init_dependencies = __esm(() => {
60926
61697
  "build.gradle.kts"
60927
61698
  ];
60928
61699
  _worktreeDependencyDeps = {
60929
- existsSync: existsSync31,
61700
+ existsSync: existsSync30,
60930
61701
  spawn
60931
61702
  };
60932
61703
  });
@@ -60937,19 +61708,19 @@ __export(exports_manager, {
60937
61708
  _managerDeps: () => _managerDeps,
60938
61709
  WorktreeManager: () => WorktreeManager
60939
61710
  });
60940
- import { existsSync as existsSync32, symlinkSync } from "fs";
60941
- import { mkdir as mkdir16 } from "fs/promises";
60942
- 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";
60943
61714
 
60944
61715
  class WorktreeManager {
60945
61716
  async ensureGitExcludes(projectRoot) {
60946
61717
  const logger = getSafeLogger();
60947
- const infoDir = join76(projectRoot, ".git", "info");
60948
- const excludePath = join76(infoDir, "exclude");
61718
+ const infoDir = join79(projectRoot, ".git", "info");
61719
+ const excludePath = join79(infoDir, "exclude");
60949
61720
  try {
60950
- await mkdir16(infoDir, { recursive: true });
61721
+ await mkdir15(infoDir, { recursive: true });
60951
61722
  let existing = "";
60952
- if (existsSync32(excludePath)) {
61723
+ if (existsSync31(excludePath)) {
60953
61724
  existing = await Bun.file(excludePath).text();
60954
61725
  }
60955
61726
  const missing = NAX_GITIGNORE_ENTRIES.filter((entry) => !existing.includes(entry));
@@ -60972,7 +61743,7 @@ ${missing.join(`
60972
61743
  }
60973
61744
  async create(projectRoot, storyId) {
60974
61745
  validateStoryId(storyId);
60975
- const worktreePath = join76(projectRoot, ".nax-wt", storyId);
61746
+ const worktreePath = join79(projectRoot, ".nax-wt", storyId);
60976
61747
  const branchName = `nax/${storyId}`;
60977
61748
  try {
60978
61749
  const pruneProc = _managerDeps.spawn(["git", "worktree", "prune"], {
@@ -61013,9 +61784,9 @@ ${missing.join(`
61013
61784
  }
61014
61785
  throw new Error(`Failed to create worktree: ${String(error48)}`);
61015
61786
  }
61016
- const envSource = join76(projectRoot, ".env");
61017
- if (existsSync32(envSource)) {
61018
- const envTarget = join76(worktreePath, ".env");
61787
+ const envSource = join79(projectRoot, ".env");
61788
+ if (existsSync31(envSource)) {
61789
+ const envTarget = join79(worktreePath, ".env");
61019
61790
  try {
61020
61791
  symlinkSync(envSource, envTarget, "file");
61021
61792
  } catch (error48) {
@@ -61026,7 +61797,7 @@ ${missing.join(`
61026
61797
  }
61027
61798
  async remove(projectRoot, storyId) {
61028
61799
  validateStoryId(storyId);
61029
- const worktreePath = join76(projectRoot, ".nax-wt", storyId);
61800
+ const worktreePath = join79(projectRoot, ".nax-wt", storyId);
61030
61801
  const branchName = `nax/${storyId}`;
61031
61802
  try {
61032
61803
  const proc = _managerDeps.spawn(["git", "worktree", "remove", worktreePath, "--force"], {
@@ -61838,10 +62609,10 @@ var init_merge_conflict_rectify = __esm(() => {
61838
62609
  });
61839
62610
 
61840
62611
  // src/execution/pipeline-result-handler.ts
61841
- import { join as join77 } from "path";
62612
+ import { join as join80 } from "path";
61842
62613
  async function removeWorktreeDirectory(projectRoot, storyId) {
61843
62614
  const logger = getSafeLogger();
61844
- const worktreePath = join77(projectRoot, ".nax-wt", storyId);
62615
+ const worktreePath = join80(projectRoot, ".nax-wt", storyId);
61845
62616
  try {
61846
62617
  const proc = _resultHandlerDeps.spawn(["git", "worktree", "remove", worktreePath, "--force"], {
61847
62618
  cwd: projectRoot,
@@ -62057,8 +62828,8 @@ var init_pipeline_result_handler = __esm(() => {
62057
62828
  });
62058
62829
 
62059
62830
  // src/execution/iteration-runner.ts
62060
- import { existsSync as existsSync33 } from "fs";
62061
- import { join as join78 } from "path";
62831
+ import { existsSync as existsSync32 } from "fs";
62832
+ import { join as join81 } from "path";
62062
62833
  async function runIteration(ctx, prd, selection, iterations, totalCost, allStoryMetrics) {
62063
62834
  const { story, storiesToExecute, routing, isBatchExecution } = selection;
62064
62835
  if (ctx.dryRun) {
@@ -62083,7 +62854,7 @@ async function runIteration(ctx, prd, selection, iterations, totalCost, allStory
62083
62854
  const storyStartTime = Date.now();
62084
62855
  let effectiveWorkdir = ctx.workdir;
62085
62856
  if (ctx.config.execution.storyIsolation === "worktree") {
62086
- const worktreePath = join78(ctx.workdir, ".nax-wt", story.id);
62857
+ const worktreePath = join81(ctx.workdir, ".nax-wt", story.id);
62087
62858
  const worktreeExists = _iterationRunnerDeps.existsSync(worktreePath);
62088
62859
  if (!worktreeExists) {
62089
62860
  await _iterationRunnerDeps.worktreeManager.ensureGitExcludes(ctx.workdir);
@@ -62103,7 +62874,7 @@ async function runIteration(ctx, prd, selection, iterations, totalCost, allStory
62103
62874
  }
62104
62875
  const accumulatedAttemptCost = (story.priorFailures || []).reduce((sum, f) => sum + (f.cost || 0), 0);
62105
62876
  const profileOverride = ctx.config.profile && ctx.config.profile !== "default" ? { profile: ctx.config.profile } : undefined;
62106
- 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;
62107
62878
  let dependencyContext;
62108
62879
  if (ctx.config.execution.storyIsolation === "worktree") {
62109
62880
  try {
@@ -62130,7 +62901,7 @@ async function runIteration(ctx, prd, selection, iterations, totalCost, allStory
62130
62901
  };
62131
62902
  }
62132
62903
  }
62133
- 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;
62134
62905
  const pipelineContext = {
62135
62906
  config: effectiveConfig,
62136
62907
  rootConfig: ctx.config,
@@ -62262,7 +63033,7 @@ var init_iteration_runner = __esm(() => {
62262
63033
  loadConfigForWorkdir,
62263
63034
  prepareWorktreeDependencies,
62264
63035
  runPipeline,
62265
- existsSync: existsSync33,
63036
+ existsSync: existsSync32,
62266
63037
  worktreeManager: new WorktreeManager
62267
63038
  };
62268
63039
  });
@@ -62332,7 +63103,7 @@ __export(exports_parallel_worker, {
62332
63103
  buildWorktreePipelineContext: () => buildWorktreePipelineContext,
62333
63104
  _parallelWorkerDeps: () => _parallelWorkerDeps
62334
63105
  });
62335
- import { join as join79 } from "path";
63106
+ import { join as join82 } from "path";
62336
63107
  function buildWorktreePipelineContext(base, _story) {
62337
63108
  return { ...base, prd: structuredClone(base.prd) };
62338
63109
  }
@@ -62355,7 +63126,7 @@ async function executeStoryInWorktree(story, worktreePath, dependencyContext, co
62355
63126
  story,
62356
63127
  stories: [story],
62357
63128
  projectDir: context.projectDir,
62358
- workdir: dependencyContext.cwd ?? (story.workdir ? join79(worktreePath, story.workdir) : worktreePath),
63129
+ workdir: dependencyContext.cwd ?? (story.workdir ? join82(worktreePath, story.workdir) : worktreePath),
62359
63130
  worktreeDependencyContext: dependencyContext,
62360
63131
  routing,
62361
63132
  storyGitRef: storyGitRef ?? undefined
@@ -63242,7 +64013,7 @@ async function writeStatusFile(filePath, status) {
63242
64013
  var init_status_file = () => {};
63243
64014
 
63244
64015
  // src/execution/status-writer.ts
63245
- import { join as join80 } from "path";
64016
+ import { join as join83 } from "path";
63246
64017
 
63247
64018
  class StatusWriter {
63248
64019
  statusFile;
@@ -63361,7 +64132,7 @@ class StatusWriter {
63361
64132
  if (!this._prd)
63362
64133
  return;
63363
64134
  const safeLogger = getSafeLogger();
63364
- const featureStatusPath = join80(featureDir, "status.json");
64135
+ const featureStatusPath = join83(featureDir, "status.json");
63365
64136
  const write = async () => {
63366
64137
  try {
63367
64138
  const base = this.getSnapshot(totalCost, iterations);
@@ -63392,11 +64163,11 @@ __export(exports_migrate, {
63392
64163
  migrateCommand: () => migrateCommand,
63393
64164
  detectGeneratedContent: () => detectGeneratedContent
63394
64165
  });
63395
- import { existsSync as existsSync34 } from "fs";
63396
- 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";
63397
64168
  import path22 from "path";
63398
64169
  async function detectGeneratedContent(naxDir) {
63399
- if (!existsSync34(naxDir))
64170
+ if (!existsSync33(naxDir))
63400
64171
  return [];
63401
64172
  const candidates = [];
63402
64173
  let entries = [];
@@ -63411,7 +64182,7 @@ async function detectGeneratedContent(naxDir) {
63411
64182
  }
63412
64183
  }
63413
64184
  const featuresDir = path22.join(naxDir, "features");
63414
- if (existsSync34(featuresDir)) {
64185
+ if (existsSync33(featuresDir)) {
63415
64186
  let featureDirs = [];
63416
64187
  try {
63417
64188
  featureDirs = await readdir6(featuresDir);
@@ -63473,7 +64244,7 @@ async function migrateCommand(options) {
63473
64244
  });
63474
64245
  }
63475
64246
  const src = path22.join(globalConfigDir(), options.reclaim);
63476
- if (!existsSync34(src)) {
64247
+ if (!existsSync33(src)) {
63477
64248
  throw new NaxError(`Nothing to reclaim: ~/.nax/${options.reclaim} does not exist`, "MIGRATE_RECLAIM_NOT_FOUND", {
63478
64249
  stage: "migrate",
63479
64250
  name: options.reclaim
@@ -63481,7 +64252,7 @@ async function migrateCommand(options) {
63481
64252
  }
63482
64253
  const archiveBase = path22.join(globalConfigDir(), "_archive");
63483
64254
  const archiveDest = path22.join(archiveBase, `${options.reclaim}-${Date.now()}`);
63484
- await mkdir17(archiveBase, { recursive: true });
64255
+ await mkdir16(archiveBase, { recursive: true });
63485
64256
  await rename3(src, archiveDest);
63486
64257
  logger.info("migrate", `Reclaimed: archived to ${archiveDest}`, { storyId: "_migrate" });
63487
64258
  return;
@@ -63519,7 +64290,7 @@ async function migrateCommand(options) {
63519
64290
  }
63520
64291
  const naxDir = path22.join(options.workdir, ".nax");
63521
64292
  const configPath = path22.join(naxDir, "config.json");
63522
- if (!existsSync34(configPath)) {
64293
+ if (!existsSync33(configPath)) {
63523
64294
  throw new NaxError("No .nax/config.json found \u2014 run nax init first", "MIGRATE_NO_CONFIG", {
63524
64295
  stage: "migrate",
63525
64296
  workdir: options.workdir
@@ -63549,12 +64320,12 @@ async function migrateCommand(options) {
63549
64320
  }
63550
64321
  return;
63551
64322
  }
63552
- await mkdir17(destBase, { recursive: true });
64323
+ await mkdir16(destBase, { recursive: true });
63553
64324
  let moved = 0;
63554
64325
  for (const candidate of candidates) {
63555
64326
  const dest = path22.join(destBase, candidate.name);
63556
- await mkdir17(path22.dirname(dest), { recursive: true });
63557
- if (existsSync34(dest)) {
64327
+ await mkdir16(path22.dirname(dest), { recursive: true });
64328
+ if (existsSync33(dest)) {
63558
64329
  throw new NaxError(`Migration conflict: destination already exists.
63559
64330
  Source: ${candidate.srcPath}
63560
64331
  Destination: ${dest}
@@ -63795,7 +64566,7 @@ __export(exports_run_initialization, {
63795
64566
  initializeRun: () => initializeRun,
63796
64567
  _reconcileDeps: () => _reconcileDeps
63797
64568
  });
63798
- import { join as join81 } from "path";
64569
+ import { join as join84 } from "path";
63799
64570
  async function reconcileState(prd, prdPath, workdir, config2) {
63800
64571
  const logger = getSafeLogger();
63801
64572
  let reconciledCount = 0;
@@ -63812,7 +64583,7 @@ async function reconcileState(prd, prdPath, workdir, config2) {
63812
64583
  });
63813
64584
  continue;
63814
64585
  }
63815
- const effectiveWorkdir = story.workdir ? join81(workdir, story.workdir) : workdir;
64586
+ const effectiveWorkdir = story.workdir ? join84(workdir, story.workdir) : workdir;
63816
64587
  try {
63817
64588
  const reviewResult = await _reconcileDeps.runReview(config2.review, effectiveWorkdir, config2.execution);
63818
64589
  if (!reviewResult.success) {
@@ -93605,7 +94376,7 @@ __export(exports_curator, {
93605
94376
  });
93606
94377
  import { readdirSync as readdirSync8 } from "fs";
93607
94378
  import { unlink as unlink4 } from "fs/promises";
93608
- import { basename as basename15, join as join83 } from "path";
94379
+ import { basename as basename15, join as join86 } from "path";
93609
94380
  function getProjectKey(config2, projectDir) {
93610
94381
  return config2.name?.trim() || basename15(projectDir);
93611
94382
  }
@@ -93688,7 +94459,7 @@ async function curatorStatus(options) {
93688
94459
  const config2 = await _curatorCmdDeps.loadConfig(resolved.projectDir);
93689
94460
  const projectKey = getProjectKey(config2, resolved.projectDir);
93690
94461
  const outputDir = _curatorCmdDeps.projectOutputDir(projectKey, config2.outputDir);
93691
- const runsDir = join83(outputDir, "runs");
94462
+ const runsDir = join86(outputDir, "runs");
93692
94463
  const runIds = listRunIds(runsDir);
93693
94464
  let runId;
93694
94465
  if (options.run) {
@@ -93705,8 +94476,8 @@ async function curatorStatus(options) {
93705
94476
  runId = runIds[runIds.length - 1];
93706
94477
  }
93707
94478
  console.log(`Run: ${runId}`);
93708
- const runDir = join83(runsDir, runId);
93709
- const observationsPath = join83(runDir, "observations.jsonl");
94479
+ const runDir = join86(runsDir, runId);
94480
+ const observationsPath = join86(runDir, "observations.jsonl");
93710
94481
  const observations = await parseObservations(observationsPath);
93711
94482
  const counts = new Map;
93712
94483
  for (const obs of observations) {
@@ -93716,7 +94487,7 @@ async function curatorStatus(options) {
93716
94487
  for (const [kind, count] of counts.entries()) {
93717
94488
  console.log(` ${kind}: ${count}`);
93718
94489
  }
93719
- const proposalsPath = join83(runDir, "curator-proposals.md");
94490
+ const proposalsPath = join86(runDir, "curator-proposals.md");
93720
94491
  const proposalText = await _curatorCmdDeps.readFile(proposalsPath).catch(() => null);
93721
94492
  if (proposalText !== null) {
93722
94493
  console.log("");
@@ -93730,8 +94501,8 @@ async function curatorCommit(options) {
93730
94501
  const config2 = await _curatorCmdDeps.loadConfig(resolved.projectDir);
93731
94502
  const projectKey = getProjectKey(config2, resolved.projectDir);
93732
94503
  const outputDir = _curatorCmdDeps.projectOutputDir(projectKey, config2.outputDir);
93733
- const runDir = join83(outputDir, "runs", options.runId);
93734
- const proposalsPath = join83(runDir, "curator-proposals.md");
94504
+ const runDir = join86(outputDir, "runs", options.runId);
94505
+ const proposalsPath = join86(runDir, "curator-proposals.md");
93735
94506
  const proposalText = await _curatorCmdDeps.readFile(proposalsPath).catch(() => null);
93736
94507
  if (proposalText === null) {
93737
94508
  console.log(`curator-proposals.md not found for run ${options.runId}.`);
@@ -93747,7 +94518,7 @@ async function curatorCommit(options) {
93747
94518
  const dropFileState = new Map;
93748
94519
  const skippedDrops = new Set;
93749
94520
  for (const drop2 of drops) {
93750
- const targetPath = join83(resolved.projectDir, drop2.canonicalFile);
94521
+ const targetPath = join86(resolved.projectDir, drop2.canonicalFile);
93751
94522
  if (!dropFileState.has(targetPath)) {
93752
94523
  const fileExists2 = await Bun.file(targetPath).exists();
93753
94524
  const existing = fileExists2 ? await _curatorCmdDeps.readFile(targetPath).catch(() => "") : "";
@@ -93781,7 +94552,7 @@ async function curatorCommit(options) {
93781
94552
  if (skippedDrops.has(drop2)) {
93782
94553
  continue;
93783
94554
  }
93784
- const targetPath = join83(resolved.projectDir, drop2.canonicalFile);
94555
+ const targetPath = join86(resolved.projectDir, drop2.canonicalFile);
93785
94556
  const existing = await _curatorCmdDeps.readFile(targetPath).catch(() => "");
93786
94557
  const filtered = filterDropContent(existing, drop2.description);
93787
94558
  await _curatorCmdDeps.writeFile(targetPath, filtered);
@@ -93790,7 +94561,7 @@ async function curatorCommit(options) {
93790
94561
  }
93791
94562
  const adds = proposals.filter((p) => p.action === "add" || p.action === "advisory");
93792
94563
  for (const add2 of adds) {
93793
- const targetPath = join83(resolved.projectDir, add2.canonicalFile);
94564
+ const targetPath = join86(resolved.projectDir, add2.canonicalFile);
93794
94565
  const content = buildAddContent(add2);
93795
94566
  await _curatorCmdDeps.appendFile(targetPath, content);
93796
94567
  modifiedFiles.add(targetPath);
@@ -93827,7 +94598,7 @@ async function curatorDryrun(options) {
93827
94598
  const config2 = await _curatorCmdDeps.loadConfig(resolved.projectDir);
93828
94599
  const projectKey = getProjectKey(config2, resolved.projectDir);
93829
94600
  const outputDir = _curatorCmdDeps.projectOutputDir(projectKey, config2.outputDir);
93830
- const runsDir = join83(outputDir, "runs");
94601
+ const runsDir = join86(outputDir, "runs");
93831
94602
  const runIds = listRunIds(runsDir);
93832
94603
  if (runIds.length === 0) {
93833
94604
  console.log("No runs found.");
@@ -93838,7 +94609,7 @@ async function curatorDryrun(options) {
93838
94609
  console.log(`Run ${options.run} not found in ${runsDir}.`);
93839
94610
  return;
93840
94611
  }
93841
- const observationsPath = join83(runsDir, runId, "observations.jsonl");
94612
+ const observationsPath = join86(runsDir, runId, "observations.jsonl");
93842
94613
  const observations = await parseObservations(observationsPath);
93843
94614
  const thresholds = getThresholds(config2);
93844
94615
  const proposals = runHeuristics(observations, thresholds);
@@ -93879,12 +94650,12 @@ async function curatorGc(options) {
93879
94650
  await _curatorCmdDeps.writeFile(rollupPath, newContent);
93880
94651
  const projectKey = getProjectKey(config2, resolved.projectDir);
93881
94652
  const outputDir = _curatorCmdDeps.projectOutputDir(projectKey, config2.outputDir);
93882
- const perRunsDir = join83(outputDir, "runs");
94653
+ const perRunsDir = join86(outputDir, "runs");
93883
94654
  for (const runId of uniqueRunIds) {
93884
94655
  if (!keepSet.has(runId)) {
93885
- const runDir = join83(perRunsDir, runId);
93886
- await _curatorCmdDeps.removeFile(join83(runDir, "observations.jsonl"));
93887
- 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"));
93888
94659
  }
93889
94660
  }
93890
94661
  console.log(`[gc] Pruned rollup to ${keep} most recent runs (was ${uniqueRunIds.length}).`);
@@ -93927,9 +94698,9 @@ var init_curator2 = __esm(() => {
93927
94698
 
93928
94699
  // bin/nax.ts
93929
94700
  init_source();
93930
- import { existsSync as existsSync36, mkdirSync as mkdirSync7 } from "fs";
94701
+ import { existsSync as existsSync35, mkdirSync as mkdirSync7 } from "fs";
93931
94702
  import { homedir as homedir3 } from "os";
93932
- import { basename as basename16, join as join84 } from "path";
94703
+ import { basename as basename16, join as join87 } from "path";
93933
94704
 
93934
94705
  // node_modules/commander/esm.mjs
93935
94706
  var import__ = __toESM(require_commander(), 1);
@@ -95019,6 +95790,7 @@ async function runsShowCommand(options) {
95019
95790
  // src/cli/index.ts
95020
95791
  init_prompts2();
95021
95792
  init_init2();
95793
+ init_setup();
95022
95794
 
95023
95795
  // src/cli/plugins.ts
95024
95796
  init_paths();
@@ -95093,8 +95865,8 @@ init_interaction();
95093
95865
  init_source();
95094
95866
  init_loader();
95095
95867
  init_generator2();
95096
- import { existsSync as existsSync25 } from "fs";
95097
- import { join as join58 } from "path";
95868
+ import { existsSync as existsSync24 } from "fs";
95869
+ import { join as join61 } from "path";
95098
95870
  var VALID_AGENTS = ["claude", "codex", "opencode", "cursor", "windsurf", "aider", "gemini"];
95099
95871
  async function generateCommand(options) {
95100
95872
  const workdir = options.dir ?? process.cwd();
@@ -95137,7 +95909,7 @@ async function generateCommand(options) {
95137
95909
  return;
95138
95910
  }
95139
95911
  if (options.package) {
95140
- const packageDir = join58(workdir, options.package);
95912
+ const packageDir = join61(workdir, options.package);
95141
95913
  if (dryRun) {
95142
95914
  console.log(source_default.yellow("\u26A0 Dry run \u2014 no files will be written"));
95143
95915
  }
@@ -95157,10 +95929,10 @@ async function generateCommand(options) {
95157
95929
  process.exit(1);
95158
95930
  return;
95159
95931
  }
95160
- const contextPath = options.context ? join58(workdir, options.context) : join58(workdir, ".nax/context.md");
95161
- 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;
95162
95934
  const autoInject = !options.noAutoInject;
95163
- if (!existsSync25(contextPath)) {
95935
+ if (!existsSync24(contextPath)) {
95164
95936
  console.error(source_default.red(`\u2717 Context file not found: ${contextPath}`));
95165
95937
  console.error(source_default.yellow(" Create .nax/context.md first, or run `nax init` to scaffold it."));
95166
95938
  process.exit(1);
@@ -95263,8 +96035,8 @@ async function generateCommand(options) {
95263
96035
  }
95264
96036
  // src/cli/config-display.ts
95265
96037
  init_loader();
95266
- import { existsSync as existsSync27 } from "fs";
95267
- import { join as join60 } from "path";
96038
+ import { existsSync as existsSync26 } from "fs";
96039
+ import { join as join63 } from "path";
95268
96040
 
95269
96041
  // src/cli/config-descriptions.ts
95270
96042
  var FIELD_DESCRIPTIONS = {
@@ -95515,10 +96287,10 @@ function deepEqual(a, b) {
95515
96287
  // src/cli/config-get.ts
95516
96288
  init_defaults();
95517
96289
  init_loader();
95518
- import { existsSync as existsSync26 } from "fs";
95519
- import { join as join59 } from "path";
96290
+ import { existsSync as existsSync25 } from "fs";
96291
+ import { join as join62 } from "path";
95520
96292
  async function loadConfigFile(path18) {
95521
- if (!existsSync26(path18))
96293
+ if (!existsSync25(path18))
95522
96294
  return null;
95523
96295
  try {
95524
96296
  return await Bun.file(path18).json();
@@ -95538,7 +96310,7 @@ async function loadProjectConfig() {
95538
96310
  const projectDir = findProjectDir();
95539
96311
  if (!projectDir)
95540
96312
  return null;
95541
- const projectPath = join59(projectDir, "config.json");
96313
+ const projectPath = join62(projectDir, "config.json");
95542
96314
  return await loadConfigFile(projectPath);
95543
96315
  }
95544
96316
 
@@ -95598,14 +96370,14 @@ async function configCommand(config2, options = {}) {
95598
96370
  function determineConfigSources() {
95599
96371
  const globalPath = globalConfigPath();
95600
96372
  const projectDir = findProjectDir();
95601
- const projectPath = projectDir ? join60(projectDir, "config.json") : null;
96373
+ const projectPath = projectDir ? join63(projectDir, "config.json") : null;
95602
96374
  return {
95603
96375
  global: fileExists(globalPath) ? globalPath : null,
95604
96376
  project: projectPath && fileExists(projectPath) ? projectPath : null
95605
96377
  };
95606
96378
  }
95607
96379
  function fileExists(path18) {
95608
- return existsSync27(path18);
96380
+ return existsSync26(path18);
95609
96381
  }
95610
96382
  function displayConfigWithDescriptions(obj, path18, sources, indent = 0) {
95611
96383
  const indentStr = " ".repeat(indent);
@@ -95747,15 +96519,15 @@ init_paths();
95747
96519
  init_profile();
95748
96520
  import { mkdirSync as mkdirSync5 } from "fs";
95749
96521
  import { readdirSync as readdirSync5 } from "fs";
95750
- import { join as join61 } from "path";
96522
+ import { join as join64 } from "path";
95751
96523
  var _profileCLIDeps = {
95752
96524
  env: process.env
95753
96525
  };
95754
96526
  var SENSITIVE_KEY_PATTERN = /key|token|secret|password|credential/i;
95755
96527
  var VAR_PATTERN = /\$[A-Za-z_][A-Za-z0-9_]*/;
95756
96528
  async function profileListCommand(startDir) {
95757
- const globalProfilesDir = join61(globalConfigDir(), "profiles");
95758
- const projectProfilesDir = join61(projectConfigDir(startDir), "profiles");
96529
+ const globalProfilesDir = join64(globalConfigDir(), "profiles");
96530
+ const projectProfilesDir = join64(projectConfigDir(startDir), "profiles");
95759
96531
  const globalProfiles = scanProfileDir(globalProfilesDir);
95760
96532
  const projectProfiles = scanProfileDir(projectProfilesDir);
95761
96533
  const activeProfile = await resolveProfileName({}, _profileCLIDeps.env, startDir);
@@ -95814,7 +96586,7 @@ function maskProfileValues(obj) {
95814
96586
  return result;
95815
96587
  }
95816
96588
  async function profileUseCommand(profileName, startDir) {
95817
- const configPath = join61(projectConfigDir(startDir), "config.json");
96589
+ const configPath = join64(projectConfigDir(startDir), "config.json");
95818
96590
  const configFile = Bun.file(configPath);
95819
96591
  let existing = {};
95820
96592
  if (await configFile.exists()) {
@@ -95833,8 +96605,8 @@ async function profileCurrentCommand(startDir) {
95833
96605
  return resolveProfileName({}, _profileCLIDeps.env, startDir);
95834
96606
  }
95835
96607
  async function profileCreateCommand(profileName, startDir) {
95836
- const profilesDir = join61(projectConfigDir(startDir), "profiles");
95837
- const profilePath = join61(profilesDir, `${profileName}.json`);
96608
+ const profilesDir = join64(projectConfigDir(startDir), "profiles");
96609
+ const profilePath = join64(profilesDir, `${profileName}.json`);
95838
96610
  const profileFile = Bun.file(profilePath);
95839
96611
  if (await profileFile.exists()) {
95840
96612
  throw new Error(`Profile "${profileName}" already exists at ${profilePath}`);
@@ -95955,8 +96727,8 @@ async function contextInspectCommand(options) {
95955
96727
  // src/cli/rules.ts
95956
96728
  init_canonical_loader();
95957
96729
  init_errors();
95958
- import { mkdir as mkdir12 } from "fs/promises";
95959
- 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";
95960
96732
  var _rulesCLIDeps = {
95961
96733
  readFile: async (path18) => Bun.file(path18).text(),
95962
96734
  writeFile: async (path18, content) => {
@@ -95965,13 +96737,13 @@ var _rulesCLIDeps = {
95965
96737
  fileExists: async (path18) => Bun.file(path18).exists(),
95966
96738
  globInDir: (dir) => {
95967
96739
  try {
95968
- 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));
95969
96741
  } catch {
95970
96742
  return [];
95971
96743
  }
95972
96744
  },
95973
96745
  mkdir: async (path18) => {
95974
- await mkdir12(path18, { recursive: true });
96746
+ await mkdir11(path18, { recursive: true });
95975
96747
  },
95976
96748
  globCanonicalRuleFiles: (workdir) => {
95977
96749
  try {
@@ -96014,7 +96786,7 @@ ${r.content}`).join(`
96014
96786
  `);
96015
96787
  const shimContent = `${header + body}
96016
96788
  `;
96017
- const shimPath = join62(workdir, shimFileName);
96789
+ const shimPath = join65(workdir, shimFileName);
96018
96790
  if (options.dryRun) {
96019
96791
  console.log(`[dry-run] Would write ${shimPath} (${shimContent.length} bytes)`);
96020
96792
  return;
@@ -96043,14 +96815,14 @@ function neutralizeContent(content) {
96043
96815
  }
96044
96816
  async function collectMigrationSources(workdir) {
96045
96817
  const sources = [];
96046
- const claudeMdPath = join62(workdir, "CLAUDE.md");
96818
+ const claudeMdPath = join65(workdir, "CLAUDE.md");
96047
96819
  if (await _rulesCLIDeps.fileExists(claudeMdPath)) {
96048
96820
  const content = await _rulesCLIDeps.readFile(claudeMdPath);
96049
96821
  if (content.trim()) {
96050
96822
  sources.push({ sourcePath: claudeMdPath, targetFileName: "project-conventions.md", content });
96051
96823
  }
96052
96824
  }
96053
- const rulesDir = join62(workdir, ".claude", "rules");
96825
+ const rulesDir = join65(workdir, ".claude", "rules");
96054
96826
  const ruleFiles = _rulesCLIDeps.globInDir(rulesDir);
96055
96827
  for (const filePath of ruleFiles) {
96056
96828
  try {
@@ -96070,7 +96842,7 @@ async function rulesMigrateCommand(options) {
96070
96842
  console.log("[WARN] No source files found (checked CLAUDE.md and .claude/rules/*.md). Nothing to migrate.");
96071
96843
  return;
96072
96844
  }
96073
- const targetDir = join62(workdir, CANONICAL_RULES_DIR);
96845
+ const targetDir = join65(workdir, CANONICAL_RULES_DIR);
96074
96846
  if (!options.dryRun) {
96075
96847
  try {
96076
96848
  await _rulesCLIDeps.mkdir(targetDir);
@@ -96081,7 +96853,7 @@ async function rulesMigrateCommand(options) {
96081
96853
  let written = 0;
96082
96854
  let skipped = 0;
96083
96855
  for (const { sourcePath, targetFileName, content } of sources) {
96084
- const targetPath = join62(targetDir, targetFileName);
96856
+ const targetPath = join65(targetDir, targetFileName);
96085
96857
  if (!force && !options.dryRun && await _rulesCLIDeps.fileExists(targetPath)) {
96086
96858
  console.log(`[skip] ${targetFileName} already exists (use --force to overwrite)`);
96087
96859
  skipped++;
@@ -96120,7 +96892,7 @@ function collectCanonicalRuleRoots(workdir) {
96120
96892
  const packageRel = normalized.slice(0, idx);
96121
96893
  if (!packageRel)
96122
96894
  continue;
96123
- roots.add(join62(workdir, packageRel));
96895
+ roots.add(join65(workdir, packageRel));
96124
96896
  }
96125
96897
  return [...roots].sort();
96126
96898
  }
@@ -96142,7 +96914,7 @@ init_logger2();
96142
96914
  init_detect2();
96143
96915
  init_workspace();
96144
96916
  init_common();
96145
- import { join as join63 } from "path";
96917
+ import { join as join66 } from "path";
96146
96918
  function resolveEffective(detected, configPatterns) {
96147
96919
  if (configPatterns !== undefined)
96148
96920
  return "config";
@@ -96227,7 +96999,7 @@ async function detectCommand(options) {
96227
96999
  const rootDetected = detectionMap[""] ?? { patterns: [], confidence: "empty", sources: [] };
96228
97000
  const pkgEntries = await Promise.all(packageDirs.map(async (dir) => {
96229
97001
  const det = detectionMap[dir] ?? { patterns: [], confidence: "empty", sources: [] };
96230
- const pkgConfigPath = join63(workdir, ".nax", "mono", dir, "config.json");
97002
+ const pkgConfigPath = join66(workdir, ".nax", "mono", dir, "config.json");
96231
97003
  const pkgRaw = await loadRawConfig(pkgConfigPath);
96232
97004
  const pkgPatterns = deepGet(pkgRaw, TEST_PATTERNS_KEY);
96233
97005
  const effective = Array.isArray(pkgPatterns) ? pkgPatterns : undefined;
@@ -96281,13 +97053,13 @@ async function detectCommand(options) {
96281
97053
  if (rootDetected.confidence === "empty") {
96282
97054
  console.log(source_default.yellow(" root: skipped (empty detection)"));
96283
97055
  } else {
96284
- const rootConfigPath = join63(workdir, ".nax", "config.json");
97056
+ const rootConfigPath = join66(workdir, ".nax", "config.json");
96285
97057
  try {
96286
97058
  const status = await applyToConfig(rootConfigPath, rootDetected.patterns, options.force ?? false);
96287
97059
  if (status === "skipped") {
96288
97060
  console.log(source_default.dim(" root: skipped (testFilePatterns already set; use --force to overwrite)"));
96289
97061
  } else {
96290
- 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")}`));
96291
97063
  }
96292
97064
  } catch (err) {
96293
97065
  console.error(source_default.red(` root: write failed \u2014 ${err.message}`));
@@ -96300,13 +97072,13 @@ async function detectCommand(options) {
96300
97072
  console.log(source_default.dim(` ${dir}: skipped (empty detection)`));
96301
97073
  continue;
96302
97074
  }
96303
- const pkgConfigPath = join63(workdir, ".nax", "mono", dir, "config.json");
97075
+ const pkgConfigPath = join66(workdir, ".nax", "mono", dir, "config.json");
96304
97076
  try {
96305
97077
  const status = await applyToConfig(pkgConfigPath, det.patterns, options.force ?? false);
96306
97078
  if (status === "skipped") {
96307
97079
  console.log(source_default.dim(` ${dir}: skipped (already set)`));
96308
97080
  } else {
96309
- 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")}`));
96310
97082
  }
96311
97083
  } catch (err) {
96312
97084
  console.error(source_default.red(` ${dir}: write failed \u2014 ${err.message}`));
@@ -96323,20 +97095,20 @@ async function detectCommand(options) {
96323
97095
 
96324
97096
  // src/commands/logs.ts
96325
97097
  init_common();
96326
- import { existsSync as existsSync29 } from "fs";
96327
- import { join as join67 } from "path";
97098
+ import { existsSync as existsSync28 } from "fs";
97099
+ import { join as join70 } from "path";
96328
97100
 
96329
97101
  // src/commands/logs-formatter.ts
96330
97102
  init_source();
96331
97103
  init_formatter();
96332
97104
  import { readdirSync as readdirSync7 } from "fs";
96333
- import { join as join66 } from "path";
97105
+ import { join as join69 } from "path";
96334
97106
 
96335
97107
  // src/commands/logs-reader.ts
96336
97108
  init_paths3();
96337
- import { existsSync as existsSync28, readdirSync as readdirSync6 } from "fs";
97109
+ import { existsSync as existsSync27, readdirSync as readdirSync6 } from "fs";
96338
97110
  import { readdir as readdir4 } from "fs/promises";
96339
- import { join as join65 } from "path";
97111
+ import { join as join68 } from "path";
96340
97112
  var _logsReaderDeps = {
96341
97113
  getRunsDir
96342
97114
  };
@@ -96350,7 +97122,7 @@ async function resolveRunFileFromRegistry(runId) {
96350
97122
  }
96351
97123
  let matched = null;
96352
97124
  for (const entry of entries) {
96353
- const metaPath = join65(runsDir, entry, "meta.json");
97125
+ const metaPath = join68(runsDir, entry, "meta.json");
96354
97126
  try {
96355
97127
  const meta3 = await Bun.file(metaPath).json();
96356
97128
  if (meta3.runId === runId || meta3.runId.startsWith(runId)) {
@@ -96362,7 +97134,7 @@ async function resolveRunFileFromRegistry(runId) {
96362
97134
  if (!matched) {
96363
97135
  throw new Error(`Run not found in registry: ${runId}`);
96364
97136
  }
96365
- if (!existsSync28(matched.eventsDir)) {
97137
+ if (!existsSync27(matched.eventsDir)) {
96366
97138
  console.log(`Log directory unavailable for run: ${runId}`);
96367
97139
  return null;
96368
97140
  }
@@ -96372,14 +97144,14 @@ async function resolveRunFileFromRegistry(runId) {
96372
97144
  return null;
96373
97145
  }
96374
97146
  const specificFile = files.find((f) => f === `${matched.runId}.jsonl`);
96375
- return join65(matched.eventsDir, specificFile ?? files[0]);
97147
+ return join68(matched.eventsDir, specificFile ?? files[0]);
96376
97148
  }
96377
97149
  async function selectRunFile(runsDir) {
96378
97150
  const files = readdirSync6(runsDir).filter((f) => f.endsWith(".jsonl") && f !== "latest.jsonl").sort().reverse();
96379
97151
  if (files.length === 0) {
96380
97152
  return null;
96381
97153
  }
96382
- return join65(runsDir, files[0]);
97154
+ return join68(runsDir, files[0]);
96383
97155
  }
96384
97156
  async function extractRunSummary(filePath) {
96385
97157
  const file3 = Bun.file(filePath);
@@ -96465,7 +97237,7 @@ Runs:
96465
97237
  console.log(source_default.gray(" Timestamp Stories Duration Cost Status"));
96466
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"));
96467
97239
  for (const file3 of files) {
96468
- const filePath = join66(runsDir, file3);
97240
+ const filePath = join69(runsDir, file3);
96469
97241
  const summary = await extractRunSummary(filePath);
96470
97242
  const timestamp = file3.replace(".jsonl", "");
96471
97243
  const stories = summary ? `${summary.passed}/${summary.total}` : "?/?";
@@ -96579,7 +97351,7 @@ async function logsCommand(options) {
96579
97351
  return;
96580
97352
  }
96581
97353
  const resolved = resolveProject({ dir: options.dir });
96582
- const naxDir = join67(resolved.projectDir, ".nax");
97354
+ const naxDir = join70(resolved.projectDir, ".nax");
96583
97355
  const configPath = resolved.configPath;
96584
97356
  const configFile = Bun.file(configPath);
96585
97357
  const config2 = await configFile.json();
@@ -96587,9 +97359,9 @@ async function logsCommand(options) {
96587
97359
  if (!featureName) {
96588
97360
  throw new Error("No feature specified in config.json");
96589
97361
  }
96590
- const featureDir = join67(naxDir, "features", featureName);
96591
- const runsDir = join67(featureDir, "runs");
96592
- if (!existsSync29(runsDir)) {
97362
+ const featureDir = join70(naxDir, "features", featureName);
97363
+ const runsDir = join70(featureDir, "runs");
97364
+ if (!existsSync28(runsDir)) {
96593
97365
  throw new Error(`No runs directory found for feature: ${featureName}`);
96594
97366
  }
96595
97367
  if (options.list) {
@@ -96613,8 +97385,8 @@ init_config();
96613
97385
  init_prd();
96614
97386
  init_precheck();
96615
97387
  init_common();
96616
- import { existsSync as existsSync30 } from "fs";
96617
- import { join as join68 } from "path";
97388
+ import { existsSync as existsSync29 } from "fs";
97389
+ import { join as join71 } from "path";
96618
97390
  async function precheckCommand(options) {
96619
97391
  const resolved = resolveProject({
96620
97392
  dir: options.dir,
@@ -96636,14 +97408,14 @@ async function precheckCommand(options) {
96636
97408
  process.exit(1);
96637
97409
  }
96638
97410
  }
96639
- const naxDir = join68(resolved.projectDir, ".nax");
96640
- const featureDir = join68(naxDir, "features", featureName);
96641
- const prdPath = join68(featureDir, "prd.json");
96642
- 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)) {
96643
97415
  console.error(source_default.red(`Feature not found: ${featureName}`));
96644
97416
  process.exit(1);
96645
97417
  }
96646
- if (!existsSync30(prdPath)) {
97418
+ if (!existsSync29(prdPath)) {
96647
97419
  console.error(source_default.red(`Missing prd.json for feature: ${featureName}`));
96648
97420
  console.error(source_default.dim(`Run: nax plan -f ${featureName} --from spec.md --auto`));
96649
97421
  process.exit(EXIT_CODES.INVALID_PRD);
@@ -96661,7 +97433,7 @@ async function precheckCommand(options) {
96661
97433
  init_source();
96662
97434
  init_paths3();
96663
97435
  import { readdir as readdir5 } from "fs/promises";
96664
- import { join as join69 } from "path";
97436
+ import { join as join72 } from "path";
96665
97437
  var DEFAULT_LIMIT = 20;
96666
97438
  var _runsCmdDeps = {
96667
97439
  getRunsDir
@@ -96716,7 +97488,7 @@ async function runsCommand(options = {}) {
96716
97488
  }
96717
97489
  const rows = [];
96718
97490
  for (const entry of entries) {
96719
- const metaPath = join69(runsDir, entry, "meta.json");
97491
+ const metaPath = join72(runsDir, entry, "meta.json");
96720
97492
  let meta3;
96721
97493
  try {
96722
97494
  meta3 = await Bun.file(metaPath).json();
@@ -96793,7 +97565,7 @@ async function runsCommand(options = {}) {
96793
97565
 
96794
97566
  // src/commands/unlock.ts
96795
97567
  init_source();
96796
- import { join as join70 } from "path";
97568
+ import { join as join73 } from "path";
96797
97569
  function isProcessAlive2(pid) {
96798
97570
  try {
96799
97571
  process.kill(pid, 0);
@@ -96808,7 +97580,7 @@ function formatLockAge(ageMs) {
96808
97580
  }
96809
97581
  async function unlockCommand(options) {
96810
97582
  const workdir = options.dir ?? process.cwd();
96811
- const lockPath = join70(workdir, "nax.lock");
97583
+ const lockPath = join73(workdir, "nax.lock");
96812
97584
  const lockFile = Bun.file(lockPath);
96813
97585
  const exists = await lockFile.exists();
96814
97586
  if (!exists) {
@@ -103850,7 +104622,8 @@ function LiveActivityPanel({
103850
104622
  storySteps,
103851
104623
  runSummary,
103852
104624
  runErrored,
103853
- escalationLog = []
104625
+ escalationLog = [],
104626
+ currentStage
103854
104627
  }) {
103855
104628
  const borderColor = focused ? "cyan" : "gray";
103856
104629
  const activeCallList = activeCalls ? Array.from(activeCalls.values()) : [];
@@ -103907,7 +104680,8 @@ function LiveActivityPanel({
103907
104680
  paddingY: 1,
103908
104681
  children: activeCallList.map((call) => /* @__PURE__ */ jsx_dev_runtime3.jsxDEV(ActiveCallRow, {
103909
104682
  call,
103910
- step: call.storyId ? storySteps?.[call.storyId] : undefined
104683
+ step: call.storyId ? storySteps?.[call.storyId] : undefined,
104684
+ currentStage
103911
104685
  }, call.callId, false, undefined, this))
103912
104686
  }, undefined, false, undefined, this),
103913
104687
  hasEscalations && /* @__PURE__ */ jsx_dev_runtime3.jsxDEV(Box_default, {
@@ -103948,7 +104722,24 @@ function LiveActivityPanel({
103948
104722
  !hasActiveCalls && !hasSummary && !hasError && (!storySteps || Object.keys(storySteps).length === 0) && /* @__PURE__ */ jsx_dev_runtime3.jsxDEV(Box_default, {
103949
104723
  paddingX: 1,
103950
104724
  paddingY: 1,
103951
- children: /* @__PURE__ */ jsx_dev_runtime3.jsxDEV(Text, {
104725
+ children: currentStage ? /* @__PURE__ */ jsx_dev_runtime3.jsxDEV(Box_default, {
104726
+ flexDirection: "row",
104727
+ gap: 1,
104728
+ children: [
104729
+ /* @__PURE__ */ jsx_dev_runtime3.jsxDEV(Text, {
104730
+ color: "yellow",
104731
+ children: [
104732
+ "[",
104733
+ currentStage,
104734
+ "]"
104735
+ ]
104736
+ }, undefined, true, undefined, this),
104737
+ /* @__PURE__ */ jsx_dev_runtime3.jsxDEV(Text, {
104738
+ dimColor: true,
104739
+ children: "preparing..."
104740
+ }, undefined, false, undefined, this)
104741
+ ]
104742
+ }, undefined, true, undefined, this) : /* @__PURE__ */ jsx_dev_runtime3.jsxDEV(Text, {
103952
104743
  dimColor: true,
103953
104744
  children: "Waiting for agent..."
103954
104745
  }, undefined, false, undefined, this)
@@ -103956,7 +104747,29 @@ function LiveActivityPanel({
103956
104747
  ]
103957
104748
  }, undefined, true, undefined, this);
103958
104749
  }
103959
- function ActiveCallRow({ call, step }) {
104750
+ function ActiveCallRow({ call, step, currentStage }) {
104751
+ const stageLabel = step ? /* @__PURE__ */ jsx_dev_runtime3.jsxDEV(Text, {
104752
+ color: "yellow",
104753
+ children: [
104754
+ "[",
104755
+ step,
104756
+ "]"
104757
+ ]
104758
+ }, undefined, true, undefined, this) : call.stage ? /* @__PURE__ */ jsx_dev_runtime3.jsxDEV(Text, {
104759
+ dimColor: true,
104760
+ children: [
104761
+ "[",
104762
+ call.stage,
104763
+ "]"
104764
+ ]
104765
+ }, undefined, true, undefined, this) : currentStage ? /* @__PURE__ */ jsx_dev_runtime3.jsxDEV(Text, {
104766
+ dimColor: true,
104767
+ children: [
104768
+ "[",
104769
+ currentStage,
104770
+ "]"
104771
+ ]
104772
+ }, undefined, true, undefined, this) : null;
103960
104773
  return /* @__PURE__ */ jsx_dev_runtime3.jsxDEV(Box_default, {
103961
104774
  flexDirection: "column",
103962
104775
  marginBottom: 1,
@@ -103972,21 +104785,7 @@ function ActiveCallRow({ call, step }) {
103972
104785
  call.storyId && /* @__PURE__ */ jsx_dev_runtime3.jsxDEV(Text, {
103973
104786
  children: call.storyId
103974
104787
  }, undefined, false, undefined, this),
103975
- step ? /* @__PURE__ */ jsx_dev_runtime3.jsxDEV(Text, {
103976
- color: "yellow",
103977
- children: [
103978
- "[",
103979
- step,
103980
- "]"
103981
- ]
103982
- }, undefined, true, undefined, this) : call.stage ? /* @__PURE__ */ jsx_dev_runtime3.jsxDEV(Text, {
103983
- dimColor: true,
103984
- children: [
103985
- "[",
103986
- call.stage,
103987
- "]"
103988
- ]
103989
- }, undefined, true, undefined, this) : null
104788
+ stageLabel
103990
104789
  ]
103991
104790
  }, undefined, true, undefined, this),
103992
104791
  /* @__PURE__ */ jsx_dev_runtime3.jsxDEV(Box_default, {
@@ -104193,7 +104992,14 @@ function getStatusIcon(status) {
104193
104992
  return "\u23F8\uFE0F";
104194
104993
  }
104195
104994
  }
104196
- function StoriesPanel({ stories, postRunPhases, width, compact: compact2 = false, maxHeight }) {
104995
+ function StoriesPanel({
104996
+ stories,
104997
+ preRunPhases,
104998
+ postRunPhases,
104999
+ width,
105000
+ compact: compact2 = false,
105001
+ maxHeight
105002
+ }) {
104197
105003
  const maxVisible = compact2 ? COMPACT_MAX_VISIBLE_STORIES : MAX_VISIBLE_STORIES;
104198
105004
  const needsScrolling = stories.length > maxVisible;
104199
105005
  const [scrollOffset, setScrollOffset] = import_react30.useState(0);
@@ -104225,7 +105031,7 @@ function StoriesPanel({ stories, postRunPhases, width, compact: compact2 = false
104225
105031
  children: [
104226
105032
  /* @__PURE__ */ jsx_dev_runtime5.jsxDEV(Text, {
104227
105033
  bold: true,
104228
- children: "Stories"
105034
+ children: "Progress"
104229
105035
  }, undefined, false, undefined, this),
104230
105036
  needsScrolling && /* @__PURE__ */ jsx_dev_runtime5.jsxDEV(Text, {
104231
105037
  dimColor: true,
@@ -104237,6 +105043,16 @@ function StoriesPanel({ stories, postRunPhases, width, compact: compact2 = false
104237
105043
  }, undefined, true, undefined, this)
104238
105044
  ]
104239
105045
  }, undefined, true, undefined, this),
105046
+ preRunPhases && Object.keys(preRunPhases).length > 0 && /* @__PURE__ */ jsx_dev_runtime5.jsxDEV(Box_default, {
105047
+ flexDirection: "column",
105048
+ paddingX: 1,
105049
+ paddingTop: 1,
105050
+ children: Object.entries(preRunPhases).map(([name, phase]) => /* @__PURE__ */ jsx_dev_runtime5.jsxDEV(PreRunPhaseRow, {
105051
+ label: name,
105052
+ phase,
105053
+ compact: compact2
105054
+ }, name, false, undefined, this))
105055
+ }, undefined, false, undefined, this),
104240
105056
  needsScrolling && canScrollUp && /* @__PURE__ */ jsx_dev_runtime5.jsxDEV(Box_default, {
104241
105057
  paddingX: 1,
104242
105058
  children: /* @__PURE__ */ jsx_dev_runtime5.jsxDEV(Text, {
@@ -104315,11 +105131,6 @@ function StoriesPanel({ stories, postRunPhases, width, compact: compact2 = false
104315
105131
  paddingX: 1,
104316
105132
  paddingTop: 1,
104317
105133
  children: [
104318
- /* @__PURE__ */ jsx_dev_runtime5.jsxDEV(Box_default, {
104319
- borderStyle: "single",
104320
- borderTop: true,
104321
- borderColor: "gray"
104322
- }, undefined, false, undefined, this),
104323
105134
  /* @__PURE__ */ jsx_dev_runtime5.jsxDEV(Text, {
104324
105135
  dimColor: true,
104325
105136
  children: "Post-Run"
@@ -104344,6 +105155,21 @@ function StoriesPanel({ stories, postRunPhases, width, compact: compact2 = false
104344
105155
  ]
104345
105156
  }, undefined, true, undefined, this);
104346
105157
  }
105158
+ function PreRunPhaseRow({ label, phase, compact: compact2 }) {
105159
+ const icon = phase.status === "running" ? "\u25CF" : phase.status === "passed" ? "\u2713" : "\u2717";
105160
+ const color = phase.status === "running" ? "yellow" : phase.status === "passed" ? "green" : "red";
105161
+ const displayLabel = compact2 ? label.slice(0, 6) : label;
105162
+ return /* @__PURE__ */ jsx_dev_runtime5.jsxDEV(Box_default, {
105163
+ children: /* @__PURE__ */ jsx_dev_runtime5.jsxDEV(Text, {
105164
+ color,
105165
+ children: [
105166
+ icon,
105167
+ " ",
105168
+ displayLabel
105169
+ ]
105170
+ }, undefined, true, undefined, this)
105171
+ }, undefined, false, undefined, this);
105172
+ }
104347
105173
  function PostRunPhaseRow({ label, phase, compact: compact2 }) {
104348
105174
  const icon = phase.status === "running" ? ">" : phase.status === "passed" ? "[OK]" : "[X]";
104349
105175
  const color = phase.status === "running" ? "cyan" : phase.status === "passed" ? "green" : "red";
@@ -104361,110 +105187,120 @@ function PostRunPhaseRow({ label, phase, compact: compact2 }) {
104361
105187
 
104362
105188
  // src/tui/hooks/useAgentStreamEvents.ts
104363
105189
  var import_react31 = __toESM(require_react(), 1);
105190
+ var RENDER_INTERVAL_MS = 150;
104364
105191
  function useAgentStreamEvents(bus) {
105192
+ const activeCallsRef = import_react31.useRef(new Map);
105193
+ const inputTokensRef = import_react31.useRef(0);
105194
+ const outputTokensRef = import_react31.useRef(0);
105195
+ const lastTokensRef = import_react31.useRef(new Map);
105196
+ const dirtyRef = import_react31.useRef(false);
104365
105197
  const [activeCalls, setActiveCalls] = import_react31.useState(new Map);
104366
105198
  const [inputTokens, setInputTokens] = import_react31.useState(0);
104367
105199
  const [outputTokens, setOutputTokens] = import_react31.useState(0);
104368
- const lastTokensRef = import_react31.useRef(new Map);
104369
105200
  import_react31.useEffect(() => {
104370
105201
  if (!bus)
104371
105202
  return;
104372
105203
  const unsubscribe = bus.onAgentStream((event) => {
104373
- setActiveCalls((prev) => {
104374
- const next = new Map(prev);
104375
- switch (event.kind) {
104376
- case "agent.call_started": {
104377
- const now3 = event.timestamp;
105204
+ const next = new Map(activeCallsRef.current);
105205
+ switch (event.kind) {
105206
+ case "agent.call_started": {
105207
+ next.set(event.callId, {
105208
+ callId: event.callId,
105209
+ agentName: event.agentName,
105210
+ storyId: event.storyId,
105211
+ stage: event.stage,
105212
+ startedAt: event.timestamp,
105213
+ lastActivityAt: event.timestamp,
105214
+ messageUpdates: 0,
105215
+ thinkingUpdates: 0,
105216
+ usageUpdates: 0,
105217
+ toolCallUpdates: 0,
105218
+ status: "active",
105219
+ model: event.model
105220
+ });
105221
+ break;
105222
+ }
105223
+ case "agent.message_update": {
105224
+ const state = next.get(event.callId);
105225
+ if (state) {
104378
105226
  next.set(event.callId, {
104379
- callId: event.callId,
104380
- agentName: event.agentName,
104381
- storyId: event.storyId,
104382
- stage: event.stage,
104383
- startedAt: now3,
104384
- lastActivityAt: now3,
104385
- messageUpdates: 0,
104386
- thinkingUpdates: 0,
104387
- usageUpdates: 0,
104388
- toolCallUpdates: 0,
104389
- status: "active",
104390
- model: event.model
105227
+ ...state,
105228
+ messageUpdates: state.messageUpdates + 1,
105229
+ lastActivityAt: event.timestamp
104391
105230
  });
104392
- break;
104393
- }
104394
- case "agent.message_update": {
104395
- const state = next.get(event.callId);
104396
- if (state) {
104397
- next.set(event.callId, {
104398
- ...state,
104399
- messageUpdates: state.messageUpdates + 1,
104400
- lastActivityAt: event.timestamp
104401
- });
104402
- }
104403
- break;
104404
105231
  }
104405
- case "agent.thinking_update": {
104406
- const state = next.get(event.callId);
104407
- if (state) {
104408
- next.set(event.callId, {
104409
- ...state,
104410
- thinkingUpdates: state.thinkingUpdates + 1,
104411
- lastActivityAt: event.timestamp
104412
- });
104413
- }
104414
- break;
105232
+ break;
105233
+ }
105234
+ case "agent.thinking_update": {
105235
+ const state = next.get(event.callId);
105236
+ if (state) {
105237
+ next.set(event.callId, {
105238
+ ...state,
105239
+ thinkingUpdates: state.thinkingUpdates + 1,
105240
+ lastActivityAt: event.timestamp
105241
+ });
104415
105242
  }
104416
- case "agent.usage_update": {
104417
- const state = next.get(event.callId);
104418
- if (state) {
104419
- next.set(event.callId, {
104420
- ...state,
104421
- usageUpdates: state.usageUpdates + 1,
104422
- lastActivityAt: event.timestamp
104423
- });
104424
- }
104425
- {
104426
- const last2 = lastTokensRef.current.get(event.callId) ?? { input: 0, output: 0 };
104427
- const newInput = event.inputTokens ?? last2.input;
104428
- const newOutput = event.outputTokens ?? last2.output;
104429
- const deltaIn = newInput - last2.input;
104430
- const deltaOut = newOutput - last2.output;
104431
- lastTokensRef.current.set(event.callId, { input: newInput, output: newOutput });
104432
- if (deltaIn > 0)
104433
- setInputTokens((prev2) => prev2 + deltaIn);
104434
- if (deltaOut > 0)
104435
- setOutputTokens((prev2) => prev2 + deltaOut);
104436
- }
104437
- break;
105243
+ break;
105244
+ }
105245
+ case "agent.usage_update": {
105246
+ const state = next.get(event.callId);
105247
+ if (state) {
105248
+ next.set(event.callId, {
105249
+ ...state,
105250
+ usageUpdates: state.usageUpdates + 1,
105251
+ lastActivityAt: event.timestamp
105252
+ });
104438
105253
  }
104439
- case "agent.tool_call_update": {
104440
- const state = next.get(event.callId);
104441
- if (state) {
104442
- next.set(event.callId, {
104443
- ...state,
104444
- toolCallUpdates: state.toolCallUpdates + 1,
104445
- lastActivityAt: event.timestamp,
104446
- lastToolName: event.toolName
104447
- });
104448
- }
104449
- break;
105254
+ {
105255
+ const last2 = lastTokensRef.current.get(event.callId) ?? { input: 0, output: 0 };
105256
+ const newInput = event.inputTokens ?? last2.input;
105257
+ const newOutput = event.outputTokens ?? last2.output;
105258
+ const deltaIn = newInput - last2.input;
105259
+ const deltaOut = newOutput - last2.output;
105260
+ lastTokensRef.current.set(event.callId, { input: newInput, output: newOutput });
105261
+ if (deltaIn > 0)
105262
+ inputTokensRef.current += deltaIn;
105263
+ if (deltaOut > 0)
105264
+ outputTokensRef.current += deltaOut;
104450
105265
  }
104451
- case "agent.call_ended": {
104452
- const state = next.get(event.callId);
104453
- if (state) {
104454
- next.set(event.callId, { ...state, status: "ended" });
104455
- next.delete(event.callId);
104456
- }
104457
- lastTokensRef.current.delete(event.callId);
104458
- break;
105266
+ break;
105267
+ }
105268
+ case "agent.tool_call_update": {
105269
+ const state = next.get(event.callId);
105270
+ if (state) {
105271
+ next.set(event.callId, {
105272
+ ...state,
105273
+ toolCallUpdates: state.toolCallUpdates + 1,
105274
+ lastActivityAt: event.timestamp,
105275
+ lastToolName: event.toolName
105276
+ });
104459
105277
  }
104460
- default:
104461
- break;
105278
+ break;
104462
105279
  }
104463
- return next;
104464
- });
105280
+ case "agent.call_ended": {
105281
+ next.delete(event.callId);
105282
+ lastTokensRef.current.delete(event.callId);
105283
+ break;
105284
+ }
105285
+ default:
105286
+ break;
105287
+ }
105288
+ activeCallsRef.current = next;
105289
+ dirtyRef.current = true;
104465
105290
  });
104466
105291
  return unsubscribe;
104467
105292
  }, [bus]);
105293
+ import_react31.useEffect(() => {
105294
+ const interval = setInterval(() => {
105295
+ if (!dirtyRef.current)
105296
+ return;
105297
+ dirtyRef.current = false;
105298
+ setActiveCalls(new Map(activeCallsRef.current));
105299
+ setInputTokens(inputTokensRef.current);
105300
+ setOutputTokens(outputTokensRef.current);
105301
+ }, RENDER_INTERVAL_MS);
105302
+ return () => clearInterval(interval);
105303
+ }, []);
104468
105304
  return { activeCalls, inputTokens, outputTokens };
104469
105305
  }
104470
105306
 
@@ -104660,14 +105496,33 @@ function usePipelineBusEvents(initialStories) {
104660
105496
 
104661
105497
  // src/tui/hooks/usePipelineEvents.ts
104662
105498
  var import_react33 = __toESM(require_react(), 1);
105499
+ var PRE_RUN_STAGES = new Set(["acceptance-setup"]);
104663
105500
  function usePipelineEvents(events) {
104664
105501
  const [currentStage, setCurrentStage] = import_react33.useState(undefined);
105502
+ const [preRunPhases, setPreRunPhases] = import_react33.useState({});
104665
105503
  import_react33.useEffect(() => {
104666
- const onStageEnter = (stage) => setCurrentStage(stage);
105504
+ const onStageEnter = (stage) => {
105505
+ setCurrentStage(stage);
105506
+ if (PRE_RUN_STAGES.has(stage)) {
105507
+ setPreRunPhases((prev) => ({ ...prev, [stage]: { status: "running" } }));
105508
+ }
105509
+ };
105510
+ const onStageExit = (stage, result2) => {
105511
+ if (PRE_RUN_STAGES.has(stage)) {
105512
+ setPreRunPhases((prev) => ({
105513
+ ...prev,
105514
+ [stage]: { status: result2.action === "fail" ? "failed" : "passed" }
105515
+ }));
105516
+ }
105517
+ };
104667
105518
  events.on("stage:enter", onStageEnter);
104668
- return () => events.off("stage:enter", onStageEnter);
105519
+ events.on("stage:exit", onStageExit);
105520
+ return () => {
105521
+ events.off("stage:enter", onStageEnter);
105522
+ events.off("stage:exit", onStageExit);
105523
+ };
104669
105524
  }, [events]);
104670
- return { currentStage };
105525
+ return { currentStage, preRunPhases };
104671
105526
  }
104672
105527
 
104673
105528
  // src/tui/hooks/usePty.ts
@@ -104799,7 +105654,7 @@ function App2({
104799
105654
  }) {
104800
105655
  const layout = useLayout();
104801
105656
  const busState = usePipelineBusEvents(initialStories);
104802
- const { currentStage } = usePipelineEvents(events);
105657
+ const { currentStage, preRunPhases } = usePipelineEvents(events);
104803
105658
  const { exit } = use_app_default();
104804
105659
  const startTimeRef = import_react35.useRef(Date.now());
104805
105660
  const [elapsedMs, setElapsedMs] = import_react35.useState(0);
@@ -104820,6 +105675,8 @@ function App2({
104820
105675
  const runningStories = busState.stories.filter((s) => s.status === "running");
104821
105676
  const isParallel = runningStories.length > 1;
104822
105677
  const currentRunningStory = runningStories[0];
105678
+ const runningPostRunPhase = busState.postRunPhases.acceptance?.status === "running" ? "post-run: acceptance" : busState.postRunPhases.regression?.status === "running" ? "post-run: regression" : busState.postRunPhases.review?.status === "running" ? "post-run: review" : undefined;
105679
+ const currentPhaseLabel = runningPostRunPhase ?? currentStage;
104823
105680
  const runErroredForPanel = busState.runErrored ? "Run encountered an error" : undefined;
104824
105681
  const handleKeyboardAction = async (action) => {
104825
105682
  switch (action.type) {
@@ -104903,7 +105760,11 @@ function App2({
104903
105760
  const isTooSmall = layout.width < MIN_TERMINAL_WIDTH;
104904
105761
  const activeCount = runningStories.length;
104905
105762
  const displayElapsed = busState.runSummary ? busState.runSummary.durationMs : elapsedMs;
104906
- const tokensStr = inputTokens > 0 || outputTokens > 0 ? `${formatTokens(inputTokens)} in / ${formatTokens(outputTokens)} out` : null;
105763
+ const tokenParts = [
105764
+ inputTokens > 0 ? `${formatTokens(inputTokens)} in` : null,
105765
+ outputTokens > 0 ? `${formatTokens(outputTokens)} out` : null
105766
+ ].filter(Boolean);
105767
+ const tokensStr = tokenParts.length > 0 ? tokenParts.join(" / ") : null;
104907
105768
  const headerRight = [
104908
105769
  activeCount > 0 ? `${activeCount} running` : null,
104909
105770
  formatCost3(busState.totalCost),
@@ -104964,6 +105825,7 @@ function App2({
104964
105825
  children: [
104965
105826
  /* @__PURE__ */ jsx_dev_runtime6.jsxDEV(MemoStoriesPanel, {
104966
105827
  stories: busState.stories,
105828
+ preRunPhases,
104967
105829
  postRunPhases: busState.postRunPhases,
104968
105830
  width: layout.mode === "single" ? layout.width : layout.storiesPanelWidth,
104969
105831
  compact: layout.mode === "single",
@@ -104975,7 +105837,8 @@ function App2({
104975
105837
  storySteps: busState.storySteps,
104976
105838
  runSummary: busState.runSummary,
104977
105839
  runErrored: runErroredForPanel,
104978
- escalationLog: busState.escalationLog
105840
+ escalationLog: busState.escalationLog,
105841
+ currentStage: currentPhaseLabel
104979
105842
  }, undefined, false, undefined, this)
104980
105843
  ]
104981
105844
  }, undefined, true, undefined, this),
@@ -105141,8 +106004,8 @@ Next: nax generate --package ${options.package}`));
105141
106004
  }
105142
106005
  return;
105143
106006
  }
105144
- const naxDir = join84(workdir, ".nax");
105145
- if (existsSync36(naxDir) && !options.force) {
106007
+ const naxDir = join87(workdir, ".nax");
106008
+ if (existsSync35(naxDir) && !options.force) {
105146
106009
  console.log(source_default.yellow("nax already initialized. Use --force to overwrite."));
105147
106010
  return;
105148
106011
  }
@@ -105170,11 +106033,11 @@ Next: nax generate --package ${options.package}`));
105170
106033
  }
105171
106034
  }
105172
106035
  }
105173
- mkdirSync7(join84(naxDir, "features"), { recursive: true });
105174
- mkdirSync7(join84(naxDir, "hooks"), { recursive: true });
106036
+ mkdirSync7(join87(naxDir, "features"), { recursive: true });
106037
+ mkdirSync7(join87(naxDir, "hooks"), { recursive: true });
105175
106038
  const initConfig = options.name ? { ...DEFAULT_CONFIG, name: options.name } : DEFAULT_CONFIG;
105176
- await Bun.write(join84(naxDir, "config.json"), JSON.stringify(initConfig, null, 2));
105177
- 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({
105178
106041
  hooks: {
105179
106042
  "on-start": { command: 'echo "nax started: $NAX_FEATURE"', enabled: false },
105180
106043
  "on-complete": { command: 'echo "nax complete: $NAX_FEATURE"', enabled: false },
@@ -105182,12 +106045,12 @@ Next: nax generate --package ${options.package}`));
105182
106045
  "on-error": { command: 'echo "nax error: $NAX_REASON"', enabled: false }
105183
106046
  }
105184
106047
  }, null, 2));
105185
- await Bun.write(join84(naxDir, ".gitignore"), `# nax temp files
106048
+ await Bun.write(join87(naxDir, ".gitignore"), `# nax temp files
105186
106049
  *.tmp
105187
106050
  .paused.json
105188
106051
  .nax-verifier-verdict.json
105189
106052
  `);
105190
- await Bun.write(join84(naxDir, "context.md"), `# Project Context
106053
+ await Bun.write(join87(naxDir, "context.md"), `# Project Context
105191
106054
 
105192
106055
  This document defines coding standards, architectural decisions, and forbidden patterns for this project.
105193
106056
  Run \`nax generate\` to regenerate agent config files (CLAUDE.md, AGENTS.md, .cursorrules, etc.) from this file.
@@ -105272,6 +106135,24 @@ Run \`nax generate\` to regenerate agent config files (CLAUDE.md, AGENTS.md, .cu
105272
106135
  console.log(source_default.dim(`
105273
106136
  Next: nax features create <name>`));
105274
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
+ });
105275
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) => {
105276
106157
  let workdir;
105277
106158
  try {
@@ -105284,7 +106165,7 @@ program2.command("run").description("Run the orchestration loop for a feature").
105284
106165
  console.error(source_default.red("Error: --plan requires --from <spec-path>"));
105285
106166
  process.exit(1);
105286
106167
  }
105287
- if (options.from && !existsSync36(options.from)) {
106168
+ if (options.from && !existsSync35(options.from)) {
105288
106169
  console.error(source_default.red(`Error: File not found: ${options.from} (required with --plan)`));
105289
106170
  process.exit(1);
105290
106171
  }
@@ -105317,10 +106198,10 @@ program2.command("run").description("Run the orchestration loop for a feature").
105317
106198
  console.error(source_default.red("nax not initialized. Run: nax init"));
105318
106199
  process.exit(1);
105319
106200
  }
105320
- const featureDir = join84(naxDir, "features", options.feature);
105321
- const prdPath = join84(featureDir, "prd.json");
106201
+ const featureDir = join87(naxDir, "features", options.feature);
106202
+ const prdPath = join87(featureDir, "prd.json");
105322
106203
  if (options.plan && options.from) {
105323
- if (existsSync36(prdPath) && !options.force) {
106204
+ if (existsSync35(prdPath) && !options.force) {
105324
106205
  console.error(source_default.red(`Error: prd.json already exists for feature "${options.feature}".`));
105325
106206
  console.error(source_default.dim(" Use --force to overwrite, or run without --plan to use the existing PRD."));
105326
106207
  process.exit(1);
@@ -105340,10 +106221,10 @@ program2.command("run").description("Run the orchestration loop for a feature").
105340
106221
  }
105341
106222
  }
105342
106223
  try {
105343
- const planLogDir = join84(featureDir, "plan");
106224
+ const planLogDir = join87(featureDir, "plan");
105344
106225
  mkdirSync7(planLogDir, { recursive: true });
105345
106226
  const planLogId = new Date().toISOString().replace(/:/g, "-").replace(/\..+/, "");
105346
- const planLogPath = join84(planLogDir, `${planLogId}.jsonl`);
106227
+ const planLogPath = join87(planLogDir, `${planLogId}.jsonl`);
105347
106228
  initLogger({ level: "info", filePath: planLogPath, useChalk: false, headless: true });
105348
106229
  console.log(source_default.dim(` [Plan log: ${planLogPath}]`));
105349
106230
  console.log(source_default.dim(" [Planning phase: generating PRD from spec]"));
@@ -105382,17 +106263,17 @@ program2.command("run").description("Run the orchestration loop for a feature").
105382
106263
  process.exit(1);
105383
106264
  }
105384
106265
  }
105385
- if (!existsSync36(prdPath)) {
106266
+ if (!existsSync35(prdPath)) {
105386
106267
  console.error(source_default.red(`Feature "${options.feature}" not found or missing prd.json`));
105387
106268
  process.exit(1);
105388
106269
  }
105389
106270
  resetLogger();
105390
106271
  const projectKey = config2.name?.trim() || basename16(workdir);
105391
106272
  const outputDir = projectOutputDir(projectKey, config2.outputDir);
105392
- const runsDir = join84(outputDir, "features", options.feature, "runs");
106273
+ const runsDir = join87(outputDir, "features", options.feature, "runs");
105393
106274
  mkdirSync7(runsDir, { recursive: true });
105394
106275
  const runId = new Date().toISOString().replace(/:/g, "-").replace(/\..+/, "");
105395
- const logFilePath = join84(runsDir, `${runId}.jsonl`);
106276
+ const logFilePath = join87(runsDir, `${runId}.jsonl`);
105396
106277
  const isTTY = process.stdout.isTTY ?? false;
105397
106278
  const headlessFlag = options.headless ?? false;
105398
106279
  const headlessEnv = process.env.NAX_HEADLESS === "1";
@@ -105410,7 +106291,7 @@ program2.command("run").description("Run the orchestration loop for a feature").
105410
106291
  config2.agent.default = options.agent;
105411
106292
  }
105412
106293
  config2.execution.maxIterations = Number.parseInt(options.maxIterations, 10);
105413
- const globalNaxDir = join84(homedir3(), ".nax");
106294
+ const globalNaxDir = join87(homedir3(), ".nax");
105414
106295
  const hooks = await loadHooksConfig(naxDir, globalNaxDir);
105415
106296
  const eventEmitter = new PipelineEventEmitter;
105416
106297
  const agentStreamEvents = useHeadless ? undefined : new AgentStreamEventBus;
@@ -105430,12 +106311,12 @@ program2.command("run").description("Run the orchestration loop for a feature").
105430
106311
  events: eventEmitter,
105431
106312
  ptyOptions: null,
105432
106313
  agentStreamEvents,
105433
- queueFilePath: join84(workdir, ".queue.txt")
106314
+ queueFilePath: join87(workdir, ".queue.txt")
105434
106315
  });
105435
106316
  } else {
105436
106317
  console.log(source_default.dim(" [Headless mode \u2014 pipe output]"));
105437
106318
  }
105438
- const statusFilePath = join84(outputDir, "status.json");
106319
+ const statusFilePath = join87(outputDir, "status.json");
105439
106320
  let parallel;
105440
106321
  if (options.parallel !== undefined) {
105441
106322
  parallel = Number.parseInt(options.parallel, 10);
@@ -105462,9 +106343,9 @@ program2.command("run").description("Run the orchestration loop for a feature").
105462
106343
  skipPrecheck: options.skipPrecheck ?? false,
105463
106344
  agentStreamEvents
105464
106345
  });
105465
- const latestSymlink = join84(runsDir, "latest.jsonl");
106346
+ const latestSymlink = join87(runsDir, "latest.jsonl");
105466
106347
  try {
105467
- if (existsSync36(latestSymlink)) {
106348
+ if (existsSync35(latestSymlink)) {
105468
106349
  Bun.spawnSync(["rm", latestSymlink]);
105469
106350
  }
105470
106351
  Bun.spawnSync(["ln", "-s", `${runId}.jsonl`, latestSymlink], {
@@ -105523,9 +106404,9 @@ features.command("create <name>").description("Create a new feature").option("-d
105523
106404
  console.error(source_default.red("nax not initialized. Run: nax init"));
105524
106405
  process.exit(1);
105525
106406
  }
105526
- const featureDir = join84(naxDir, "features", name);
106407
+ const featureDir = join87(naxDir, "features", name);
105527
106408
  mkdirSync7(featureDir, { recursive: true });
105528
- await Bun.write(join84(featureDir, "spec.md"), `# Feature: ${name}
106409
+ await Bun.write(join87(featureDir, "spec.md"), `# Feature: ${name}
105529
106410
 
105530
106411
  ## Overview
105531
106412
 
@@ -105558,7 +106439,7 @@ features.command("create <name>").description("Create a new feature").option("-d
105558
106439
 
105559
106440
  <!-- What this feature explicitly does NOT cover. -->
105560
106441
  `);
105561
- await Bun.write(join84(featureDir, "progress.txt"), `# Progress: ${name}
106442
+ await Bun.write(join87(featureDir, "progress.txt"), `# Progress: ${name}
105562
106443
 
105563
106444
  Created: ${new Date().toISOString()}
105564
106445
 
@@ -105584,8 +106465,8 @@ features.command("list").description("List all features").option("-d, --dir <pat
105584
106465
  console.error(source_default.red("nax not initialized."));
105585
106466
  process.exit(1);
105586
106467
  }
105587
- const featuresDir = join84(naxDir, "features");
105588
- if (!existsSync36(featuresDir)) {
106468
+ const featuresDir = join87(naxDir, "features");
106469
+ if (!existsSync35(featuresDir)) {
105589
106470
  console.log(source_default.dim("No features yet."));
105590
106471
  return;
105591
106472
  }
@@ -105599,8 +106480,8 @@ features.command("list").description("List all features").option("-d, --dir <pat
105599
106480
  Features:
105600
106481
  `));
105601
106482
  for (const name of entries) {
105602
- const prdPath = join84(featuresDir, name, "prd.json");
105603
- if (existsSync36(prdPath)) {
106483
+ const prdPath = join87(featuresDir, name, "prd.json");
106484
+ if (existsSync35(prdPath)) {
105604
106485
  const prd = await loadPRD(prdPath);
105605
106486
  const c = countStories(prd);
105606
106487
  console.log(` ${name} \u2014 ${c.passed}/${c.total} stories done`);
@@ -105634,10 +106515,10 @@ Use: nax plan -f <feature> --from <spec>`));
105634
106515
  cliOverrides.profile = options.profile;
105635
106516
  }
105636
106517
  const config2 = await loadConfig(workdir, cliOverrides);
105637
- const featureLogDir = join84(naxDir, "features", options.feature, "plan");
106518
+ const featureLogDir = join87(naxDir, "features", options.feature, "plan");
105638
106519
  mkdirSync7(featureLogDir, { recursive: true });
105639
106520
  const planLogId = new Date().toISOString().replace(/:/g, "-").replace(/\..+/, "");
105640
- const planLogPath = join84(featureLogDir, `${planLogId}.jsonl`);
106521
+ const planLogPath = join87(featureLogDir, `${planLogId}.jsonl`);
105641
106522
  initLogger({ level: "info", filePath: planLogPath, useChalk: false, headless: true });
105642
106523
  console.log(source_default.dim(` [Plan log: ${planLogPath}]`));
105643
106524
  try {