@nathapp/nax 0.57.1-canary.1 → 0.57.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 +1475 -878
  2. package/package.json +1 -1
package/dist/nax.js CHANGED
@@ -17829,261 +17829,6 @@ var init_zod = __esm(() => {
17829
17829
  init_external();
17830
17830
  });
17831
17831
 
17832
- // src/config/defaults.ts
17833
- var DEFAULT_CONFIG;
17834
- var init_defaults = __esm(() => {
17835
- DEFAULT_CONFIG = {
17836
- version: 1,
17837
- models: {
17838
- claude: {
17839
- fast: "haiku",
17840
- balanced: "sonnet",
17841
- powerful: "opus"
17842
- }
17843
- },
17844
- autoMode: {
17845
- enabled: true,
17846
- defaultAgent: "claude",
17847
- fallbackOrder: ["claude"],
17848
- complexityRouting: {
17849
- simple: "fast",
17850
- medium: "balanced",
17851
- complex: "powerful",
17852
- expert: "powerful"
17853
- },
17854
- escalation: {
17855
- enabled: true,
17856
- tierOrder: [
17857
- { tier: "fast", attempts: 5 },
17858
- { tier: "balanced", attempts: 3 },
17859
- { tier: "powerful", attempts: 2 }
17860
- ],
17861
- escalateEntireBatch: true
17862
- }
17863
- },
17864
- routing: {
17865
- strategy: "keyword",
17866
- llm: {
17867
- model: "fast",
17868
- fallbackToKeywords: true,
17869
- cacheDecisions: true,
17870
- mode: "hybrid",
17871
- timeoutMs: 30000
17872
- }
17873
- },
17874
- execution: {
17875
- maxIterations: 10,
17876
- iterationDelayMs: 2000,
17877
- costLimit: 30,
17878
- sessionTimeoutSeconds: 3600,
17879
- verificationTimeoutSeconds: 600,
17880
- maxStoriesPerFeature: 500,
17881
- rectification: {
17882
- enabled: true,
17883
- maxRetries: 2,
17884
- fullSuiteTimeoutSeconds: 300,
17885
- maxFailureSummaryChars: 2000,
17886
- abortOnIncreasingFailures: true,
17887
- escalateOnExhaustion: true,
17888
- rethinkAtAttempt: 2,
17889
- urgencyAtAttempt: 3
17890
- },
17891
- regressionGate: {
17892
- enabled: true,
17893
- timeoutSeconds: 300,
17894
- acceptOnTimeout: true,
17895
- maxRectificationAttempts: 2
17896
- },
17897
- contextProviderTokenBudget: 2000,
17898
- smartTestRunner: true
17899
- },
17900
- quality: {
17901
- requireTypecheck: true,
17902
- requireLint: true,
17903
- requireTests: true,
17904
- commands: {},
17905
- forceExit: false,
17906
- detectOpenHandles: true,
17907
- detectOpenHandlesRetries: 1,
17908
- gracePeriodMs: 5000,
17909
- drainTimeoutMs: 2000,
17910
- shell: "/bin/sh",
17911
- stripEnvVars: [
17912
- "CLAUDECODE",
17913
- "REPL_ID",
17914
- "AGENT",
17915
- "GITLAB_ACCESS_TOKEN",
17916
- "GITHUB_TOKEN",
17917
- "GITHUB_ACCESS_TOKEN",
17918
- "GH_TOKEN",
17919
- "CI_GIT_TOKEN",
17920
- "CI_JOB_TOKEN",
17921
- "BITBUCKET_ACCESS_TOKEN",
17922
- "NPM_TOKEN",
17923
- "NPM_AUTH_TOKEN",
17924
- "YARN_NPM_AUTH_TOKEN",
17925
- "ANTHROPIC_API_KEY",
17926
- "OPENAI_API_KEY",
17927
- "GEMINI_API_KEY",
17928
- "COHERE_API_KEY",
17929
- "AWS_ACCESS_KEY_ID",
17930
- "AWS_SECRET_ACCESS_KEY",
17931
- "AWS_SESSION_TOKEN",
17932
- "GOOGLE_APPLICATION_CREDENTIALS",
17933
- "GCLOUD_SERVICE_KEY",
17934
- "AZURE_CLIENT_SECRET",
17935
- "AZURE_TENANT_ID",
17936
- "TELEGRAM_BOT_TOKEN",
17937
- "SLACK_TOKEN",
17938
- "SLACK_WEBHOOK_URL",
17939
- "SENTRY_AUTH_TOKEN",
17940
- "DATADOG_API_KEY"
17941
- ],
17942
- testing: {
17943
- hermetic: true
17944
- }
17945
- },
17946
- tdd: {
17947
- maxRetries: 2,
17948
- autoVerifyIsolation: true,
17949
- autoApproveVerifier: true,
17950
- strategy: "auto",
17951
- sessionTiers: {
17952
- testWriter: "balanced",
17953
- verifier: "fast"
17954
- },
17955
- testWriterAllowedPaths: ["src/index.ts", "src/**/index.ts"],
17956
- rollbackOnFailure: true,
17957
- greenfieldDetection: true
17958
- },
17959
- constitution: {
17960
- enabled: true,
17961
- path: "constitution.md",
17962
- maxTokens: 2000
17963
- },
17964
- analyze: {
17965
- llmEnhanced: true,
17966
- model: "balanced",
17967
- fallbackToKeywords: true,
17968
- maxCodebaseSummaryTokens: 5000
17969
- },
17970
- review: {
17971
- enabled: true,
17972
- checks: ["typecheck", "lint"],
17973
- commands: {},
17974
- pluginMode: "per-story",
17975
- semantic: {
17976
- modelTier: "balanced",
17977
- rules: [],
17978
- timeoutMs: 600000,
17979
- excludePatterns: [":!test/", ":!tests/", ":!*_test.go", ":!*.test.ts", ":!*.spec.ts", ":!**/__tests__/"]
17980
- }
17981
- },
17982
- plan: {
17983
- model: "balanced",
17984
- outputPath: "spec.md"
17985
- },
17986
- acceptance: {
17987
- enabled: true,
17988
- maxRetries: 2,
17989
- generateTests: true,
17990
- testPath: ".nax-acceptance.test.ts",
17991
- model: "fast",
17992
- refinement: true,
17993
- redGate: true,
17994
- timeoutMs: 1800000,
17995
- fix: {
17996
- diagnoseModel: "fast",
17997
- fixModel: "balanced",
17998
- strategy: "diagnose-first",
17999
- maxRetries: 2
18000
- }
18001
- },
18002
- context: {
18003
- fileInjection: "disabled",
18004
- testCoverage: {
18005
- enabled: true,
18006
- detail: "names-and-counts",
18007
- maxTokens: 500,
18008
- testPattern: "**/*.test.{ts,js,tsx,jsx}",
18009
- scopeToStory: true
18010
- },
18011
- autoDetect: {
18012
- enabled: true,
18013
- maxFiles: 5,
18014
- traceImports: false
18015
- }
18016
- },
18017
- interaction: {
18018
- plugin: "cli",
18019
- config: {},
18020
- defaults: {
18021
- timeout: 600000,
18022
- fallback: "escalate"
18023
- },
18024
- triggers: {
18025
- "security-review": true,
18026
- "cost-warning": true
18027
- }
18028
- },
18029
- precheck: {
18030
- storySizeGate: {
18031
- enabled: true,
18032
- maxAcCount: 10,
18033
- maxDescriptionLength: 3000,
18034
- maxBulletPoints: 12,
18035
- action: "block",
18036
- maxReplanAttempts: 3
18037
- }
18038
- },
18039
- prompts: {},
18040
- agent: {
18041
- protocol: "acp"
18042
- },
18043
- debate: {
18044
- enabled: false,
18045
- agents: 3,
18046
- stages: {
18047
- plan: {
18048
- enabled: true,
18049
- resolver: { type: "synthesis" },
18050
- sessionMode: "stateful",
18051
- rounds: 3,
18052
- timeoutSeconds: 600
18053
- },
18054
- review: {
18055
- enabled: true,
18056
- resolver: { type: "majority-fail-closed" },
18057
- sessionMode: "one-shot",
18058
- rounds: 2,
18059
- timeoutSeconds: 600
18060
- },
18061
- acceptance: {
18062
- enabled: false,
18063
- resolver: { type: "majority-fail-closed" },
18064
- sessionMode: "one-shot",
18065
- rounds: 1,
18066
- timeoutSeconds: 600
18067
- },
18068
- rectification: {
18069
- enabled: false,
18070
- resolver: { type: "synthesis" },
18071
- sessionMode: "one-shot",
18072
- rounds: 1,
18073
- timeoutSeconds: 600
18074
- },
18075
- escalation: {
18076
- enabled: false,
18077
- resolver: { type: "majority-fail-closed" },
18078
- sessionMode: "one-shot",
18079
- rounds: 1,
18080
- timeoutSeconds: 600
18081
- }
18082
- }
18083
- }
18084
- };
18085
- });
18086
-
18087
17832
  // src/config/schemas.ts
18088
17833
  function isLegacyFlatModels(val) {
18089
17834
  if (typeof val !== "object" || val === null)
@@ -18112,7 +17857,6 @@ var TokenPricingSchema, ModelDefSchema, ModelEntrySchema, PerAgentModelMapSchema
18112
17857
  })), DebateConfigSchema, NaxConfigSchema;
18113
17858
  var init_schemas3 = __esm(() => {
18114
17859
  init_zod();
18115
- init_defaults();
18116
17860
  TokenPricingSchema = exports_external.object({
18117
17861
  inputPer1M: exports_external.number().min(0),
18118
17862
  outputPer1M: exports_external.number().min(0)
@@ -18127,8 +17871,7 @@ var init_schemas3 = __esm(() => {
18127
17871
  PerAgentModelMapSchema = exports_external.record(exports_external.string().min(1), exports_external.record(exports_external.string().min(1), ModelEntrySchema));
18128
17872
  ModelMapSchema = exports_external.preprocess((val) => {
18129
17873
  if (isLegacyFlatModels(val)) {
18130
- const defaultAgent = DEFAULT_CONFIG.autoMode.defaultAgent;
18131
- return { [defaultAgent]: val };
17874
+ return { claude: val };
18132
17875
  }
18133
17876
  return val;
18134
17877
  }, PerAgentModelMapSchema);
@@ -18220,6 +17963,19 @@ var init_schemas3 = __esm(() => {
18220
17963
  formatFix: exports_external.string().optional(),
18221
17964
  build: exports_external.string().optional()
18222
17965
  }),
17966
+ autofix: exports_external.object({
17967
+ enabled: exports_external.boolean().default(true),
17968
+ maxAttempts: exports_external.number().int().min(1).default(3),
17969
+ maxTotalAttempts: exports_external.number().int().min(1).default(12),
17970
+ rethinkAtAttempt: exports_external.number().int().min(1).default(2),
17971
+ urgencyAtAttempt: exports_external.number().int().min(1).default(3)
17972
+ }).default({
17973
+ enabled: true,
17974
+ maxAttempts: 3,
17975
+ maxTotalAttempts: 12,
17976
+ rethinkAtAttempt: 2,
17977
+ urgencyAtAttempt: 3
17978
+ }),
18223
17979
  forceExit: exports_external.boolean().default(false),
18224
17980
  detectOpenHandles: exports_external.boolean().default(true),
18225
17981
  detectOpenHandlesRetries: exports_external.number().int().min(0).max(5).default(1),
@@ -18313,10 +18069,10 @@ var init_schemas3 = __esm(() => {
18313
18069
  timeoutSeconds: exports_external.number().int().positive().default(600)
18314
18070
  });
18315
18071
  AcceptanceFixConfigSchema = exports_external.object({
18316
- diagnoseModel: exports_external.string().min(1, "acceptance.fix.diagnoseModel must be non-empty"),
18317
- fixModel: exports_external.string().min(1, "acceptance.fix.fixModel must be non-empty"),
18318
- strategy: exports_external.enum(["diagnose-first", "implement-only"]),
18319
- maxRetries: exports_external.number().int().nonnegative()
18072
+ diagnoseModel: exports_external.string().min(1, "acceptance.fix.diagnoseModel must be non-empty").default("fast"),
18073
+ fixModel: exports_external.string().min(1, "acceptance.fix.fixModel must be non-empty").default("balanced"),
18074
+ strategy: exports_external.enum(["diagnose-first", "implement-only"]).default("diagnose-first"),
18075
+ maxRetries: exports_external.number().int().nonnegative().default(2)
18320
18076
  });
18321
18077
  AcceptanceConfigSchema = exports_external.object({
18322
18078
  enabled: exports_external.boolean(),
@@ -18436,6 +18192,7 @@ var init_schemas3 = __esm(() => {
18436
18192
  DebateConfigSchema = exports_external.preprocess(toObject, exports_external.object({
18437
18193
  enabled: exports_external.boolean().default(false),
18438
18194
  agents: exports_external.number().int().min(2).default(3),
18195
+ maxConcurrentDebaters: exports_external.number().int().min(1).max(10).default(2),
18439
18196
  stages: exports_external.preprocess(toObject, exports_external.object({
18440
18197
  plan: DebateStageConfigSchema({ enabled: true, resolverType: "synthesis", sessionMode: "stateful", rounds: 3 }),
18441
18198
  review: DebateStageConfigSchema({
@@ -18465,36 +18222,289 @@ var init_schemas3 = __esm(() => {
18465
18222
  }))
18466
18223
  }));
18467
18224
  NaxConfigSchema = exports_external.object({
18468
- version: exports_external.number(),
18469
- models: ModelMapSchema,
18470
- autoMode: AutoModeConfigSchema,
18471
- routing: RoutingConfigSchema,
18472
- execution: ExecutionConfigSchema,
18473
- quality: QualityConfigSchema,
18474
- tdd: TddConfigSchema,
18475
- constitution: ConstitutionConfigSchema,
18476
- analyze: AnalyzeConfigSchema,
18477
- review: ReviewConfigSchema,
18478
- plan: PlanConfigSchema,
18479
- acceptance: AcceptanceConfigSchema,
18480
- context: ContextConfigSchema,
18225
+ version: exports_external.number().default(1),
18226
+ models: ModelMapSchema.default({
18227
+ claude: {
18228
+ fast: "haiku",
18229
+ balanced: "sonnet",
18230
+ powerful: "opus"
18231
+ }
18232
+ }),
18233
+ autoMode: AutoModeConfigSchema.default({
18234
+ enabled: true,
18235
+ defaultAgent: "claude",
18236
+ fallbackOrder: ["claude"],
18237
+ complexityRouting: {
18238
+ simple: "fast",
18239
+ medium: "balanced",
18240
+ complex: "powerful",
18241
+ expert: "powerful"
18242
+ },
18243
+ escalation: {
18244
+ enabled: true,
18245
+ tierOrder: [
18246
+ { tier: "fast", attempts: 5 },
18247
+ { tier: "balanced", attempts: 3 },
18248
+ { tier: "powerful", attempts: 2 }
18249
+ ],
18250
+ escalateEntireBatch: true
18251
+ }
18252
+ }),
18253
+ routing: RoutingConfigSchema.default({
18254
+ strategy: "keyword",
18255
+ llm: {
18256
+ model: "fast",
18257
+ fallbackToKeywords: true,
18258
+ cacheDecisions: true,
18259
+ mode: "hybrid",
18260
+ timeoutMs: 30000
18261
+ }
18262
+ }),
18263
+ execution: ExecutionConfigSchema.default({
18264
+ maxIterations: 10,
18265
+ iterationDelayMs: 2000,
18266
+ costLimit: 30,
18267
+ sessionTimeoutSeconds: 3600,
18268
+ verificationTimeoutSeconds: 600,
18269
+ maxStoriesPerFeature: 500,
18270
+ rectification: {
18271
+ enabled: true,
18272
+ maxRetries: 2,
18273
+ fullSuiteTimeoutSeconds: 300,
18274
+ maxFailureSummaryChars: 2000,
18275
+ abortOnIncreasingFailures: true,
18276
+ escalateOnExhaustion: true,
18277
+ rethinkAtAttempt: 2,
18278
+ urgencyAtAttempt: 3
18279
+ },
18280
+ regressionGate: {
18281
+ enabled: true,
18282
+ timeoutSeconds: 300,
18283
+ acceptOnTimeout: true,
18284
+ mode: "deferred",
18285
+ maxRectificationAttempts: 3
18286
+ },
18287
+ contextProviderTokenBudget: 2000,
18288
+ lintCommand: null,
18289
+ typecheckCommand: null,
18290
+ dangerouslySkipPermissions: true,
18291
+ permissionProfile: "unrestricted",
18292
+ smartTestRunner: true
18293
+ }),
18294
+ quality: QualityConfigSchema.default({
18295
+ requireTypecheck: true,
18296
+ requireLint: true,
18297
+ requireTests: true,
18298
+ commands: {},
18299
+ autofix: {
18300
+ enabled: true,
18301
+ maxAttempts: 3,
18302
+ maxTotalAttempts: 12,
18303
+ rethinkAtAttempt: 2,
18304
+ urgencyAtAttempt: 3
18305
+ },
18306
+ forceExit: false,
18307
+ detectOpenHandles: true,
18308
+ detectOpenHandlesRetries: 1,
18309
+ gracePeriodMs: 5000,
18310
+ drainTimeoutMs: 2000,
18311
+ shell: "/bin/sh",
18312
+ stripEnvVars: [
18313
+ "CLAUDECODE",
18314
+ "REPL_ID",
18315
+ "AGENT",
18316
+ "GITLAB_ACCESS_TOKEN",
18317
+ "GITHUB_TOKEN",
18318
+ "GITHUB_ACCESS_TOKEN",
18319
+ "GH_TOKEN",
18320
+ "CI_GIT_TOKEN",
18321
+ "CI_JOB_TOKEN",
18322
+ "BITBUCKET_ACCESS_TOKEN",
18323
+ "NPM_TOKEN",
18324
+ "NPM_AUTH_TOKEN",
18325
+ "YARN_NPM_AUTH_TOKEN",
18326
+ "ANTHROPIC_API_KEY",
18327
+ "OPENAI_API_KEY",
18328
+ "GEMINI_API_KEY",
18329
+ "COHERE_API_KEY",
18330
+ "AWS_ACCESS_KEY_ID",
18331
+ "AWS_SECRET_ACCESS_KEY",
18332
+ "AWS_SESSION_TOKEN",
18333
+ "GOOGLE_APPLICATION_CREDENTIALS",
18334
+ "GCLOUD_SERVICE_KEY",
18335
+ "AZURE_CLIENT_SECRET",
18336
+ "AZURE_TENANT_ID",
18337
+ "TELEGRAM_BOT_TOKEN",
18338
+ "SLACK_TOKEN",
18339
+ "SLACK_WEBHOOK_URL",
18340
+ "SENTRY_AUTH_TOKEN",
18341
+ "DATADOG_API_KEY"
18342
+ ],
18343
+ testing: {
18344
+ hermetic: true
18345
+ }
18346
+ }),
18347
+ tdd: TddConfigSchema.default({
18348
+ maxRetries: 2,
18349
+ autoVerifyIsolation: true,
18350
+ autoApproveVerifier: true,
18351
+ strategy: "auto",
18352
+ sessionTiers: {
18353
+ testWriter: "balanced",
18354
+ verifier: "fast"
18355
+ },
18356
+ testWriterAllowedPaths: ["src/index.ts", "src/**/index.ts"],
18357
+ rollbackOnFailure: true,
18358
+ greenfieldDetection: true
18359
+ }),
18360
+ constitution: ConstitutionConfigSchema.default({
18361
+ enabled: true,
18362
+ path: "constitution.md",
18363
+ maxTokens: 2000
18364
+ }),
18365
+ analyze: AnalyzeConfigSchema.default({
18366
+ llmEnhanced: true,
18367
+ model: "balanced",
18368
+ fallbackToKeywords: true,
18369
+ maxCodebaseSummaryTokens: 5000
18370
+ }),
18371
+ review: ReviewConfigSchema.default({
18372
+ enabled: true,
18373
+ checks: ["typecheck", "lint"],
18374
+ commands: {},
18375
+ pluginMode: "per-story",
18376
+ semantic: {
18377
+ modelTier: "balanced",
18378
+ rules: [],
18379
+ timeoutMs: 600000,
18380
+ excludePatterns: [":!test/", ":!tests/", ":!*_test.go", ":!*.test.ts", ":!*.spec.ts", ":!**/__tests__/"]
18381
+ }
18382
+ }),
18383
+ plan: PlanConfigSchema.default({
18384
+ model: "balanced",
18385
+ outputPath: "spec.md",
18386
+ timeoutSeconds: 600
18387
+ }),
18388
+ acceptance: AcceptanceConfigSchema.default({
18389
+ enabled: true,
18390
+ maxRetries: 2,
18391
+ generateTests: true,
18392
+ testPath: ".nax-acceptance.test.ts",
18393
+ model: "fast",
18394
+ refinement: true,
18395
+ redGate: true,
18396
+ timeoutMs: 1800000,
18397
+ fix: {
18398
+ diagnoseModel: "fast",
18399
+ fixModel: "balanced",
18400
+ strategy: "diagnose-first",
18401
+ maxRetries: 2
18402
+ }
18403
+ }),
18404
+ context: ContextConfigSchema.default({
18405
+ fileInjection: "disabled",
18406
+ testCoverage: {
18407
+ enabled: true,
18408
+ detail: "names-and-counts",
18409
+ maxTokens: 500,
18410
+ testPattern: "**/*.test.{ts,js,tsx,jsx}",
18411
+ scopeToStory: true
18412
+ },
18413
+ autoDetect: {
18414
+ enabled: true,
18415
+ maxFiles: 5,
18416
+ traceImports: false
18417
+ }
18418
+ }),
18481
18419
  optimizer: OptimizerConfigSchema.optional(),
18482
18420
  plugins: exports_external.array(PluginConfigEntrySchema).optional(),
18483
18421
  disabledPlugins: exports_external.array(exports_external.string()).optional(),
18484
18422
  hooks: HooksConfigSchema.optional(),
18485
- interaction: InteractionConfigSchema.optional(),
18486
- agent: AgentConfigSchema.optional(),
18487
- precheck: PrecheckConfigSchema.optional(),
18423
+ interaction: InteractionConfigSchema.optional().default({
18424
+ plugin: "cli",
18425
+ config: {},
18426
+ defaults: {
18427
+ timeout: 600000,
18428
+ fallback: "escalate"
18429
+ },
18430
+ triggers: {
18431
+ "security-review": true,
18432
+ "cost-warning": true
18433
+ }
18434
+ }),
18435
+ agent: AgentConfigSchema.optional().default({
18436
+ protocol: "acp",
18437
+ maxInteractionTurns: 10
18438
+ }),
18439
+ precheck: PrecheckConfigSchema.optional().default({
18440
+ storySizeGate: {
18441
+ enabled: true,
18442
+ maxAcCount: 10,
18443
+ maxDescriptionLength: 3000,
18444
+ maxBulletPoints: 12,
18445
+ action: "block",
18446
+ maxReplanAttempts: 3
18447
+ }
18448
+ }),
18488
18449
  prompts: PromptsConfigSchema.optional(),
18489
18450
  generate: GenerateConfigSchema.optional(),
18490
18451
  project: ProjectProfileSchema.optional(),
18491
- debate: DebateConfigSchema.optional().default(() => DEFAULT_CONFIG.debate)
18452
+ debate: DebateConfigSchema.optional().default(() => ({
18453
+ enabled: false,
18454
+ agents: 3,
18455
+ maxConcurrentDebaters: 2,
18456
+ stages: {
18457
+ plan: {
18458
+ enabled: true,
18459
+ resolver: { type: "synthesis" },
18460
+ sessionMode: "stateful",
18461
+ rounds: 3,
18462
+ timeoutSeconds: 600
18463
+ },
18464
+ review: {
18465
+ enabled: true,
18466
+ resolver: { type: "majority-fail-closed" },
18467
+ sessionMode: "one-shot",
18468
+ rounds: 2,
18469
+ timeoutSeconds: 600
18470
+ },
18471
+ acceptance: {
18472
+ enabled: false,
18473
+ resolver: { type: "majority-fail-closed" },
18474
+ sessionMode: "one-shot",
18475
+ rounds: 1,
18476
+ timeoutSeconds: 600
18477
+ },
18478
+ rectification: {
18479
+ enabled: false,
18480
+ resolver: { type: "synthesis" },
18481
+ sessionMode: "one-shot",
18482
+ rounds: 1,
18483
+ timeoutSeconds: 600
18484
+ },
18485
+ escalation: {
18486
+ enabled: false,
18487
+ resolver: { type: "majority-fail-closed" },
18488
+ sessionMode: "one-shot",
18489
+ rounds: 1,
18490
+ timeoutSeconds: 600
18491
+ }
18492
+ }
18493
+ })),
18494
+ profile: exports_external.string().default("default")
18492
18495
  }).refine((data) => data.version === 1, {
18493
18496
  message: "Invalid version: expected 1",
18494
18497
  path: ["version"]
18495
18498
  });
18496
18499
  });
18497
18500
 
18501
+ // src/config/defaults.ts
18502
+ var DEFAULT_CONFIG;
18503
+ var init_defaults = __esm(() => {
18504
+ init_schemas3();
18505
+ DEFAULT_CONFIG = NaxConfigSchema.parse({});
18506
+ });
18507
+
18498
18508
  // src/config/schema.ts
18499
18509
  var exports_schema = {};
18500
18510
  __export(exports_schema, {
@@ -20687,26 +20697,179 @@ var init_path_security = () => {};
20687
20697
  import { homedir as homedir2 } from "os";
20688
20698
  import { join as join3, resolve as resolve2 } from "path";
20689
20699
  function globalConfigDir() {
20700
+ const override = process.env[GLOBAL_CONFIG_DIR_ENV];
20701
+ if (override)
20702
+ return override;
20690
20703
  return join3(homedir2(), ".nax");
20691
20704
  }
20692
- var PROJECT_NAX_DIR = ".nax";
20705
+ function projectConfigDir(projectRoot) {
20706
+ return join3(resolve2(projectRoot), PROJECT_NAX_DIR);
20707
+ }
20708
+ var GLOBAL_CONFIG_DIR_ENV = "NAX_GLOBAL_CONFIG_DIR", PROJECT_NAX_DIR = ".nax";
20693
20709
  var init_paths = () => {};
20694
20710
 
20711
+ // src/config/dotenv.ts
20712
+ function parseDotenv(content) {
20713
+ if (!content)
20714
+ return {};
20715
+ const result = {};
20716
+ for (const rawLine of content.split(`
20717
+ `)) {
20718
+ const line = rawLine.trim();
20719
+ if (!line || line.startsWith("#"))
20720
+ continue;
20721
+ const stripped = line.startsWith("export ") ? line.slice(7).trim() : line;
20722
+ const eqIndex = stripped.indexOf("=");
20723
+ if (eqIndex === -1)
20724
+ continue;
20725
+ const key = stripped.slice(0, eqIndex).trim();
20726
+ let value = stripped.slice(eqIndex + 1).trim();
20727
+ if (value.startsWith('"') && value.endsWith('"') || value.startsWith("'") && value.endsWith("'")) {
20728
+ value = value.slice(1, -1);
20729
+ }
20730
+ result[key] = value;
20731
+ }
20732
+ return result;
20733
+ }
20734
+ function resolveEnvVars(config2, env2) {
20735
+ if (typeof config2 === "string") {
20736
+ return resolveString(config2, env2);
20737
+ }
20738
+ if (Array.isArray(config2)) {
20739
+ return config2.map((item) => resolveEnvVars(item, env2));
20740
+ }
20741
+ if (config2 !== null && typeof config2 === "object") {
20742
+ const result = {};
20743
+ for (const [key, value] of Object.entries(config2)) {
20744
+ result[key] = resolveEnvVars(value, env2);
20745
+ }
20746
+ return result;
20747
+ }
20748
+ return config2;
20749
+ }
20750
+ function resolveString(str, env2) {
20751
+ return str.replace(/\$\$([A-Za-z_][A-Za-z0-9_]*)/g, `${DOUBLE_DOLLAR_PLACEHOLDER}$1`).replace(/\$([A-Za-z_][A-Za-z0-9_]*)/g, (_match, varName) => {
20752
+ if (!(varName in env2)) {
20753
+ throw new Error(`Environment variable $${varName} (${varName}) is not defined`);
20754
+ }
20755
+ return env2[varName];
20756
+ }).replace(new RegExp(`${DOUBLE_DOLLAR_PLACEHOLDER}([A-Za-z_][A-Za-z0-9_]*)`, "g"), "$$$1");
20757
+ }
20758
+ var DOUBLE_DOLLAR_PLACEHOLDER = "__DOLLAR_ESCAPE__";
20759
+
20760
+ // src/config/profile.ts
20761
+ import { readdirSync } from "fs";
20762
+ import { join as join4 } from "path";
20763
+ async function loadProfile(profileName, projectRoot) {
20764
+ const globalPath = join4(globalConfigDir(), "profiles", `${profileName}.json`);
20765
+ const projectPath = join4(projectConfigDir(projectRoot), "profiles", `${profileName}.json`);
20766
+ const globalFile = Bun.file(globalPath);
20767
+ const projectFile = Bun.file(projectPath);
20768
+ const [globalExists, projectExists] = await Promise.all([globalFile.exists(), projectFile.exists()]);
20769
+ if (!globalExists && !projectExists) {
20770
+ const available = await listAvailableProfileNames(projectRoot);
20771
+ const availableList = available.length > 0 ? available.join(", ") : "(none)";
20772
+ throw new Error(`Profile "${profileName}" not found. Available: ${availableList}`);
20773
+ }
20774
+ let base = {};
20775
+ if (globalExists) {
20776
+ base = await globalFile.json();
20777
+ }
20778
+ if (projectExists) {
20779
+ const projectData = await projectFile.json();
20780
+ base = deepMergeConfig(base, projectData);
20781
+ }
20782
+ return base;
20783
+ }
20784
+ async function loadProfileEnv(profileName, projectRoot) {
20785
+ const globalPath = join4(globalConfigDir(), "profiles", `${profileName}.env`);
20786
+ const projectPath = join4(projectConfigDir(projectRoot), "profiles", `${profileName}.env`);
20787
+ const globalFile = Bun.file(globalPath);
20788
+ const projectFile = Bun.file(projectPath);
20789
+ const [globalExists, projectExists] = await Promise.all([globalFile.exists(), projectFile.exists()]);
20790
+ if (!globalExists && !projectExists) {
20791
+ return {};
20792
+ }
20793
+ let merged = {};
20794
+ if (globalExists) {
20795
+ const globalContent = await globalFile.text();
20796
+ merged = { ...merged, ...parseDotenv(globalContent) };
20797
+ }
20798
+ if (projectExists) {
20799
+ const projectContent = await projectFile.text();
20800
+ merged = { ...merged, ...parseDotenv(projectContent) };
20801
+ }
20802
+ return merged;
20803
+ }
20804
+ async function resolveProfileName(cliOptions, env2, projectRoot) {
20805
+ if (cliOptions.profile) {
20806
+ return cliOptions.profile;
20807
+ }
20808
+ if (env2.NAX_PROFILE) {
20809
+ return env2.NAX_PROFILE;
20810
+ }
20811
+ const projectConfigPath = join4(projectConfigDir(projectRoot), "config.json");
20812
+ const projectConfigFile = Bun.file(projectConfigPath);
20813
+ if (await projectConfigFile.exists()) {
20814
+ const config2 = await projectConfigFile.json();
20815
+ if (typeof config2.profile === "string" && config2.profile && config2.profile !== "default") {
20816
+ return config2.profile;
20817
+ }
20818
+ }
20819
+ const globalConfigPath = join4(globalConfigDir(), "config.json");
20820
+ const globalConfigFile = Bun.file(globalConfigPath);
20821
+ if (await globalConfigFile.exists()) {
20822
+ const config2 = await globalConfigFile.json();
20823
+ if (typeof config2.profile === "string" && config2.profile && config2.profile !== "default") {
20824
+ return config2.profile;
20825
+ }
20826
+ }
20827
+ return "default";
20828
+ }
20829
+ async function listAvailableProfileNames(projectRoot) {
20830
+ const entries = await listProfiles(projectRoot);
20831
+ const names = [...new Set(entries.map((e) => e.name))].sort();
20832
+ return names;
20833
+ }
20834
+ async function listProfiles(projectRoot) {
20835
+ const globalProfilesDir = join4(globalConfigDir(), "profiles");
20836
+ const projectProfilesDir = join4(projectConfigDir(projectRoot), "profiles");
20837
+ const entries = [];
20838
+ for (const dir of [globalProfilesDir, projectProfilesDir]) {
20839
+ let files;
20840
+ try {
20841
+ files = readdirSync(dir);
20842
+ } catch {
20843
+ continue;
20844
+ }
20845
+ for (const file3 of files) {
20846
+ if (file3.endsWith(".json")) {
20847
+ const name = file3.replace(/\.json$/, "");
20848
+ entries.push({ name, path: join4(dir, file3) });
20849
+ }
20850
+ }
20851
+ }
20852
+ return entries;
20853
+ }
20854
+ var init_profile = __esm(() => {
20855
+ init_paths();
20856
+ });
20857
+
20695
20858
  // src/config/loader.ts
20696
20859
  import { existsSync as existsSync4 } from "fs";
20697
- import { basename, dirname, join as join4, resolve as resolve3 } from "path";
20860
+ import { basename, dirname, join as join5, resolve as resolve3 } from "path";
20698
20861
  function globalConfigPath() {
20699
- return join4(globalConfigDir(), "config.json");
20862
+ return join5(globalConfigDir(), "config.json");
20700
20863
  }
20701
20864
  function findProjectDir(startDir = process.cwd()) {
20702
20865
  let dir = resolve3(startDir);
20703
20866
  let depth = 0;
20704
20867
  while (depth < MAX_DIRECTORY_DEPTH) {
20705
- const candidate = join4(dir, PROJECT_NAX_DIR);
20706
- if (existsSync4(join4(candidate, "config.json"))) {
20868
+ const candidate = join5(dir, PROJECT_NAX_DIR);
20869
+ if (existsSync4(join5(candidate, "config.json"))) {
20707
20870
  return candidate;
20708
20871
  }
20709
- const parent = join4(dir, "..");
20872
+ const parent = join5(dir, "..");
20710
20873
  if (parent === dir)
20711
20874
  break;
20712
20875
  dir = parent;
@@ -20749,22 +20912,36 @@ function applyBatchModeCompat(conf) {
20749
20912
  }
20750
20913
  async function loadConfig(startDir, cliOverrides) {
20751
20914
  let rawConfig = structuredClone(DEFAULT_CONFIG);
20915
+ const projDir = startDir ? basename(startDir) === PROJECT_NAX_DIR ? startDir : findProjectDir(startDir) : findProjectDir();
20916
+ const projectRoot = startDir ? basename(startDir) === PROJECT_NAX_DIR ? dirname(startDir) : startDir : process.cwd();
20917
+ const profileName = await resolveProfileName(cliOverrides ?? {}, process.env, projectRoot);
20918
+ if (profileName !== "default") {
20919
+ const profileData = await loadProfile(profileName, projectRoot);
20920
+ rawConfig = deepMergeConfig(rawConfig, profileData);
20921
+ await loadProfileEnv(profileName, projectRoot);
20922
+ }
20752
20923
  const globalConfRaw = await loadJsonFile(globalConfigPath(), "config");
20753
20924
  if (globalConfRaw) {
20754
- const globalConf = applyBatchModeCompat(applyRemovedStrategyCompat(globalConfRaw));
20925
+ const { profile: _gProfile, ...globalConfStripped } = globalConfRaw;
20926
+ const globalConf = applyBatchModeCompat(applyRemovedStrategyCompat(globalConfStripped));
20755
20927
  rawConfig = deepMergeConfig(rawConfig, globalConf);
20756
20928
  }
20757
- const projDir = startDir ? basename(startDir) === PROJECT_NAX_DIR ? startDir : findProjectDir(startDir) : findProjectDir();
20758
20929
  if (projDir) {
20759
- const projConf = await loadJsonFile(join4(projDir, "config.json"), "config");
20930
+ const projConf = await loadJsonFile(join5(projDir, "config.json"), "config");
20760
20931
  if (projConf) {
20761
- const resolvedProjConf = applyBatchModeCompat(applyRemovedStrategyCompat(projConf));
20932
+ const { profile: _pProfile, ...projConfStripped } = projConf;
20933
+ const resolvedProjConf = applyBatchModeCompat(applyRemovedStrategyCompat(projConfStripped));
20762
20934
  rawConfig = deepMergeConfig(rawConfig, resolvedProjConf);
20763
20935
  }
20764
20936
  }
20765
20937
  if (cliOverrides) {
20766
20938
  rawConfig = deepMergeConfig(rawConfig, cliOverrides);
20767
20939
  }
20940
+ rawConfig.profile = profileName;
20941
+ const hasMergedConfigs = globalConfRaw || projDir !== null || cliOverrides !== undefined || profileName !== "default";
20942
+ if (!hasMergedConfigs) {
20943
+ return structuredClone(DEFAULT_CONFIG);
20944
+ }
20768
20945
  const result = NaxConfigSchema.safeParse(rawConfig);
20769
20946
  if (!result.success) {
20770
20947
  const errors3 = result.error.issues.map((err) => {
@@ -20786,7 +20963,7 @@ async function loadConfigForWorkdir(rootConfigPath, packageDir) {
20786
20963
  return rootConfig;
20787
20964
  }
20788
20965
  const repoRoot = dirname(rootNaxDir);
20789
- const packageConfigPath = join4(repoRoot, PROJECT_NAX_DIR, "mono", packageDir, "config.json");
20966
+ const packageConfigPath = join5(repoRoot, PROJECT_NAX_DIR, "mono", packageDir, "config.json");
20790
20967
  const packageOverride = await loadJsonFile(packageConfigPath, "config");
20791
20968
  if (!packageOverride) {
20792
20969
  logger.debug("config", "Per-package config not found \u2014 falling back to root config", {
@@ -20803,6 +20980,7 @@ var init_loader = __esm(() => {
20803
20980
  init_json_file();
20804
20981
  init_path_security();
20805
20982
  init_paths();
20983
+ init_profile();
20806
20984
  init_schema();
20807
20985
  });
20808
20986
  // src/config/index.ts
@@ -20811,6 +20989,7 @@ var init_config = __esm(() => {
20811
20989
  init_loader();
20812
20990
  init_path_security();
20813
20991
  init_paths();
20992
+ init_profile();
20814
20993
  });
20815
20994
 
20816
20995
  // src/utils/errors.ts
@@ -20996,7 +21175,7 @@ __export(exports_generator, {
20996
21175
  acceptanceTestFilename: () => acceptanceTestFilename,
20997
21176
  _generatorPRDDeps: () => _generatorPRDDeps
20998
21177
  });
20999
- import { join as join5 } from "path";
21178
+ import { join as join6 } from "path";
21000
21179
  function skeletonImportLine(testFramework) {
21001
21180
  if (!testFramework)
21002
21181
  return `import { describe, test, expect } from "bun:test";`;
@@ -21092,7 +21271,7 @@ Rules:
21092
21271
  - Every test MUST have real assertions that PASS when the feature is correctly implemented and FAIL when it is broken
21093
21272
  - **Prefer behavioral tests** \u2014 import functions and call them rather than reading source files. For example, to verify "getPostRunActions() returns empty array", import PluginRegistry and call getPostRunActions(), don't grep the source file for the method name.
21094
21273
  - **File output (REQUIRED)**: Write the acceptance test file DIRECTLY to the path shown below. Do NOT output the test code in your response. After writing the file, reply with a brief confirmation.
21095
- - **Path anchor (CRITICAL)**: Write the test file to this exact path: \`${join5(options.workdir, ".nax", "features", options.featureName, resolveAcceptanceTestFile(options.language, options.config?.acceptance?.testPath))}\`. Import from package sources using relative paths like \`../../../src/...\` (3 levels up from \`.nax/features/<name>/\` to the package root).`;
21274
+ - **Path anchor (CRITICAL)**: Write the test file to this exact path: \`${join6(options.workdir, ".nax", "features", options.featureName, resolveAcceptanceTestFile(options.language, options.config?.acceptance?.testPath))}\`. Import from package sources using relative paths like \`../../../src/...\` (3 levels up from \`.nax/features/<name>/\` to the package root).`;
21096
21275
  const prompt = basePrompt;
21097
21276
  logger.info("acceptance", "Generating tests from PRD refined criteria", { count: refinedCriteria.length });
21098
21277
  const completeResult = await (options.adapter ?? _generatorPRDDeps.adapter).complete(prompt, {
@@ -21111,7 +21290,7 @@ Rules:
21111
21290
  outputPreview: rawOutput.slice(0, 300)
21112
21291
  });
21113
21292
  if (!testCode) {
21114
- const targetPath = join5(options.workdir, ".nax", "features", options.featureName, resolveAcceptanceTestFile(options.language, options.config?.acceptance?.testPath));
21293
+ const targetPath = join6(options.workdir, ".nax", "features", options.featureName, resolveAcceptanceTestFile(options.language, options.config?.acceptance?.testPath));
21115
21294
  let recoveryFailed = false;
21116
21295
  logger.debug("acceptance", "BUG-076 recovery: checking for agent-written file", { targetPath });
21117
21296
  try {
@@ -21166,7 +21345,7 @@ Rules:
21166
21345
  testable: c.testable,
21167
21346
  storyId: c.storyId
21168
21347
  })), null, 2);
21169
- await _generatorPRDDeps.writeFile(join5(options.featureDir, "acceptance-refined.json"), refinedJsonContent);
21348
+ await _generatorPRDDeps.writeFile(join6(options.featureDir, "acceptance-refined.json"), refinedJsonContent);
21170
21349
  return { testCode, criteria };
21171
21350
  }
21172
21351
  function parseAcceptanceCriteria(specContent) {
@@ -22163,7 +22342,7 @@ var package_default;
22163
22342
  var init_package = __esm(() => {
22164
22343
  package_default = {
22165
22344
  name: "@nathapp/nax",
22166
- version: "0.57.1-canary.1",
22345
+ version: "0.57.1",
22167
22346
  description: "AI Coding Agent Orchestrator \u2014 loops until done",
22168
22347
  type: "module",
22169
22348
  bin: {
@@ -22242,8 +22421,8 @@ var init_version = __esm(() => {
22242
22421
  NAX_VERSION = package_default.version;
22243
22422
  NAX_COMMIT = (() => {
22244
22423
  try {
22245
- if (/^[0-9a-f]{6,10}$/.test("814ed29f"))
22246
- return "814ed29f";
22424
+ if (/^[0-9a-f]{6,10}$/.test("39861723"))
22425
+ return "39861723";
22247
22426
  } catch {}
22248
22427
  try {
22249
22428
  const result = Bun.spawnSync(["git", "rev-parse", "--short", "HEAD"], {
@@ -22390,6 +22569,27 @@ var init_prd = __esm(() => {
22390
22569
  PRD_MAX_FILE_SIZE = 5 * 1024 * 1024;
22391
22570
  });
22392
22571
 
22572
+ // src/debate/concurrency.ts
22573
+ async function allSettledBounded(tasks, limit) {
22574
+ if (tasks.length === 0)
22575
+ return [];
22576
+ const results = new Array(tasks.length);
22577
+ let nextIndex = 0;
22578
+ async function worker() {
22579
+ while (nextIndex < tasks.length) {
22580
+ const i = nextIndex++;
22581
+ try {
22582
+ results[i] = { status: "fulfilled", value: await tasks[i]() };
22583
+ } catch (reason) {
22584
+ results[i] = { status: "rejected", reason };
22585
+ }
22586
+ }
22587
+ }
22588
+ const concurrency = Math.max(1, Math.min(limit, tasks.length));
22589
+ await Promise.all(Array.from({ length: concurrency }, () => worker()));
22590
+ return results;
22591
+ }
22592
+
22393
22593
  // src/debate/prompts.ts
22394
22594
  function buildCritiquePrompt(taskPrompt, allProposals, debaterIndex) {
22395
22595
  const othersProposals = allProposals.filter((_, i) => i !== debaterIndex);
@@ -22496,7 +22696,7 @@ var DEFAULT_FALLBACK_AGENT = "claude";
22496
22696
  var init_resolvers = () => {};
22497
22697
 
22498
22698
  // src/debate/session.ts
22499
- import { join as join11 } from "path";
22699
+ import { join as join12 } from "path";
22500
22700
  function resolveDebaterModel(debater, config2) {
22501
22701
  const tier = debater.model ?? "fast";
22502
22702
  if (!config2?.models)
@@ -22661,7 +22861,10 @@ class DebateSession {
22661
22861
  stage: this.stage,
22662
22862
  debaters: resolved.map((r) => r.debater.agent)
22663
22863
  });
22664
- const proposalSettled = await Promise.allSettled(resolved.map(({ debater, adapter }, debaterIdx) => this.runStatefulTurn(adapter, debater, prompt, `debate-${this.stage}-${debaterIdx}`, config2.rounds > 1)));
22864
+ const cfg = this.config;
22865
+ const debate = cfg?.debate;
22866
+ const concurrencyLimit = debate?.maxConcurrentDebaters ?? 2;
22867
+ const proposalSettled = await allSettledBounded(resolved.map(({ debater, adapter }, debaterIdx) => () => this.runStatefulTurn(adapter, debater, prompt, `debate-${this.stage}-${debaterIdx}`, config2.rounds > 1)), concurrencyLimit);
22665
22868
  const successfulProposals = proposalSettled.filter((r) => r.status === "fulfilled").map((r) => r.value);
22666
22869
  for (const r of proposalSettled) {
22667
22870
  if (r.status === "fulfilled") {
@@ -22741,7 +22944,7 @@ class DebateSession {
22741
22944
  let critiqueOutputs = [];
22742
22945
  if (config2.rounds > 1) {
22743
22946
  const proposalOutputs2 = successfulProposals.map((s) => s.output);
22744
- const critiqueSettled = await Promise.allSettled(successfulProposals.map((proposal, successfulIdx) => this.runStatefulTurn(proposal.adapter, proposal.debater, buildCritiquePrompt(prompt, proposalOutputs2, successfulIdx), proposal.roleKey ?? `debate-${this.stage}-${successfulIdx}`, false)));
22947
+ const critiqueSettled = await allSettledBounded(successfulProposals.map((proposal, successfulIdx) => () => this.runStatefulTurn(proposal.adapter, proposal.debater, buildCritiquePrompt(prompt, proposalOutputs2, successfulIdx), proposal.roleKey ?? `debate-${this.stage}-${successfulIdx}`, false)), concurrencyLimit);
22745
22948
  for (const r of critiqueSettled) {
22746
22949
  if (r.status === "fulfilled") {
22747
22950
  totalCostUsd += r.value.cost;
@@ -22791,14 +22994,17 @@ class DebateSession {
22791
22994
  stage: this.stage,
22792
22995
  debaters: resolved.map((r) => r.debater.agent)
22793
22996
  });
22794
- const proposalSettled = await Promise.allSettled(resolved.map(({ debater, adapter }) => runComplete(adapter, prompt, {
22997
+ const cfg = this.config;
22998
+ const debate = cfg?.debate;
22999
+ const concurrencyLimit = debate?.maxConcurrentDebaters ?? 2;
23000
+ const proposalSettled = await allSettledBounded(resolved.map(({ debater, adapter }, i) => () => runComplete(adapter, prompt, {
22795
23001
  model: resolveDebaterModel(debater, this.config),
22796
23002
  featureName: this.stage,
22797
23003
  config: this.config,
22798
23004
  storyId: this.storyId,
22799
- sessionRole: "debate-proposal",
23005
+ sessionRole: `debate-proposal-${i}`,
22800
23006
  timeoutMs: this.timeoutMs
22801
- }, modelTierFromDebater(debater)).then((result) => ({ debater, adapter, output: result.output, cost: result.costUsd }))));
23007
+ }, modelTierFromDebater(debater)).then((result) => ({ debater, adapter, output: result.output, cost: result.costUsd }))), concurrencyLimit);
22802
23008
  const successful = proposalSettled.filter((r) => r.status === "fulfilled").map((r) => r.value);
22803
23009
  for (const r of proposalSettled) {
22804
23010
  if (r.status === "fulfilled") {
@@ -22877,14 +23083,14 @@ class DebateSession {
22877
23083
  let critiqueOutputs = [];
22878
23084
  if (config2.rounds > 1) {
22879
23085
  const proposalOutputs2 = successful.map((p) => p.output);
22880
- const critiqueSettled = await Promise.allSettled(successful.map(({ debater, adapter }, i) => runComplete(adapter, buildCritiquePrompt(prompt, proposalOutputs2, i), {
23086
+ const critiqueSettled = await allSettledBounded(successful.map(({ debater, adapter }, i) => () => runComplete(adapter, buildCritiquePrompt(prompt, proposalOutputs2, i), {
22881
23087
  model: resolveDebaterModel(debater, this.config),
22882
23088
  featureName: this.stage,
22883
23089
  config: this.config,
22884
23090
  storyId: this.storyId,
22885
- sessionRole: "debate-critique",
23091
+ sessionRole: `debate-critique-${i}`,
22886
23092
  timeoutMs: this.timeoutMs
22887
- }, modelTierFromDebater(debater))));
23093
+ }, modelTierFromDebater(debater))), concurrencyLimit);
22888
23094
  for (const r of critiqueSettled) {
22889
23095
  if (r.status === "fulfilled") {
22890
23096
  totalCostUsd += r.value.costUsd;
@@ -22934,8 +23140,11 @@ class DebateSession {
22934
23140
  stage: this.stage,
22935
23141
  debaters: resolved.map((r) => r.debater.agent)
22936
23142
  });
22937
- const planSettled = await Promise.allSettled(resolved.map(async ({ debater, adapter }, i) => {
22938
- const tempOutputPath = join11(opts.outputDir, `prd-debate-${i}.json`);
23143
+ const cfg = this.config;
23144
+ const debate = cfg?.debate;
23145
+ const concurrencyLimit = debate?.maxConcurrentDebaters ?? 2;
23146
+ const settled = await allSettledBounded(resolved.map(({ debater, adapter }, i) => async () => {
23147
+ const tempOutputPath = join12(opts.outputDir, `prd-debate-${i}.json`);
22939
23148
  const debaterPrompt = `${basePrompt}
22940
23149
 
22941
23150
  Write the PRD JSON directly to this file path: ${tempOutputPath}
@@ -22950,12 +23159,28 @@ Do NOT output the JSON to the conversation. Write the file, then reply with a br
22950
23159
  dangerouslySkipPermissions: opts.dangerouslySkipPermissions,
22951
23160
  maxInteractionTurns: opts.maxInteractionTurns,
22952
23161
  featureName: opts.feature,
22953
- sessionRole: "plan"
23162
+ storyId: this.storyId,
23163
+ sessionRole: `plan-${i}`
22954
23164
  });
22955
23165
  const output = await _debateSessionDeps.readFile(tempOutputPath);
22956
23166
  return { debater, adapter, output, cost: 0 };
22957
- }));
22958
- const successful = planSettled.filter((r) => r.status === "fulfilled").map((r) => r.value);
23167
+ }), concurrencyLimit);
23168
+ const successful = [];
23169
+ for (let i = 0;i < settled.length; i++) {
23170
+ const res = settled[i];
23171
+ if (res.status === "fulfilled") {
23172
+ successful.push(res.value);
23173
+ } else {
23174
+ const { debater } = resolved[i];
23175
+ logger?.warn("debate", "debate:debater-failed", {
23176
+ storyId: this.storyId,
23177
+ stage: this.stage,
23178
+ debaterIndex: i,
23179
+ agent: debater.agent,
23180
+ error: res.reason instanceof Error ? res.reason.message : String(res.reason)
23181
+ });
23182
+ }
23183
+ }
22959
23184
  for (let i = 0;i < successful.length; i++) {
22960
23185
  logger?.info("debate", "debate:proposal", {
22961
23186
  storyId: this.storyId,
@@ -25881,59 +26106,6 @@ ${stderr}`;
25881
26106
  };
25882
26107
  });
25883
26108
 
25884
- // src/agents/claude/index.ts
25885
- var init_claude = __esm(() => {
25886
- init_adapter3();
25887
- init_execution();
25888
- });
25889
-
25890
- // src/agents/shared/validation.ts
25891
- function validateAgentForTier(agent, tier) {
25892
- return agent.capabilities.supportedTiers.includes(tier);
25893
- }
25894
- function validateAgentFeature(agent, feature) {
25895
- return agent.capabilities.features.has(feature);
25896
- }
25897
- function describeAgentCapabilities(agent) {
25898
- const tiers = agent.capabilities.supportedTiers.join(",");
25899
- const features = Array.from(agent.capabilities.features).join(",");
25900
- const maxTokens = agent.capabilities.maxContextTokens;
25901
- return `${agent.name}: tiers=[${tiers}], maxTokens=${maxTokens}, features=[${features}]`;
25902
- }
25903
-
25904
- // src/agents/index.ts
25905
- var exports_agents = {};
25906
- __export(exports_agents, {
25907
- validateAgentForTier: () => validateAgentForTier,
25908
- validateAgentFeature: () => validateAgentFeature,
25909
- parseTokenUsage: () => parseTokenUsage,
25910
- getInstalledAgents: () => getInstalledAgents,
25911
- getAllAgentNames: () => getAllAgentNames,
25912
- getAgentVersions: () => getAgentVersions,
25913
- getAgentVersion: () => getAgentVersion,
25914
- getAgent: () => getAgent,
25915
- formatCostWithConfidence: () => formatCostWithConfidence,
25916
- estimateCostFromTokenUsage: () => estimateCostFromTokenUsage,
25917
- estimateCostFromOutput: () => estimateCostFromOutput,
25918
- estimateCostByDuration: () => estimateCostByDuration,
25919
- estimateCost: () => estimateCost,
25920
- describeAgentCapabilities: () => describeAgentCapabilities,
25921
- checkAgentHealth: () => checkAgentHealth,
25922
- MODEL_PRICING: () => MODEL_PRICING,
25923
- CompleteError: () => CompleteError,
25924
- ClaudeCodeAdapter: () => ClaudeCodeAdapter,
25925
- COST_RATES: () => COST_RATES,
25926
- AllAgentsUnavailableError: () => AllAgentsUnavailableError
25927
- });
25928
- var init_agents = __esm(() => {
25929
- init_types2();
25930
- init_claude();
25931
- init_registry();
25932
- init_cost();
25933
- init_version_detection();
25934
- init_errors();
25935
- });
25936
-
25937
26109
  // src/pipeline/stages/acceptance-setup.ts
25938
26110
  var exports_acceptance_setup = {};
25939
26111
  __export(exports_acceptance_setup, {
@@ -26077,7 +26249,6 @@ ${stderr}` };
26077
26249
  }
26078
26250
  if (shouldGenerate) {
26079
26251
  totalCriteria = allCriteria.length;
26080
- const { getAgent: getAgent2 } = await Promise.resolve().then(() => (init_agents(), exports_agents));
26081
26252
  const agent = (ctx.agentGetFn ?? _acceptanceSetupDeps.getAgent)(ctx.config.autoMode.defaultAgent);
26082
26253
  let allRefinedCriteria;
26083
26254
  if (ctx.config.acceptance.refinement) {
@@ -26105,7 +26276,7 @@ ${stderr}` };
26105
26276
  testableCount = allRefinedCriteria.filter((r) => r.testable).length;
26106
26277
  for (const [workdir, group] of workdirGroups) {
26107
26278
  const packageDir = workdir ? path5.join(ctx.workdir, workdir) : ctx.workdir;
26108
- const testPath = path5.join(packageDir, resolveAcceptanceTestFile(language, testPathConfig));
26279
+ const testPath = path5.join(packageDir, ".nax", "features", featureName, resolveAcceptanceTestFile(language, testPathConfig));
26109
26280
  const groupStoryIds = new Set(group.stories.map((s) => s.id));
26110
26281
  const groupRefined = allRefinedCriteria.filter((r) => groupStoryIds.has(r.storyId));
26111
26282
  const result = await _acceptanceSetupDeps.generate(group.stories, groupRefined, {
@@ -26162,6 +26333,59 @@ ${stderr}` };
26162
26333
  };
26163
26334
  });
26164
26335
 
26336
+ // src/agents/claude/index.ts
26337
+ var init_claude = __esm(() => {
26338
+ init_adapter3();
26339
+ init_execution();
26340
+ });
26341
+
26342
+ // src/agents/shared/validation.ts
26343
+ function validateAgentForTier(agent, tier) {
26344
+ return agent.capabilities.supportedTiers.includes(tier);
26345
+ }
26346
+ function validateAgentFeature(agent, feature) {
26347
+ return agent.capabilities.features.has(feature);
26348
+ }
26349
+ function describeAgentCapabilities(agent) {
26350
+ const tiers = agent.capabilities.supportedTiers.join(",");
26351
+ const features = Array.from(agent.capabilities.features).join(",");
26352
+ const maxTokens = agent.capabilities.maxContextTokens;
26353
+ return `${agent.name}: tiers=[${tiers}], maxTokens=${maxTokens}, features=[${features}]`;
26354
+ }
26355
+
26356
+ // src/agents/index.ts
26357
+ var exports_agents = {};
26358
+ __export(exports_agents, {
26359
+ validateAgentForTier: () => validateAgentForTier,
26360
+ validateAgentFeature: () => validateAgentFeature,
26361
+ parseTokenUsage: () => parseTokenUsage,
26362
+ getInstalledAgents: () => getInstalledAgents,
26363
+ getAllAgentNames: () => getAllAgentNames,
26364
+ getAgentVersions: () => getAgentVersions,
26365
+ getAgentVersion: () => getAgentVersion,
26366
+ getAgent: () => getAgent,
26367
+ formatCostWithConfidence: () => formatCostWithConfidence,
26368
+ estimateCostFromTokenUsage: () => estimateCostFromTokenUsage,
26369
+ estimateCostFromOutput: () => estimateCostFromOutput,
26370
+ estimateCostByDuration: () => estimateCostByDuration,
26371
+ estimateCost: () => estimateCost,
26372
+ describeAgentCapabilities: () => describeAgentCapabilities,
26373
+ checkAgentHealth: () => checkAgentHealth,
26374
+ MODEL_PRICING: () => MODEL_PRICING,
26375
+ CompleteError: () => CompleteError,
26376
+ ClaudeCodeAdapter: () => ClaudeCodeAdapter,
26377
+ COST_RATES: () => COST_RATES,
26378
+ AllAgentsUnavailableError: () => AllAgentsUnavailableError
26379
+ });
26380
+ var init_agents = __esm(() => {
26381
+ init_types2();
26382
+ init_claude();
26383
+ init_registry();
26384
+ init_cost();
26385
+ init_version_detection();
26386
+ init_errors();
26387
+ });
26388
+
26165
26389
  // src/quality/runner.ts
26166
26390
  var {spawn: spawn2 } = globalThis.Bun;
26167
26391
  async function runQualityCommand(opts) {
@@ -26251,6 +26475,59 @@ var init_quality = __esm(() => {
26251
26475
  init_runner2();
26252
26476
  });
26253
26477
 
26478
+ // src/verification/shared-rectification-loop.ts
26479
+ function resolveLogData(data, state) {
26480
+ if (!data) {
26481
+ return;
26482
+ }
26483
+ return typeof data === "function" ? data(state) : data;
26484
+ }
26485
+ function buildProgressivePromptPreamble(opts) {
26486
+ const rethinkAt = Math.min(opts.rethinkAtAttempt ?? 2, opts.maxAttempts);
26487
+ const urgencyAt = Math.min(opts.urgencyAtAttempt ?? 3, opts.maxAttempts);
26488
+ const shouldRethink = opts.attempt >= rethinkAt;
26489
+ const shouldUrgency = opts.attempt >= urgencyAt;
26490
+ if (!shouldRethink && !shouldUrgency) {
26491
+ return "";
26492
+ }
26493
+ if (shouldUrgency) {
26494
+ opts.logger?.info(opts.stage, "Progressive prompt escalation: urgency + rethink injected", {
26495
+ attempt: opts.attempt,
26496
+ rethinkAtAttempt: rethinkAt,
26497
+ urgencyAtAttempt: urgencyAt,
26498
+ maxAttempts: opts.maxAttempts
26499
+ });
26500
+ } else {
26501
+ opts.logger?.info(opts.stage, "Progressive prompt escalation: rethink injected", {
26502
+ attempt: opts.attempt,
26503
+ rethinkAtAttempt: rethinkAt,
26504
+ maxAttempts: opts.maxAttempts
26505
+ });
26506
+ }
26507
+ const urgencySection = shouldUrgency ? opts.urgencySection : "";
26508
+ const rethinkSection = shouldRethink ? opts.rethinkSection : "";
26509
+ return `${urgencySection}${rethinkSection}`;
26510
+ }
26511
+ async function runSharedRectificationLoop(opts) {
26512
+ opts.logger?.info(opts.stage, opts.startMessage, resolveLogData(opts.startData, opts.state));
26513
+ while (opts.canContinue(opts.state)) {
26514
+ opts.state.attempt++;
26515
+ opts.logger?.info(opts.stage, opts.attemptMessage(opts.state.attempt, opts.maxAttempts, opts.state), resolveLogData(opts.attemptData, opts.state));
26516
+ const prompt = await opts.buildPrompt(opts.state.attempt, opts.state);
26517
+ await opts.runAttempt(opts.state.attempt, prompt, opts.state);
26518
+ const passed = await opts.checkResult(opts.state.attempt, opts.state);
26519
+ if (passed) {
26520
+ return true;
26521
+ }
26522
+ await opts.onAttemptFailure?.(opts.state.attempt, opts.state);
26523
+ }
26524
+ await opts.onLoopEnd?.(opts.state);
26525
+ if (opts.state.attempt >= opts.maxAttempts) {
26526
+ return await opts.onExhausted?.(opts.state) ?? false;
26527
+ }
26528
+ return false;
26529
+ }
26530
+
26254
26531
  // src/pipeline/event-bus.ts
26255
26532
  class PipelineEventBus {
26256
26533
  subscribers = new Map;
@@ -26702,6 +26979,16 @@ async function runSemanticReview(workdir, storyGitRef, story, semanticConfig, mo
26702
26979
  const needsTruncation = rawDiff.length > DIFF_CAP_BYTES;
26703
26980
  const stat = needsTruncation ? await collectDiffStat(workdir, effectiveRef) : undefined;
26704
26981
  const diff = truncateDiff(rawDiff, stat);
26982
+ if (!diff) {
26983
+ return {
26984
+ check: "semantic",
26985
+ success: true,
26986
+ command: "",
26987
+ exitCode: 0,
26988
+ output: "skipped: no production code changes",
26989
+ durationMs: Date.now() - startTime
26990
+ };
26991
+ }
26705
26992
  const agent = modelResolver(semanticConfig.modelTier);
26706
26993
  if (!agent) {
26707
26994
  logger?.warn("semantic", "No agent available for semantic review \u2014 skipping", {
@@ -27196,7 +27483,7 @@ __export(exports_review, {
27196
27483
  reviewStage: () => reviewStage,
27197
27484
  _reviewDeps: () => _reviewDeps
27198
27485
  });
27199
- import { join as join17 } from "path";
27486
+ import { join as join18 } from "path";
27200
27487
  var reviewStage, _reviewDeps;
27201
27488
  var init_review = __esm(() => {
27202
27489
  init_agents();
@@ -27210,7 +27497,7 @@ var init_review = __esm(() => {
27210
27497
  const logger = getLogger();
27211
27498
  const effectiveConfig = ctx.effectiveConfig ?? ctx.config;
27212
27499
  logger.info("review", "Running review phase", { storyId: ctx.story.id });
27213
- const effectiveWorkdir = ctx.story.workdir ? join17(ctx.workdir, ctx.story.workdir) : ctx.workdir;
27500
+ const effectiveWorkdir = ctx.story.workdir ? join18(ctx.workdir, ctx.story.workdir) : ctx.workdir;
27214
27501
  const agentResolver = ctx.agentGetFn ?? getAgent;
27215
27502
  const agentName = effectiveConfig.autoMode?.defaultAgent;
27216
27503
  const modelResolver = (_tier) => agentName ? agentResolver(agentName) ?? null : null;
@@ -27262,7 +27549,7 @@ var init_review = __esm(() => {
27262
27549
  });
27263
27550
 
27264
27551
  // src/pipeline/stages/autofix.ts
27265
- import { join as join18 } from "path";
27552
+ import { join as join19 } from "path";
27266
27553
  async function recheckReview(ctx) {
27267
27554
  const { reviewStage: reviewStage2 } = await Promise.resolve().then(() => (init_review(), exports_review));
27268
27555
  if (!reviewStage2.enabled(ctx))
@@ -27295,11 +27582,38 @@ Fix ALL errors listed above. Do NOT change test files or test behavior.
27295
27582
  Do NOT add new features \u2014 only fix the quality check errors.
27296
27583
  Commit your fixes when done.${scopeConstraint}`;
27297
27584
  }
27585
+ function buildAutofixEscalationPreamble(attempt, maxAttempts, rethinkAtAttempt, urgencyAtAttempt) {
27586
+ return buildProgressivePromptPreamble({
27587
+ attempt,
27588
+ maxAttempts,
27589
+ rethinkAtAttempt,
27590
+ urgencyAtAttempt,
27591
+ stage: "autofix",
27592
+ logger: getLogger(),
27593
+ urgencySection: `## Final Autofix Attempt Before Escalation
27594
+
27595
+ This is attempt ${attempt}. If the review still fails after this, autofix will escalate instead of retrying.
27596
+ A different approach is required. Do not repeat the same fix.
27597
+
27598
+ `,
27599
+ rethinkSection: `## Previous Attempt Did Not Fix the Failures
27600
+
27601
+ Your previous fix attempt (attempt ${attempt}) did not resolve the quality errors. Rethink your approach.
27602
+
27603
+ - Do not repeat the same edit pattern.
27604
+ - Re-read the failing diagnostics carefully.
27605
+ - Try a fundamentally different fix strategy if the earlier one did not work.
27606
+
27607
+ `
27608
+ });
27609
+ }
27298
27610
  async function runAgentRectification(ctx) {
27299
27611
  const logger = getLogger();
27300
27612
  const effectiveConfig = ctx.effectiveConfig ?? ctx.config;
27301
27613
  const maxPerCycle = effectiveConfig.quality.autofix?.maxAttempts ?? 2;
27302
27614
  const maxTotal = effectiveConfig.quality.autofix?.maxTotalAttempts ?? 10;
27615
+ const rethinkAtAttempt = effectiveConfig.quality.autofix?.rethinkAtAttempt ?? 2;
27616
+ const urgencyAtAttempt = effectiveConfig.quality.autofix?.urgencyAtAttempt ?? 3;
27303
27617
  const consumed = ctx.autofixAttempt ?? 0;
27304
27618
  const failedChecks = collectFailedChecks(ctx);
27305
27619
  if (failedChecks.length === 0) {
@@ -27316,56 +27630,90 @@ async function runAgentRectification(ctx) {
27316
27630
  }
27317
27631
  const remainingBudget = maxTotal - consumed;
27318
27632
  const maxAttempts = Math.min(maxPerCycle, remainingBudget);
27319
- logger.info("autofix", "Starting agent rectification for review failures", {
27633
+ const agentGetFn = ctx.agentGetFn ?? _autofixDeps.getAgent;
27634
+ const loopState = {
27635
+ attempt: 0,
27636
+ failedChecks
27637
+ };
27638
+ return runSharedRectificationLoop({
27639
+ stage: "autofix",
27320
27640
  storyId: ctx.story.id,
27321
- failedChecks: failedChecks.map((c) => c.check),
27322
27641
  maxAttempts,
27323
- totalUsed: consumed,
27324
- maxTotalAttempts: maxTotal
27325
- });
27326
- const agentGetFn = ctx.agentGetFn ?? _autofixDeps.getAgent;
27327
- for (let attempt = 1;attempt <= maxAttempts; attempt++) {
27328
- ctx.autofixAttempt = consumed + attempt;
27329
- logger.info("autofix", `Agent rectification attempt ${ctx.autofixAttempt}/${maxTotal}`, { storyId: ctx.story.id });
27330
- const agent = agentGetFn(ctx.config.autoMode.defaultAgent);
27331
- if (!agent) {
27332
- logger.error("autofix", "Agent not found \u2014 cannot run agent rectification", { storyId: ctx.story.id });
27333
- return false;
27334
- }
27335
- const prompt = buildReviewRectificationPrompt(failedChecks, ctx.story);
27336
- const modelTier = ctx.story.routing?.modelTier ?? ctx.config.autoMode.escalation.tierOrder[0]?.tier ?? "balanced";
27337
- const modelDef = resolveModelForAgent(ctx.config.models, ctx.routing.agent ?? ctx.config.autoMode.defaultAgent, modelTier, ctx.config.autoMode.defaultAgent);
27338
- const rectificationWorkdir = ctx.story.workdir ? join18(ctx.workdir, ctx.story.workdir) : ctx.workdir;
27339
- await agent.run({
27340
- prompt,
27341
- workdir: rectificationWorkdir,
27342
- modelTier,
27343
- modelDef,
27344
- timeoutSeconds: ctx.config.execution.sessionTimeoutSeconds,
27345
- dangerouslySkipPermissions: resolvePermissions(ctx.config, "rectification").skipPermissions,
27346
- pipelineStage: "rectification",
27347
- config: ctx.config,
27348
- maxInteractionTurns: ctx.config.agent?.maxInteractionTurns,
27642
+ state: loopState,
27643
+ logger,
27644
+ startMessage: "Starting agent rectification for review failures",
27645
+ startData: {
27349
27646
  storyId: ctx.story.id,
27350
- sessionRole: "implementer"
27351
- });
27352
- const passed = await _autofixDeps.recheckReview(ctx);
27353
- if (passed) {
27354
- logger.info("autofix", `[OK] Agent rectification succeeded on attempt ${attempt}`, {
27647
+ failedChecks: failedChecks.map((check2) => check2.check),
27648
+ maxAttempts,
27649
+ totalUsed: consumed,
27650
+ maxTotalAttempts: maxTotal
27651
+ },
27652
+ attemptMessage: (attempt) => `Agent rectification attempt ${consumed + attempt}/${maxTotal}`,
27653
+ attemptData: { storyId: ctx.story.id },
27654
+ canContinue: (state) => state.failedChecks.length > 0 && state.attempt < maxAttempts,
27655
+ buildPrompt: (attempt, state) => {
27656
+ let prompt = buildReviewRectificationPrompt(state.failedChecks, ctx.story);
27657
+ const escalationPreamble = buildAutofixEscalationPreamble(attempt, maxAttempts, rethinkAtAttempt, urgencyAtAttempt);
27658
+ if (escalationPreamble) {
27659
+ prompt = `${escalationPreamble}${prompt}`;
27660
+ }
27661
+ return prompt;
27662
+ },
27663
+ runAttempt: async (attempt, prompt) => {
27664
+ ctx.autofixAttempt = consumed + attempt;
27665
+ const agent = agentGetFn(ctx.config.autoMode.defaultAgent);
27666
+ if (!agent) {
27667
+ logger.error("autofix", "Agent not found \u2014 cannot run agent rectification", { storyId: ctx.story.id });
27668
+ throw new Error("AUTOFIX_AGENT_NOT_FOUND");
27669
+ }
27670
+ const modelTier = ctx.story.routing?.modelTier ?? ctx.config.autoMode.escalation.tierOrder[0]?.tier ?? "balanced";
27671
+ const modelDef = resolveModelForAgent(ctx.config.models, ctx.routing.agent ?? ctx.config.autoMode.defaultAgent, modelTier, ctx.config.autoMode.defaultAgent);
27672
+ const rectificationWorkdir = ctx.story.workdir ? join19(ctx.workdir, ctx.story.workdir) : ctx.workdir;
27673
+ await agent.run({
27674
+ prompt,
27675
+ workdir: rectificationWorkdir,
27676
+ modelTier,
27677
+ modelDef,
27678
+ timeoutSeconds: ctx.config.execution.sessionTimeoutSeconds,
27679
+ dangerouslySkipPermissions: resolvePermissions(ctx.config, "rectification").skipPermissions,
27680
+ pipelineStage: "rectification",
27681
+ config: ctx.config,
27682
+ maxInteractionTurns: ctx.config.agent?.maxInteractionTurns,
27683
+ storyId: ctx.story.id,
27684
+ sessionRole: "implementer"
27685
+ });
27686
+ },
27687
+ checkResult: async (attempt, state) => {
27688
+ const passed = await _autofixDeps.recheckReview(ctx);
27689
+ if (passed) {
27690
+ logger.info("autofix", `[OK] Agent rectification succeeded on attempt ${attempt}`, {
27691
+ storyId: ctx.story.id
27692
+ });
27693
+ return true;
27694
+ }
27695
+ const updatedFailed = collectFailedChecks(ctx);
27696
+ if (updatedFailed.length > 0) {
27697
+ state.failedChecks.splice(0, state.failedChecks.length, ...updatedFailed);
27698
+ }
27699
+ return false;
27700
+ },
27701
+ onAttemptFailure: (attempt) => {
27702
+ logger.warn("autofix", `Agent rectification still failing after attempt ${attempt}`, {
27355
27703
  storyId: ctx.story.id
27356
27704
  });
27357
- return true;
27705
+ },
27706
+ onLoopEnd: (state) => {
27707
+ if (state.attempt >= maxAttempts) {
27708
+ logger.warn("autofix", "Agent rectification exhausted", { storyId: ctx.story.id });
27709
+ }
27358
27710
  }
27359
- const updatedFailed = collectFailedChecks(ctx);
27360
- if (updatedFailed.length > 0) {
27361
- failedChecks.splice(0, failedChecks.length, ...updatedFailed);
27711
+ }).catch((error48) => {
27712
+ if (error48 instanceof Error && error48.message === "AUTOFIX_AGENT_NOT_FOUND") {
27713
+ return false;
27362
27714
  }
27363
- logger.warn("autofix", `Agent rectification still failing after attempt ${attempt}`, {
27364
- storyId: ctx.story.id
27365
- });
27366
- }
27367
- logger.warn("autofix", "Agent rectification exhausted", { storyId: ctx.story.id });
27368
- return false;
27715
+ throw error48;
27716
+ });
27369
27717
  }
27370
27718
  var autofixStage, _autofixDeps;
27371
27719
  var init_autofix = __esm(() => {
@@ -27399,7 +27747,7 @@ var init_autofix = __esm(() => {
27399
27747
  const effectiveConfig = ctx.effectiveConfig ?? ctx.config;
27400
27748
  const lintFixCmd = effectiveConfig.quality.commands.lintFix;
27401
27749
  const formatFixCmd = effectiveConfig.quality.commands.formatFix;
27402
- const effectiveWorkdir = ctx.story.workdir ? join18(ctx.workdir, ctx.story.workdir) : ctx.workdir;
27750
+ const effectiveWorkdir = ctx.story.workdir ? join19(ctx.workdir, ctx.story.workdir) : ctx.workdir;
27403
27751
  const failedCheckNames = new Set((reviewResult.checks ?? []).filter((c) => !c.success).map((c) => c.check));
27404
27752
  const hasLintFailure = failedCheckNames.has("lint");
27405
27753
  logger.info("autofix", "Starting autofix", {
@@ -27483,10 +27831,10 @@ var init_autofix = __esm(() => {
27483
27831
 
27484
27832
  // src/execution/progress.ts
27485
27833
  import { appendFile as appendFile2, mkdir } from "fs/promises";
27486
- import { join as join19 } from "path";
27834
+ import { join as join20 } from "path";
27487
27835
  async function appendProgress(featureDir, storyId, status, message) {
27488
27836
  await mkdir(featureDir, { recursive: true });
27489
- const progressPath = join19(featureDir, "progress.txt");
27837
+ const progressPath = join20(featureDir, "progress.txt");
27490
27838
  const timestamp = new Date().toISOString();
27491
27839
  const entry = `[${timestamp}] ${storyId} \u2014 ${status.toUpperCase()} \u2014 ${message}
27492
27840
  `;
@@ -27569,7 +27917,7 @@ function estimateTokens(text) {
27569
27917
 
27570
27918
  // src/constitution/loader.ts
27571
27919
  import { existsSync as existsSync19 } from "fs";
27572
- import { join as join20 } from "path";
27920
+ import { join as join21 } from "path";
27573
27921
  function truncateToTokens(text, maxTokens) {
27574
27922
  const maxChars = maxTokens * 3;
27575
27923
  if (text.length <= maxChars) {
@@ -27591,7 +27939,7 @@ async function loadConstitution(projectDir, config2) {
27591
27939
  }
27592
27940
  let combinedContent = "";
27593
27941
  if (!config2.skipGlobal) {
27594
- const globalPath = join20(globalConfigDir(), config2.path);
27942
+ const globalPath = join21(globalConfigDir(), config2.path);
27595
27943
  if (existsSync19(globalPath)) {
27596
27944
  const validatedPath = validateFilePath(globalPath, globalConfigDir());
27597
27945
  const globalFile = Bun.file(validatedPath);
@@ -27601,7 +27949,7 @@ async function loadConstitution(projectDir, config2) {
27601
27949
  }
27602
27950
  }
27603
27951
  }
27604
- const projectPath = join20(projectDir, config2.path);
27952
+ const projectPath = join21(projectDir, config2.path);
27605
27953
  if (existsSync19(projectPath)) {
27606
27954
  const validatedPath = validateFilePath(projectPath, projectDir);
27607
27955
  const projectFile = Bun.file(validatedPath);
@@ -28629,7 +28977,7 @@ var init_helpers = __esm(() => {
28629
28977
  });
28630
28978
 
28631
28979
  // src/pipeline/stages/context.ts
28632
- import { join as join21 } from "path";
28980
+ import { join as join22 } from "path";
28633
28981
  var contextStage;
28634
28982
  var init_context2 = __esm(() => {
28635
28983
  init_helpers();
@@ -28639,7 +28987,7 @@ var init_context2 = __esm(() => {
28639
28987
  enabled: () => true,
28640
28988
  async execute(ctx) {
28641
28989
  const logger = getLogger();
28642
- const packageWorkdir = ctx.story.workdir ? join21(ctx.workdir, ctx.story.workdir) : undefined;
28990
+ const packageWorkdir = ctx.story.workdir ? join22(ctx.workdir, ctx.story.workdir) : undefined;
28643
28991
  const result = await buildStoryContextFull(ctx.prd, ctx.story, ctx.config, packageWorkdir);
28644
28992
  if (result) {
28645
28993
  ctx.contextMarkdown = result.markdown;
@@ -28773,14 +29121,14 @@ var init_isolation = __esm(() => {
28773
29121
 
28774
29122
  // src/context/greenfield.ts
28775
29123
  import { readdir } from "fs/promises";
28776
- import { join as join22 } from "path";
29124
+ import { join as join23 } from "path";
28777
29125
  async function scanForTestFiles(dir, testPattern, isRootCall = true) {
28778
29126
  const results = [];
28779
29127
  const ignoreDirs = new Set(["node_modules", "dist", "build", ".next", ".git"]);
28780
29128
  try {
28781
29129
  const entries = await readdir(dir, { withFileTypes: true });
28782
29130
  for (const entry of entries) {
28783
- const fullPath = join22(dir, entry.name);
29131
+ const fullPath = join23(dir, entry.name);
28784
29132
  if (entry.isDirectory()) {
28785
29133
  if (ignoreDirs.has(entry.name))
28786
29134
  continue;
@@ -29135,13 +29483,13 @@ function parseTestOutput(output, exitCode) {
29135
29483
 
29136
29484
  // src/verification/runners.ts
29137
29485
  import { existsSync as existsSync20 } from "fs";
29138
- import { join as join23 } from "path";
29486
+ import { join as join24 } from "path";
29139
29487
  async function verifyAssets(workingDirectory, expectedFiles) {
29140
29488
  if (!expectedFiles || expectedFiles.length === 0)
29141
29489
  return { success: true, missingFiles: [] };
29142
29490
  const missingFiles = [];
29143
29491
  for (const file3 of expectedFiles) {
29144
- if (!existsSync20(join23(workingDirectory, file3)))
29492
+ if (!existsSync20(join24(workingDirectory, file3)))
29145
29493
  missingFiles.push(file3);
29146
29494
  }
29147
29495
  if (missingFiles.length > 0) {
@@ -29245,27 +29593,14 @@ function shouldRetryRectification(state, config2) {
29245
29593
  return true;
29246
29594
  }
29247
29595
  function buildEscalationPreamble(attempt, config2) {
29248
- const logger = getSafeLogger();
29249
- const rethinkAt = Math.min(config2.rethinkAtAttempt ?? 2, config2.maxRetries);
29250
- const urgencyAt = Math.min(config2.urgencyAtAttempt ?? 3, config2.maxRetries);
29251
- if (attempt < rethinkAt)
29252
- return "";
29253
- const isUrgent = attempt >= urgencyAt;
29254
- if (isUrgent) {
29255
- logger?.info("rectification", "Progressive prompt escalation: urgency + rethink injected", {
29256
- attempt,
29257
- urgencyAtAttempt: urgencyAt,
29258
- rethinkAtAttempt: rethinkAt,
29259
- maxRetries: config2.maxRetries
29260
- });
29261
- } else {
29262
- logger?.info("rectification", "Progressive prompt escalation: rethink injected", {
29263
- attempt,
29264
- rethinkAtAttempt: rethinkAt,
29265
- maxRetries: config2.maxRetries
29266
- });
29267
- }
29268
- const rethinkSection = `## \u26A0\uFE0F Previous Attempt Did Not Fix the Failures
29596
+ return buildProgressivePromptPreamble({
29597
+ attempt,
29598
+ maxAttempts: config2.maxRetries,
29599
+ rethinkAtAttempt: config2.rethinkAtAttempt,
29600
+ urgencyAtAttempt: config2.urgencyAtAttempt,
29601
+ stage: "rectification",
29602
+ logger: getSafeLogger(),
29603
+ rethinkSection: `## \u26A0\uFE0F Previous Attempt Did Not Fix the Failures
29269
29604
 
29270
29605
  Your previous fix attempt (attempt ${attempt}) did not resolve all failures. **Step back and reconsider your approach.**
29271
29606
 
@@ -29274,14 +29609,14 @@ Your previous fix attempt (attempt ${attempt}) did not resolve all failures. **S
29274
29609
  - Re-read the story context and test failures carefully before making changes.
29275
29610
  - Consider: are there missing edge cases, incorrect assumptions, or a design flaw in the implementation?
29276
29611
 
29277
- `;
29278
- const urgencySection = isUrgent ? `## \uD83D\uDEA8 Final Rectification Attempt Before Model Escalation
29612
+ `,
29613
+ urgencySection: `## \uD83D\uDEA8 Final Rectification Attempt Before Model Escalation
29279
29614
 
29280
29615
  This is attempt ${attempt} \u2014 if the tests still fail after this, the task will escalate to a stronger model tier.
29281
29616
  A **completely different approach** is required. Do not repeat what you have already tried.
29282
29617
 
29283
- ` : "";
29284
- return `${urgencySection}${rethinkSection}`;
29618
+ `
29619
+ });
29285
29620
  }
29286
29621
  function createRectificationPrompt(failures, story, config2, attempt) {
29287
29622
  const maxChars = config2?.maxFailureSummaryChars ?? 2000;
@@ -29541,77 +29876,112 @@ async function runRectificationLoop(story, config2, workdir, agent, implementerT
29541
29876
  storyId: story.id,
29542
29877
  sessionName: rectificationSessionName
29543
29878
  });
29544
- while (_rectificationGateDeps.shouldRetryRectification(rectificationState, rectificationConfig)) {
29545
- rectificationState.attempt++;
29546
- const isLastAttempt = rectificationState.attempt >= rectificationConfig.maxRetries;
29547
- logger.info("tdd", `-> Implementer rectification attempt ${rectificationState.attempt}/${rectificationConfig.maxRetries}`, { storyId: story.id, currentFailures: rectificationState.currentFailures });
29548
- const rectificationPrompt = buildImplementerRectificationPrompt(testSummary.failures, story, contextMarkdown, rectificationConfig);
29549
- const rectifyBeforeRef = await captureGitRef(workdir) ?? "HEAD";
29550
- const rectifyResult = await agent.run({
29551
- prompt: rectificationPrompt,
29552
- workdir,
29553
- modelTier: implementerTier,
29554
- modelDef: resolveModelForAgent(config2.models, story.routing?.agent ?? config2.autoMode.defaultAgent, implementerTier, config2.autoMode.defaultAgent),
29555
- timeoutSeconds: config2.execution.sessionTimeoutSeconds,
29556
- dangerouslySkipPermissions: resolvePermissions(config2, "rectification").skipPermissions,
29557
- pipelineStage: "rectification",
29558
- config: config2,
29559
- maxInteractionTurns: config2.agent?.maxInteractionTurns,
29560
- featureName,
29879
+ const loopState = {
29880
+ ...rectificationState,
29881
+ isolationPassed: true
29882
+ };
29883
+ const fixed = await runSharedRectificationLoop({
29884
+ stage: "tdd",
29885
+ storyId: story.id,
29886
+ maxAttempts: rectificationConfig.maxRetries,
29887
+ state: loopState,
29888
+ logger,
29889
+ startMessage: "Full suite gate detected regressions",
29890
+ startData: {
29561
29891
  storyId: story.id,
29562
- sessionRole: "implementer",
29563
- acpSessionName: rectificationSessionName,
29564
- keepSessionOpen: !isLastAttempt
29565
- });
29566
- if (!rectifyResult.success && rectifyResult.pid) {
29567
- await cleanupProcessTree(rectifyResult.pid);
29568
- }
29569
- if (rectifyResult.success) {
29570
- logger.info("tdd", "Rectification agent session complete", {
29571
- storyId: story.id,
29572
- attempt: rectificationState.attempt,
29573
- cost: rectifyResult.estimatedCost
29574
- });
29575
- } else {
29576
- logger.warn("tdd", "Rectification agent session failed", {
29892
+ failedTests: testSummary.failed,
29893
+ passedTests: testSummary.passed
29894
+ },
29895
+ attemptMessage: (attempt) => `-> Implementer rectification attempt ${attempt}/${rectificationConfig.maxRetries}`,
29896
+ attemptData: (state) => ({
29897
+ storyId: story.id,
29898
+ currentFailures: state.currentFailures
29899
+ }),
29900
+ canContinue: (state) => state.isolationPassed && _rectificationGateDeps.shouldRetryRectification(state, rectificationConfig),
29901
+ buildPrompt: () => buildImplementerRectificationPrompt(testSummary.failures, story, contextMarkdown, rectificationConfig),
29902
+ runAttempt: async (attempt, rectificationPrompt) => {
29903
+ const isLastAttempt = attempt >= rectificationConfig.maxRetries;
29904
+ const rectifyBeforeRef = await captureGitRef(workdir) ?? "HEAD";
29905
+ const rectifyResult = await agent.run({
29906
+ prompt: rectificationPrompt,
29907
+ workdir,
29908
+ modelTier: implementerTier,
29909
+ modelDef: resolveModelForAgent(config2.models, story.routing?.agent ?? config2.autoMode.defaultAgent, implementerTier, config2.autoMode.defaultAgent),
29910
+ timeoutSeconds: config2.execution.sessionTimeoutSeconds,
29911
+ dangerouslySkipPermissions: resolvePermissions(config2, "rectification").skipPermissions,
29912
+ pipelineStage: "rectification",
29913
+ config: config2,
29914
+ maxInteractionTurns: config2.agent?.maxInteractionTurns,
29915
+ featureName,
29577
29916
  storyId: story.id,
29578
- attempt: rectificationState.attempt,
29579
- exitCode: rectifyResult.exitCode
29917
+ sessionRole: "implementer",
29918
+ acpSessionName: rectificationSessionName,
29919
+ keepSessionOpen: !isLastAttempt
29580
29920
  });
29581
- }
29582
- await autoCommitIfDirty(workdir, "tdd", "rectification", story.id);
29583
- const rectifyIsolation = lite ? undefined : await verifyImplementerIsolation(workdir, rectifyBeforeRef);
29584
- if (rectifyIsolation && !rectifyIsolation.passed) {
29585
- logger.error("tdd", "Rectification violated isolation", {
29586
- storyId: story.id,
29587
- attempt: rectificationState.attempt,
29588
- violations: rectifyIsolation.violations
29921
+ if (!rectifyResult.success && rectifyResult.pid) {
29922
+ await cleanupProcessTree(rectifyResult.pid);
29923
+ }
29924
+ if (rectifyResult.success) {
29925
+ logger.info("tdd", "Rectification agent session complete", {
29926
+ storyId: story.id,
29927
+ attempt,
29928
+ cost: rectifyResult.estimatedCost
29929
+ });
29930
+ } else {
29931
+ logger.warn("tdd", "Rectification agent session failed", {
29932
+ storyId: story.id,
29933
+ attempt,
29934
+ exitCode: rectifyResult.exitCode
29935
+ });
29936
+ }
29937
+ await autoCommitIfDirty(workdir, "tdd", "rectification", story.id);
29938
+ const rectifyIsolation = lite ? undefined : await verifyImplementerIsolation(workdir, rectifyBeforeRef);
29939
+ if (rectifyIsolation && !rectifyIsolation.passed) {
29940
+ loopState.isolationPassed = false;
29941
+ logger.error("tdd", "Rectification violated isolation", {
29942
+ storyId: story.id,
29943
+ attempt,
29944
+ violations: rectifyIsolation.violations
29945
+ });
29946
+ }
29947
+ },
29948
+ checkResult: async (attempt, state) => {
29949
+ if (!state.isolationPassed) {
29950
+ return false;
29951
+ }
29952
+ const retryFullSuite = await _rectificationGateDeps.executeWithTimeout(testCmd, fullSuiteTimeout, undefined, {
29953
+ cwd: workdir
29589
29954
  });
29590
- break;
29591
- }
29592
- const retryFullSuite = await _rectificationGateDeps.executeWithTimeout(testCmd, fullSuiteTimeout, undefined, {
29593
- cwd: workdir
29594
- });
29595
- const retrySuitePassed = retryFullSuite.success && retryFullSuite.exitCode === 0;
29596
- if (retrySuitePassed) {
29597
- logger.info("tdd", "Full suite gate passed after rectification!", {
29955
+ const retrySuitePassed = retryFullSuite.success && retryFullSuite.exitCode === 0;
29956
+ if (retrySuitePassed) {
29957
+ logger.info("tdd", "Full suite gate passed after rectification!", {
29958
+ storyId: story.id,
29959
+ attempt
29960
+ });
29961
+ return true;
29962
+ }
29963
+ if (retryFullSuite.output) {
29964
+ const newTestSummary = _rectificationGateDeps.parseBunTestOutput(retryFullSuite.output);
29965
+ state.currentFailures = newTestSummary.failed;
29966
+ testSummary.failures = newTestSummary.failures;
29967
+ testSummary.failed = newTestSummary.failed;
29968
+ testSummary.passed = newTestSummary.passed;
29969
+ }
29970
+ return false;
29971
+ },
29972
+ onAttemptFailure: (attempt, state) => {
29973
+ if (!state.isolationPassed) {
29974
+ return;
29975
+ }
29976
+ logger.warn("tdd", "Full suite still failing after rectification attempt", {
29598
29977
  storyId: story.id,
29599
- attempt: rectificationState.attempt
29978
+ attempt,
29979
+ remainingFailures: state.currentFailures
29600
29980
  });
29601
- return true;
29602
29981
  }
29603
- if (retryFullSuite.output) {
29604
- const newTestSummary = _rectificationGateDeps.parseBunTestOutput(retryFullSuite.output);
29605
- rectificationState.currentFailures = newTestSummary.failed;
29606
- testSummary.failures = newTestSummary.failures;
29607
- testSummary.failed = newTestSummary.failed;
29608
- testSummary.passed = newTestSummary.passed;
29609
- }
29610
- logger.warn("tdd", "Full suite still failing after rectification attempt", {
29611
- storyId: story.id,
29612
- attempt: rectificationState.attempt,
29613
- remainingFailures: rectificationState.currentFailures
29614
- });
29982
+ });
29983
+ if (fixed) {
29984
+ return true;
29615
29985
  }
29616
29986
  const finalFullSuite = await _rectificationGateDeps.executeWithTimeout(testCmd, fullSuiteTimeout, undefined, {
29617
29987
  cwd: workdir
@@ -30035,13 +30405,13 @@ var exports_loader = {};
30035
30405
  __export(exports_loader, {
30036
30406
  loadOverride: () => loadOverride
30037
30407
  });
30038
- import { join as join24 } from "path";
30408
+ import { join as join25 } from "path";
30039
30409
  async function loadOverride(role, workdir, config2) {
30040
30410
  const overridePath = config2.prompts?.overrides?.[role];
30041
30411
  if (!overridePath) {
30042
30412
  return null;
30043
30413
  }
30044
- const absolutePath = join24(workdir, overridePath);
30414
+ const absolutePath = join25(workdir, overridePath);
30045
30415
  const file3 = Bun.file(absolutePath);
30046
30416
  if (!await file3.exists()) {
30047
30417
  return null;
@@ -30900,11 +31270,11 @@ var init_tdd = __esm(() => {
30900
31270
 
30901
31271
  // src/pipeline/stages/execution.ts
30902
31272
  import { existsSync as existsSync21 } from "fs";
30903
- import { join as join25 } from "path";
31273
+ import { join as join26 } from "path";
30904
31274
  function resolveStoryWorkdir(repoRoot, storyWorkdir) {
30905
31275
  if (!storyWorkdir)
30906
31276
  return repoRoot;
30907
- const resolved = join25(repoRoot, storyWorkdir);
31277
+ const resolved = join26(repoRoot, storyWorkdir);
30908
31278
  if (!existsSync21(resolved)) {
30909
31279
  throw new Error(`[execution] story.workdir "${storyWorkdir}" does not exist at "${resolved}"`);
30910
31280
  }
@@ -31609,167 +31979,202 @@ async function runRectificationLoop2(opts) {
31609
31979
  currentFailures: testSummary.failed,
31610
31980
  lastExitCode: 1
31611
31981
  };
31612
- logger?.info("rectification", `Starting ${label} loop`, {
31982
+ return runSharedRectificationLoop({
31983
+ stage: "rectification",
31613
31984
  storyId: story.id,
31614
- initialFailures: rectificationState.initialFailures,
31615
- maxRetries: rectificationConfig.maxRetries
31616
- });
31617
- while (shouldRetryRectification(rectificationState, rectificationConfig)) {
31618
- rectificationState.attempt++;
31619
- logger?.info("rectification", `${label} attempt ${rectificationState.attempt}/${rectificationConfig.maxRetries}`, {
31985
+ maxAttempts: rectificationConfig.maxRetries,
31986
+ state: rectificationState,
31987
+ logger,
31988
+ startMessage: `Starting ${label} loop`,
31989
+ startData: {
31990
+ storyId: story.id,
31991
+ initialFailures: rectificationState.initialFailures,
31992
+ maxRetries: rectificationConfig.maxRetries
31993
+ },
31994
+ attemptMessage: (attempt) => `${label} attempt ${attempt}/${rectificationConfig.maxRetries}`,
31995
+ attemptData: () => ({
31620
31996
  storyId: story.id,
31621
31997
  currentFailures: rectificationState.currentFailures
31622
- });
31623
- let diagnosisPrefix = null;
31624
- const debateStageConfig = config2.debate?.stages?.rectification;
31625
- if (debateStageConfig?.enabled) {
31626
- const failureSummary = formatFailureSummary(testSummary.failures);
31627
- const diagnosisPrompt = `Analyze the following test failures and identify the root cause:
31998
+ }),
31999
+ canContinue: (state) => shouldRetryRectification(state, rectificationConfig),
32000
+ buildPrompt: async (attempt) => {
32001
+ let diagnosisPrefix = null;
32002
+ const debateStageConfig = config2.debate?.stages?.rectification;
32003
+ if (debateStageConfig?.enabled) {
32004
+ const failureSummary = formatFailureSummary(testSummary.failures);
32005
+ const diagnosisPrompt = `Analyze the following test failures and identify the root cause:
31628
32006
 
31629
32007
  ${failureSummary}`;
31630
- try {
31631
- const debateResult = await _rectificationDeps.runDebate(story.id, debateStageConfig, diagnosisPrompt, config2);
31632
- if (debateResult.totalCostUsd > 0 && story.routing) {
31633
- story.routing.estimatedCost = (story.routing.estimatedCost ?? 0) + debateResult.totalCostUsd;
31634
- }
31635
- if (debateResult.output !== null) {
31636
- diagnosisPrefix = `## Root Cause Analysis
32008
+ try {
32009
+ const debateResult = await _rectificationDeps.runDebate(story.id, debateStageConfig, diagnosisPrompt, config2);
32010
+ if (debateResult.totalCostUsd > 0 && story.routing) {
32011
+ story.routing.estimatedCost = (story.routing.estimatedCost ?? 0) + debateResult.totalCostUsd;
32012
+ }
32013
+ if (debateResult.output !== null) {
32014
+ diagnosisPrefix = `## Root Cause Analysis
31637
32015
 
31638
32016
  ${debateResult.output}`;
31639
- } else {
31640
- logger?.info("rectification", "debate diagnosis fallback \u2014 all debaters failed", {
32017
+ } else {
32018
+ logger?.info("rectification", "debate diagnosis fallback \u2014 all debaters failed", {
32019
+ storyId: story.id,
32020
+ attempt,
32021
+ event: "fallback"
32022
+ });
32023
+ }
32024
+ } catch (_error) {
32025
+ logger?.info("rectification", "debate diagnosis fallback \u2014 debate threw error", {
31641
32026
  storyId: story.id,
31642
- attempt: rectificationState.attempt,
32027
+ attempt,
31643
32028
  event: "fallback"
31644
32029
  });
31645
32030
  }
31646
- } catch (err) {
31647
- logger?.info("rectification", "debate diagnosis fallback \u2014 debate threw error", {
31648
- storyId: story.id,
31649
- attempt: rectificationState.attempt,
31650
- event: "fallback"
31651
- });
31652
32031
  }
31653
- }
31654
- let rectificationPrompt = createRectificationPrompt(testSummary.failures, story, rectificationConfig, rectificationState.attempt);
31655
- if (diagnosisPrefix)
31656
- rectificationPrompt = `${diagnosisPrefix}
32032
+ let rectificationPrompt = createRectificationPrompt(testSummary.failures, story, rectificationConfig, attempt);
32033
+ if (diagnosisPrefix) {
32034
+ rectificationPrompt = `${diagnosisPrefix}
31657
32035
 
31658
32036
  ${rectificationPrompt}`;
31659
- if (promptPrefix)
31660
- rectificationPrompt = `${promptPrefix}
32037
+ }
32038
+ if (promptPrefix) {
32039
+ rectificationPrompt = `${promptPrefix}
31661
32040
 
31662
32041
  ${rectificationPrompt}`;
31663
- const agent = agentGetFn ? agentGetFn(config2.autoMode.defaultAgent) : _rectificationDeps.getAgent(config2.autoMode.defaultAgent, config2);
31664
- if (!agent) {
31665
- logger?.error("rectification", "Agent not found, cannot retry");
31666
- break;
31667
- }
31668
- const complexity = story.routing?.complexity ?? "medium";
31669
- const modelTier = config2.autoMode.complexityRouting?.[complexity] || config2.autoMode.escalation.tierOrder[0]?.tier || "balanced";
31670
- const modelDef = resolveModelForAgent(config2.models, story.routing?.agent ?? config2.autoMode.defaultAgent, modelTier, config2.autoMode.defaultAgent);
31671
- const agentResult = await agent.run({
31672
- prompt: rectificationPrompt,
31673
- workdir,
31674
- modelTier,
31675
- modelDef,
31676
- timeoutSeconds: config2.execution.sessionTimeoutSeconds,
31677
- dangerouslySkipPermissions: resolvePermissions(config2, "rectification").skipPermissions,
31678
- pipelineStage: "rectification",
31679
- config: config2,
31680
- maxInteractionTurns: config2.agent?.maxInteractionTurns,
31681
- featureName,
31682
- storyId: story.id,
31683
- sessionRole: "implementer"
31684
- });
31685
- if (agentResult.success) {
31686
- logger?.info("rectification", `Agent ${label} session complete`, {
32042
+ }
32043
+ return rectificationPrompt;
32044
+ },
32045
+ runAttempt: async (attempt, rectificationPrompt) => {
32046
+ const agent = agentGetFn ? agentGetFn(config2.autoMode.defaultAgent) : _rectificationDeps.getAgent(config2.autoMode.defaultAgent, config2);
32047
+ if (!agent) {
32048
+ logger?.error("rectification", "Agent not found, cannot retry");
32049
+ throw new Error("RECTIFICATION_AGENT_NOT_FOUND");
32050
+ }
32051
+ const complexity = story.routing?.complexity ?? "medium";
32052
+ const modelTier = config2.autoMode.complexityRouting?.[complexity] || config2.autoMode.escalation.tierOrder[0]?.tier || "balanced";
32053
+ const modelDef = resolveModelForAgent(config2.models, story.routing?.agent ?? config2.autoMode.defaultAgent, modelTier, config2.autoMode.defaultAgent);
32054
+ const agentResult = await agent.run({
32055
+ prompt: rectificationPrompt,
32056
+ workdir,
32057
+ modelTier,
32058
+ modelDef,
32059
+ timeoutSeconds: config2.execution.sessionTimeoutSeconds,
32060
+ dangerouslySkipPermissions: resolvePermissions(config2, "rectification").skipPermissions,
32061
+ pipelineStage: "rectification",
32062
+ config: config2,
32063
+ maxInteractionTurns: config2.agent?.maxInteractionTurns,
32064
+ featureName,
31687
32065
  storyId: story.id,
31688
- attempt: rectificationState.attempt,
31689
- cost: agentResult.estimatedCost
32066
+ sessionRole: "implementer"
31690
32067
  });
31691
- } else {
31692
- logger?.warn("rectification", `Agent ${label} session failed`, {
31693
- storyId: story.id,
31694
- attempt: rectificationState.attempt,
31695
- exitCode: agentResult.exitCode
32068
+ if (agentResult.success) {
32069
+ logger?.info("rectification", `Agent ${label} session complete`, {
32070
+ storyId: story.id,
32071
+ attempt,
32072
+ cost: agentResult.estimatedCost
32073
+ });
32074
+ } else {
32075
+ logger?.warn("rectification", `Agent ${label} session failed`, {
32076
+ storyId: story.id,
32077
+ attempt,
32078
+ exitCode: agentResult.exitCode
32079
+ });
32080
+ }
32081
+ },
32082
+ checkResult: async (attempt, state) => {
32083
+ const retryVerification = await _rectificationDeps.runVerification({
32084
+ workdir,
32085
+ expectedFiles: getExpectedFiles(story),
32086
+ command: testCommand,
32087
+ timeoutSeconds,
32088
+ forceExit: config2.quality.forceExit,
32089
+ detectOpenHandles: config2.quality.detectOpenHandles,
32090
+ detectOpenHandlesRetries: config2.quality.detectOpenHandlesRetries,
32091
+ timeoutRetryCount: 0,
32092
+ gracePeriodMs: config2.quality.gracePeriodMs,
32093
+ drainTimeoutMs: config2.quality.drainTimeoutMs,
32094
+ shell: config2.quality.shell,
32095
+ stripEnvVars: config2.quality.stripEnvVars
31696
32096
  });
31697
- }
31698
- const retryVerification = await _rectificationDeps.runVerification({
31699
- workdir,
31700
- expectedFiles: getExpectedFiles(story),
31701
- command: testCommand,
31702
- timeoutSeconds,
31703
- forceExit: config2.quality.forceExit,
31704
- detectOpenHandles: config2.quality.detectOpenHandles,
31705
- detectOpenHandlesRetries: config2.quality.detectOpenHandlesRetries,
31706
- timeoutRetryCount: 0,
31707
- gracePeriodMs: config2.quality.gracePeriodMs,
31708
- drainTimeoutMs: config2.quality.drainTimeoutMs,
31709
- shell: config2.quality.shell,
31710
- stripEnvVars: config2.quality.stripEnvVars
31711
- });
31712
- if (retryVerification.success) {
31713
- logger?.info("rectification", `[OK] ${label} succeeded!`, {
32097
+ if (retryVerification.success) {
32098
+ logger?.info("rectification", `[OK] ${label} succeeded!`, {
32099
+ storyId: story.id,
32100
+ attempt,
32101
+ initialFailures: state.initialFailures
32102
+ });
32103
+ return true;
32104
+ }
32105
+ if (retryVerification.output) {
32106
+ const newTestSummary = parseBunTestOutput(retryVerification.output);
32107
+ state.currentFailures = newTestSummary.failed;
32108
+ state.lastExitCode = retryVerification.status === "SUCCESS" ? 0 : 1;
32109
+ testSummary.failures = newTestSummary.failures;
32110
+ testSummary.failed = newTestSummary.failed;
32111
+ testSummary.passed = newTestSummary.passed;
32112
+ if (newTestSummary.failed === 0) {
32113
+ state.lastExitCode = 0;
32114
+ logger?.info("rectification", `[OK] ${label} succeeded after parsing retry output`, {
32115
+ storyId: story.id,
32116
+ attempt,
32117
+ initialFailures: state.initialFailures
32118
+ });
32119
+ return true;
32120
+ }
32121
+ }
32122
+ return false;
32123
+ },
32124
+ onAttemptFailure: (attempt, state) => {
32125
+ const failingTests = testSummary.failures.slice(0, 10).map((failure) => failure.testName);
32126
+ const logData = {
31714
32127
  storyId: story.id,
31715
- attempt: rectificationState.attempt,
31716
- initialFailures: rectificationState.initialFailures
31717
- });
31718
- return true;
31719
- }
31720
- if (retryVerification.output) {
31721
- const newTestSummary = parseBunTestOutput(retryVerification.output);
31722
- rectificationState.currentFailures = newTestSummary.failed;
31723
- rectificationState.lastExitCode = retryVerification.status === "SUCCESS" ? 0 : 1;
31724
- testSummary.failures = newTestSummary.failures;
31725
- testSummary.failed = newTestSummary.failed;
31726
- testSummary.passed = newTestSummary.passed;
31727
- }
31728
- const failingTests = testSummary.failures.slice(0, 10).map((f) => f.testName);
31729
- const logData = {
31730
- storyId: story.id,
31731
- attempt: rectificationState.attempt,
31732
- remainingFailures: rectificationState.currentFailures,
31733
- failingTests
31734
- };
31735
- if (testSummary.failures.length > 10 || testSummary.failures.length === 0 && testSummary.failed > 0) {
31736
- logData.totalFailingTests = testSummary.failed;
31737
- }
31738
- logger?.warn("rectification", `${label} still failing after attempt`, logData);
31739
- }
31740
- if (rectificationState.attempt >= rectificationConfig.maxRetries) {
31741
- logger?.warn("rectification", `${label} exhausted max retries`, {
31742
- storyId: story.id,
31743
- attempts: rectificationState.attempt,
31744
- remainingFailures: rectificationState.currentFailures
31745
- });
31746
- } else if (rectificationState.currentFailures > rectificationState.initialFailures) {
31747
- logger?.warn("rectification", `${label} aborted due to further regression`, {
31748
- storyId: story.id,
31749
- initialFailures: rectificationState.initialFailures,
31750
- currentFailures: rectificationState.currentFailures
31751
- });
31752
- }
31753
- const shouldEscalate = rectificationConfig.escalateOnExhaustion !== false && config2.autoMode?.escalation?.enabled === true && rectificationState.attempt >= rectificationConfig.maxRetries && rectificationState.currentFailures > 0;
31754
- if (shouldEscalate) {
31755
- const complexity = story.routing?.complexity ?? "medium";
31756
- const currentTier = config2.autoMode.complexityRouting?.[complexity] || config2.autoMode.escalation.tierOrder[0]?.tier || "balanced";
31757
- const tierOrder = config2.autoMode.escalation.tierOrder;
31758
- const escalationResult = _rectificationDeps.escalateTier(currentTier, tierOrder);
31759
- const escalatedTier = escalationResult?.tier ?? null;
31760
- const escalatedAgent = escalationResult?.agent;
31761
- if (escalatedTier !== null) {
32128
+ attempt,
32129
+ remainingFailures: state.currentFailures,
32130
+ failingTests
32131
+ };
32132
+ if (testSummary.failures.length > 10 || testSummary.failures.length === 0 && testSummary.failed > 0) {
32133
+ logData.totalFailingTests = testSummary.failed;
32134
+ }
32135
+ logger?.warn("rectification", `${label} still failing after attempt`, logData);
32136
+ },
32137
+ onLoopEnd: (state) => {
32138
+ if (state.attempt >= rectificationConfig.maxRetries) {
32139
+ logger?.warn("rectification", `${label} exhausted max retries`, {
32140
+ storyId: story.id,
32141
+ attempts: state.attempt,
32142
+ remainingFailures: state.currentFailures
32143
+ });
32144
+ } else if (state.currentFailures > state.initialFailures) {
32145
+ logger?.warn("rectification", `${label} aborted due to further regression`, {
32146
+ storyId: story.id,
32147
+ initialFailures: state.initialFailures,
32148
+ currentFailures: state.currentFailures
32149
+ });
32150
+ }
32151
+ },
32152
+ onExhausted: async (state) => {
32153
+ const shouldEscalate = rectificationConfig.escalateOnExhaustion !== false && config2.autoMode?.escalation?.enabled === true && state.currentFailures > 0;
32154
+ if (!shouldEscalate) {
32155
+ return false;
32156
+ }
32157
+ const complexity = story.routing?.complexity ?? "medium";
32158
+ const currentTier = config2.autoMode.complexityRouting?.[complexity] || config2.autoMode.escalation.tierOrder[0]?.tier || "balanced";
32159
+ const tierOrder = config2.autoMode.escalation.tierOrder;
32160
+ const escalationResult = _rectificationDeps.escalateTier(currentTier, tierOrder);
32161
+ const escalatedTier = escalationResult?.tier ?? null;
32162
+ const escalatedAgent = escalationResult?.agent;
32163
+ if (escalatedTier === null) {
32164
+ return false;
32165
+ }
31762
32166
  const agentName = escalatedAgent ?? story.routing?.agent ?? config2.autoMode.defaultAgent;
31763
32167
  const agent = agentGetFn ? agentGetFn(agentName) : _rectificationDeps.getAgent(agentName, config2);
31764
32168
  if (!agent) {
31765
32169
  return false;
31766
32170
  }
31767
32171
  const escalatedModelDef = resolveModelForAgent(config2.models, agentName, escalatedTier, config2.autoMode.defaultAgent);
31768
- let escalationPrompt = createEscalatedRectificationPrompt(testSummary.failures, story, rectificationState.attempt, currentTier, escalatedTier, rectificationConfig);
31769
- if (promptPrefix)
32172
+ let escalationPrompt = createEscalatedRectificationPrompt(testSummary.failures, story, state.attempt, currentTier, escalatedTier, rectificationConfig);
32173
+ if (promptPrefix) {
31770
32174
  escalationPrompt = `${promptPrefix}
31771
32175
 
31772
32176
  ${escalationPrompt}`;
32177
+ }
31773
32178
  const escalationRunResult = await agent.run({
31774
32179
  prompt: escalationPrompt,
31775
32180
  workdir,
@@ -31812,9 +32217,14 @@ ${escalationPrompt}`;
31812
32217
  return true;
31813
32218
  }
31814
32219
  logger?.warn("rectification", "escalated rectification also failed", { storyId: story.id, escalatedTier });
32220
+ return false;
31815
32221
  }
31816
- }
31817
- return false;
32222
+ }).catch((error48) => {
32223
+ if (error48 instanceof Error && error48.message === "RECTIFICATION_AGENT_NOT_FOUND") {
32224
+ return false;
32225
+ }
32226
+ throw error48;
32227
+ });
31818
32228
  }
31819
32229
  var _rectificationDeps;
31820
32230
  var init_rectification_loop = __esm(() => {
@@ -32475,7 +32885,7 @@ var init_regression2 = __esm(() => {
32475
32885
  });
32476
32886
 
32477
32887
  // src/pipeline/stages/routing.ts
32478
- import { join as join26 } from "path";
32888
+ import { join as join27 } from "path";
32479
32889
  var routingStage, _routingDeps;
32480
32890
  var init_routing2 = __esm(() => {
32481
32891
  init_registry();
@@ -32512,7 +32922,7 @@ var init_routing2 = __esm(() => {
32512
32922
  }
32513
32923
  const greenfieldDetectionEnabled = effectiveConfig.tdd.greenfieldDetection ?? true;
32514
32924
  if (greenfieldDetectionEnabled && routing.testStrategy.startsWith("three-session-tdd")) {
32515
- const greenfieldScanDir = ctx.story.workdir ? join26(ctx.workdir, ctx.story.workdir) : ctx.workdir;
32925
+ const greenfieldScanDir = ctx.story.workdir ? join27(ctx.workdir, ctx.story.workdir) : ctx.workdir;
32516
32926
  const isGreenfield = await _routingDeps.isGreenfieldStory(ctx.story, greenfieldScanDir);
32517
32927
  if (isGreenfield) {
32518
32928
  logger.info("routing", "Greenfield detected \u2014 forcing test-after strategy", {
@@ -32564,7 +32974,7 @@ var init_crash_detector = __esm(() => {
32564
32974
  });
32565
32975
 
32566
32976
  // src/pipeline/stages/verify.ts
32567
- import { join as join27 } from "path";
32977
+ import { join as join28 } from "path";
32568
32978
  function coerceSmartTestRunner(val) {
32569
32979
  if (val === undefined || val === true)
32570
32980
  return DEFAULT_SMART_RUNNER_CONFIG2;
@@ -32580,7 +32990,7 @@ function buildScopedCommand2(testFiles, baseCommand, testScopedTemplate) {
32580
32990
  }
32581
32991
  async function readPackageName(dir) {
32582
32992
  try {
32583
- const content = await Bun.file(join27(dir, "package.json")).json();
32993
+ const content = await Bun.file(join28(dir, "package.json")).json();
32584
32994
  return typeof content.name === "string" ? content.name : null;
32585
32995
  } catch {
32586
32996
  return null;
@@ -32625,7 +33035,7 @@ var init_verify = __esm(() => {
32625
33035
  return { action: "continue" };
32626
33036
  }
32627
33037
  logger.info("verify", "Running verification", { storyId: ctx.story.id });
32628
- const effectiveWorkdir = ctx.story.workdir ? join27(ctx.workdir, ctx.story.workdir) : ctx.workdir;
33038
+ const effectiveWorkdir = ctx.story.workdir ? join28(ctx.workdir, ctx.story.workdir) : ctx.workdir;
32629
33039
  let effectiveCommand = testCommand;
32630
33040
  let isFullSuite = true;
32631
33041
  const smartRunnerConfig = coerceSmartTestRunner(effectiveConfig.execution.smartTestRunner);
@@ -32843,7 +33253,7 @@ __export(exports_init_context, {
32843
33253
  });
32844
33254
  import { existsSync as existsSync24 } from "fs";
32845
33255
  import { mkdir as mkdir2 } from "fs/promises";
32846
- import { basename as basename3, join as join31 } from "path";
33256
+ import { basename as basename3, join as join32 } from "path";
32847
33257
  async function findFiles(dir, maxFiles = 200) {
32848
33258
  try {
32849
33259
  const proc = Bun.spawnSync([
@@ -32871,7 +33281,7 @@ async function findFiles(dir, maxFiles = 200) {
32871
33281
  return [];
32872
33282
  }
32873
33283
  async function readPackageManifest(projectRoot) {
32874
- const packageJsonPath = join31(projectRoot, "package.json");
33284
+ const packageJsonPath = join32(projectRoot, "package.json");
32875
33285
  if (!existsSync24(packageJsonPath)) {
32876
33286
  return null;
32877
33287
  }
@@ -32889,7 +33299,7 @@ async function readPackageManifest(projectRoot) {
32889
33299
  }
32890
33300
  }
32891
33301
  async function readReadmeSnippet(projectRoot) {
32892
- const readmePath = join31(projectRoot, "README.md");
33302
+ const readmePath = join32(projectRoot, "README.md");
32893
33303
  if (!existsSync24(readmePath)) {
32894
33304
  return null;
32895
33305
  }
@@ -32907,7 +33317,7 @@ async function detectEntryPoints(projectRoot) {
32907
33317
  const candidates = ["src/index.ts", "src/main.ts", "main.go", "src/lib.rs"];
32908
33318
  const found = [];
32909
33319
  for (const candidate of candidates) {
32910
- const path12 = join31(projectRoot, candidate);
33320
+ const path12 = join32(projectRoot, candidate);
32911
33321
  if (existsSync24(path12)) {
32912
33322
  found.push(candidate);
32913
33323
  }
@@ -32918,7 +33328,7 @@ async function detectConfigFiles(projectRoot) {
32918
33328
  const candidates = ["tsconfig.json", "biome.json", "turbo.json", ".env.example"];
32919
33329
  const found = [];
32920
33330
  for (const candidate of candidates) {
32921
- const path12 = join31(projectRoot, candidate);
33331
+ const path12 = join32(projectRoot, candidate);
32922
33332
  if (existsSync24(path12)) {
32923
33333
  found.push(candidate);
32924
33334
  }
@@ -33079,8 +33489,8 @@ function generatePackageContextTemplate(packagePath) {
33079
33489
  }
33080
33490
  async function initPackage(repoRoot, packagePath, force = false) {
33081
33491
  const logger = getLogger();
33082
- const naxDir = join31(repoRoot, ".nax", "mono", packagePath);
33083
- const contextPath = join31(naxDir, "context.md");
33492
+ const naxDir = join32(repoRoot, ".nax", "mono", packagePath);
33493
+ const contextPath = join32(naxDir, "context.md");
33084
33494
  if (existsSync24(contextPath) && !force) {
33085
33495
  logger.info("init", "Package context.md already exists (use --force to overwrite)", { path: contextPath });
33086
33496
  return;
@@ -33094,8 +33504,8 @@ async function initPackage(repoRoot, packagePath, force = false) {
33094
33504
  }
33095
33505
  async function initContext(projectRoot, options = {}) {
33096
33506
  const logger = getLogger();
33097
- const naxDir = join31(projectRoot, ".nax");
33098
- const contextPath = join31(naxDir, "context.md");
33507
+ const naxDir = join32(projectRoot, ".nax");
33508
+ const contextPath = join32(naxDir, "context.md");
33099
33509
  if (existsSync24(contextPath) && !options.force) {
33100
33510
  logger.info("init", "context.md already exists, skipping (use --force to overwrite)", { path: contextPath });
33101
33511
  return;
@@ -33125,7 +33535,7 @@ var init_init_context = __esm(() => {
33125
33535
 
33126
33536
  // src/utils/path-security.ts
33127
33537
  import { realpathSync as realpathSync3 } from "fs";
33128
- import { dirname as dirname4, isAbsolute as isAbsolute4, join as join32, normalize as normalize2, resolve as resolve5 } from "path";
33538
+ import { dirname as dirname4, isAbsolute as isAbsolute4, join as join33, normalize as normalize2, resolve as resolve5 } from "path";
33129
33539
  function safeRealpathForComparison(p) {
33130
33540
  try {
33131
33541
  return realpathSync3(p);
@@ -33134,7 +33544,7 @@ function safeRealpathForComparison(p) {
33134
33544
  if (parent === p)
33135
33545
  return normalize2(p);
33136
33546
  const resolvedParent = safeRealpathForComparison(parent);
33137
- return join32(resolvedParent, p.split("/").pop() ?? "");
33547
+ return join33(resolvedParent, p.split("/").pop() ?? "");
33138
33548
  }
33139
33549
  }
33140
33550
  function validateModulePath(modulePath, allowedRoots) {
@@ -33152,7 +33562,7 @@ function validateModulePath(modulePath, allowedRoots) {
33152
33562
  } else {
33153
33563
  for (let i = 0;i < allowedRoots.length; i++) {
33154
33564
  const originalRoot = resolve5(allowedRoots[i]);
33155
- const absoluteInput = resolve5(join32(originalRoot, modulePath));
33565
+ const absoluteInput = resolve5(join33(originalRoot, modulePath));
33156
33566
  const resolved = safeRealpathForComparison(absoluteInput);
33157
33567
  const resolvedRoot = resolvedRoots[i];
33158
33568
  if (resolved.startsWith(`${resolvedRoot}/`) || resolved === resolvedRoot) {
@@ -33688,19 +34098,19 @@ var init_loader4 = __esm(() => {
33688
34098
  });
33689
34099
 
33690
34100
  // src/hooks/runner.ts
33691
- import { join as join46 } from "path";
34101
+ import { join as join48 } from "path";
33692
34102
  async function loadHooksConfig(projectDir, globalDir) {
33693
34103
  let globalHooks = { hooks: {} };
33694
34104
  let projectHooks = { hooks: {} };
33695
34105
  let skipGlobal = false;
33696
- const projectPath = join46(projectDir, "hooks.json");
34106
+ const projectPath = join48(projectDir, "hooks.json");
33697
34107
  const projectData = await loadJsonFile(projectPath, "hooks");
33698
34108
  if (projectData) {
33699
34109
  projectHooks = projectData;
33700
34110
  skipGlobal = projectData.skipGlobal ?? false;
33701
34111
  }
33702
34112
  if (!skipGlobal && globalDir) {
33703
- const globalPath = join46(globalDir, "hooks.json");
34113
+ const globalPath = join48(globalDir, "hooks.json");
33704
34114
  const globalData = await loadJsonFile(globalPath, "hooks");
33705
34115
  if (globalData) {
33706
34116
  globalHooks = globalData;
@@ -34239,7 +34649,7 @@ async function diagnoseAcceptanceFailure(agent, options) {
34239
34649
  reasoning: "diagnosis failed \u2014 falling back to source fix",
34240
34650
  confidence: 0
34241
34651
  };
34242
- } catch (err) {
34652
+ } catch {
34243
34653
  return {
34244
34654
  verdict: "source_bug",
34245
34655
  reasoning: "diagnosis failed \u2014 falling back to source fix",
@@ -34349,11 +34759,12 @@ var init_fix_executor = __esm(() => {
34349
34759
  var exports_acceptance_loop = {};
34350
34760
  __export(exports_acceptance_loop, {
34351
34761
  runAcceptanceLoop: () => runAcceptanceLoop,
34762
+ regenerateAcceptanceTest: () => regenerateAcceptanceTest,
34352
34763
  isTestLevelFailure: () => isTestLevelFailure,
34353
34764
  isStubTestFile: () => isStubTestFile,
34354
34765
  _acceptanceLoopDeps: () => _acceptanceLoopDeps
34355
34766
  });
34356
- import path14, { join as join47 } from "path";
34767
+ import path14, { join as join49 } from "path";
34357
34768
  function isStubTestFile(content) {
34358
34769
  return /expect\s*\(\s*true\s*\)\s*\.\s*toBe\s*\(\s*(?:false|true)\s*\)/.test(content);
34359
34770
  }
@@ -34424,7 +34835,7 @@ async function executeFixStory(ctx, story, prd, iterations) {
34424
34835
  agent: ctx.config.autoMode.defaultAgent,
34425
34836
  iteration: iterations
34426
34837
  }), ctx.workdir);
34427
- const fixEffectiveConfig = story.workdir ? await loadConfigForWorkdir(join47(ctx.workdir, ".nax", "config.json"), story.workdir) : ctx.config;
34838
+ const fixEffectiveConfig = story.workdir ? await loadConfigForWorkdir(join49(ctx.workdir, ".nax", "config.json"), story.workdir) : ctx.config;
34428
34839
  const fixContext = {
34429
34840
  config: ctx.config,
34430
34841
  effectiveConfig: fixEffectiveConfig,
@@ -35237,12 +35648,12 @@ var init_headless_formatter = __esm(() => {
35237
35648
  // src/pipeline/subscribers/events-writer.ts
35238
35649
  import { appendFile as appendFile3, mkdir as mkdir3 } from "fs/promises";
35239
35650
  import { homedir as homedir5 } from "os";
35240
- import { basename as basename6, join as join48 } from "path";
35651
+ import { basename as basename6, join as join50 } from "path";
35241
35652
  function wireEventsWriter(bus, feature, runId, workdir) {
35242
35653
  const logger = getSafeLogger();
35243
35654
  const project = basename6(workdir);
35244
- const eventsDir = join48(homedir5(), ".nax", "events", project);
35245
- const eventsFile = join48(eventsDir, "events.jsonl");
35655
+ const eventsDir = join50(homedir5(), ".nax", "events", project);
35656
+ const eventsFile = join50(eventsDir, "events.jsonl");
35246
35657
  let dirReady = false;
35247
35658
  const write = (line) => {
35248
35659
  return (async () => {
@@ -35423,12 +35834,12 @@ var init_interaction2 = __esm(() => {
35423
35834
  // src/pipeline/subscribers/registry.ts
35424
35835
  import { mkdir as mkdir4, writeFile } from "fs/promises";
35425
35836
  import { homedir as homedir6 } from "os";
35426
- import { basename as basename7, join as join49 } from "path";
35837
+ import { basename as basename7, join as join51 } from "path";
35427
35838
  function wireRegistry(bus, feature, runId, workdir) {
35428
35839
  const logger = getSafeLogger();
35429
35840
  const project = basename7(workdir);
35430
- const runDir = join49(homedir6(), ".nax", "runs", `${project}-${feature}-${runId}`);
35431
- const metaFile = join49(runDir, "meta.json");
35841
+ const runDir = join51(homedir6(), ".nax", "runs", `${project}-${feature}-${runId}`);
35842
+ const metaFile = join51(runDir, "meta.json");
35432
35843
  const unsub = bus.on("run:started", (_ev) => {
35433
35844
  return (async () => {
35434
35845
  try {
@@ -35438,8 +35849,8 @@ function wireRegistry(bus, feature, runId, workdir) {
35438
35849
  project,
35439
35850
  feature,
35440
35851
  workdir,
35441
- statusPath: join49(workdir, ".nax", "features", feature, "status.json"),
35442
- eventsDir: join49(workdir, ".nax", "features", feature, "runs"),
35852
+ statusPath: join51(workdir, ".nax", "features", feature, "status.json"),
35853
+ eventsDir: join51(workdir, ".nax", "features", feature, "runs"),
35443
35854
  registeredAt: new Date().toISOString()
35444
35855
  };
35445
35856
  await writeFile(metaFile, JSON.stringify(meta3, null, 2));
@@ -36091,7 +36502,7 @@ var init_pipeline_result_handler = __esm(() => {
36091
36502
  });
36092
36503
 
36093
36504
  // src/execution/iteration-runner.ts
36094
- import { join as join50 } from "path";
36505
+ import { join as join52 } from "path";
36095
36506
  async function runIteration(ctx, prd, selection, iterations, totalCost, allStoryMetrics) {
36096
36507
  const logger = getSafeLogger();
36097
36508
  const { story, storiesToExecute, routing, isBatchExecution } = selection;
@@ -36126,7 +36537,7 @@ async function runIteration(ctx, prd, selection, iterations, totalCost, allStory
36126
36537
  }
36127
36538
  }
36128
36539
  const accumulatedAttemptCost = (story.priorFailures || []).reduce((sum, f) => sum + (f.cost || 0), 0);
36129
- const effectiveConfig = story.workdir ? await _iterationRunnerDeps.loadConfigForWorkdir(join50(ctx.workdir, ".nax", "config.json"), story.workdir) : ctx.config;
36540
+ const effectiveConfig = story.workdir ? await _iterationRunnerDeps.loadConfigForWorkdir(join52(ctx.workdir, ".nax", "config.json"), story.workdir) : ctx.config;
36130
36541
  const pipelineContext = {
36131
36542
  config: ctx.config,
36132
36543
  effectiveConfig,
@@ -36385,13 +36796,13 @@ __export(exports_manager, {
36385
36796
  });
36386
36797
  import { existsSync as existsSync32, symlinkSync } from "fs";
36387
36798
  import { mkdir as mkdir5 } from "fs/promises";
36388
- import { join as join51 } from "path";
36799
+ import { join as join53 } from "path";
36389
36800
 
36390
36801
  class WorktreeManager {
36391
36802
  async ensureGitExcludes(projectRoot) {
36392
36803
  const logger = getSafeLogger();
36393
- const infoDir = join51(projectRoot, ".git", "info");
36394
- const excludePath = join51(infoDir, "exclude");
36804
+ const infoDir = join53(projectRoot, ".git", "info");
36805
+ const excludePath = join53(infoDir, "exclude");
36395
36806
  try {
36396
36807
  await mkdir5(infoDir, { recursive: true });
36397
36808
  let existing = "";
@@ -36418,7 +36829,7 @@ ${missing.join(`
36418
36829
  }
36419
36830
  async create(projectRoot, storyId) {
36420
36831
  validateStoryId(storyId);
36421
- const worktreePath = join51(projectRoot, ".nax-wt", storyId);
36832
+ const worktreePath = join53(projectRoot, ".nax-wt", storyId);
36422
36833
  const branchName = `nax/${storyId}`;
36423
36834
  try {
36424
36835
  const pruneProc = _managerDeps.spawn(["git", "worktree", "prune"], {
@@ -36459,9 +36870,9 @@ ${missing.join(`
36459
36870
  }
36460
36871
  throw new Error(`Failed to create worktree: ${String(error48)}`);
36461
36872
  }
36462
- const nodeModulesSource = join51(projectRoot, "node_modules");
36873
+ const nodeModulesSource = join53(projectRoot, "node_modules");
36463
36874
  if (existsSync32(nodeModulesSource)) {
36464
- const nodeModulesTarget = join51(worktreePath, "node_modules");
36875
+ const nodeModulesTarget = join53(worktreePath, "node_modules");
36465
36876
  try {
36466
36877
  symlinkSync(nodeModulesSource, nodeModulesTarget, "dir");
36467
36878
  } catch (error48) {
@@ -36469,9 +36880,9 @@ ${missing.join(`
36469
36880
  throw new Error(`Failed to symlink node_modules: ${errorMessage(error48)}`);
36470
36881
  }
36471
36882
  }
36472
- const envSource = join51(projectRoot, ".env");
36883
+ const envSource = join53(projectRoot, ".env");
36473
36884
  if (existsSync32(envSource)) {
36474
- const envTarget = join51(worktreePath, ".env");
36885
+ const envTarget = join53(worktreePath, ".env");
36475
36886
  try {
36476
36887
  symlinkSync(envSource, envTarget, "file");
36477
36888
  } catch (error48) {
@@ -36482,7 +36893,7 @@ ${missing.join(`
36482
36893
  }
36483
36894
  async remove(projectRoot, storyId) {
36484
36895
  validateStoryId(storyId);
36485
- const worktreePath = join51(projectRoot, ".nax-wt", storyId);
36896
+ const worktreePath = join53(projectRoot, ".nax-wt", storyId);
36486
36897
  const branchName = `nax/${storyId}`;
36487
36898
  try {
36488
36899
  const proc = _managerDeps.spawn(["git", "worktree", "remove", worktreePath, "--force"], {
@@ -37401,16 +37812,16 @@ var init_unified_executor = __esm(() => {
37401
37812
  });
37402
37813
 
37403
37814
  // src/project/detector.ts
37404
- import { join as join52 } from "path";
37815
+ import { join as join54 } from "path";
37405
37816
  async function detectLanguage(workdir, pkg) {
37406
37817
  const deps = _detectorDeps;
37407
- if (await deps.fileExists(join52(workdir, "go.mod")))
37818
+ if (await deps.fileExists(join54(workdir, "go.mod")))
37408
37819
  return "go";
37409
- if (await deps.fileExists(join52(workdir, "Cargo.toml")))
37820
+ if (await deps.fileExists(join54(workdir, "Cargo.toml")))
37410
37821
  return "rust";
37411
- if (await deps.fileExists(join52(workdir, "pyproject.toml")))
37822
+ if (await deps.fileExists(join54(workdir, "pyproject.toml")))
37412
37823
  return "python";
37413
- if (await deps.fileExists(join52(workdir, "requirements.txt")))
37824
+ if (await deps.fileExists(join54(workdir, "requirements.txt")))
37414
37825
  return "python";
37415
37826
  if (pkg != null) {
37416
37827
  const allDeps = {
@@ -37470,18 +37881,18 @@ async function detectLintTool(workdir, language) {
37470
37881
  if (language === "python")
37471
37882
  return "ruff";
37472
37883
  const deps = _detectorDeps;
37473
- if (await deps.fileExists(join52(workdir, "biome.json")))
37884
+ if (await deps.fileExists(join54(workdir, "biome.json")))
37474
37885
  return "biome";
37475
- if (await deps.fileExists(join52(workdir, ".eslintrc")))
37886
+ if (await deps.fileExists(join54(workdir, ".eslintrc")))
37476
37887
  return "eslint";
37477
- if (await deps.fileExists(join52(workdir, ".eslintrc.js")))
37888
+ if (await deps.fileExists(join54(workdir, ".eslintrc.js")))
37478
37889
  return "eslint";
37479
- if (await deps.fileExists(join52(workdir, ".eslintrc.json")))
37890
+ if (await deps.fileExists(join54(workdir, ".eslintrc.json")))
37480
37891
  return "eslint";
37481
37892
  return;
37482
37893
  }
37483
37894
  async function detectProjectProfile(workdir, existing) {
37484
- const pkg = await _detectorDeps.readJson(join52(workdir, "package.json"));
37895
+ const pkg = await _detectorDeps.readJson(join54(workdir, "package.json"));
37485
37896
  const language = existing.language !== undefined ? existing.language : await detectLanguage(workdir, pkg);
37486
37897
  const type = existing.type !== undefined ? existing.type : detectType(pkg);
37487
37898
  const testFramework = existing.testFramework !== undefined ? existing.testFramework : await detectTestFramework(workdir, language, pkg);
@@ -37574,7 +37985,7 @@ async function writeStatusFile(filePath, status) {
37574
37985
  var init_status_file = () => {};
37575
37986
 
37576
37987
  // src/execution/status-writer.ts
37577
- import { join as join53 } from "path";
37988
+ import { join as join55 } from "path";
37578
37989
 
37579
37990
  class StatusWriter {
37580
37991
  statusFile;
@@ -37648,7 +38059,7 @@ class StatusWriter {
37648
38059
  if (!this._prd)
37649
38060
  return;
37650
38061
  const safeLogger = getSafeLogger();
37651
- const featureStatusPath = join53(featureDir, "status.json");
38062
+ const featureStatusPath = join55(featureDir, "status.json");
37652
38063
  const write = async () => {
37653
38064
  try {
37654
38065
  const base = this.getSnapshot(totalCost, iterations);
@@ -37764,7 +38175,7 @@ var exports_precheck_runner = {};
37764
38175
  __export(exports_precheck_runner, {
37765
38176
  runPrecheckValidation: () => runPrecheckValidation
37766
38177
  });
37767
- import { mkdirSync as mkdirSync4 } from "fs";
38178
+ import { mkdirSync as mkdirSync5 } from "fs";
37768
38179
  import path17 from "path";
37769
38180
  async function runPrecheckValidation(ctx) {
37770
38181
  const logger = getSafeLogger();
@@ -37779,7 +38190,7 @@ async function runPrecheckValidation(ctx) {
37779
38190
  format: "human"
37780
38191
  });
37781
38192
  if (ctx.logFilePath) {
37782
- mkdirSync4(path17.dirname(ctx.logFilePath), { recursive: true });
38193
+ mkdirSync5(path17.dirname(ctx.logFilePath), { recursive: true });
37783
38194
  const precheckLog = {
37784
38195
  type: "precheck",
37785
38196
  timestamp: new Date().toISOString(),
@@ -37859,7 +38270,7 @@ __export(exports_run_initialization, {
37859
38270
  initializeRun: () => initializeRun,
37860
38271
  _reconcileDeps: () => _reconcileDeps
37861
38272
  });
37862
- import { join as join54 } from "path";
38273
+ import { join as join56 } from "path";
37863
38274
  async function reconcileState(prd, prdPath, workdir, config2) {
37864
38275
  const logger = getSafeLogger();
37865
38276
  let reconciledCount = 0;
@@ -37877,7 +38288,7 @@ async function reconcileState(prd, prdPath, workdir, config2) {
37877
38288
  });
37878
38289
  continue;
37879
38290
  }
37880
- const effectiveWorkdir = story.workdir ? join54(workdir, story.workdir) : workdir;
38291
+ const effectiveWorkdir = story.workdir ? join56(workdir, story.workdir) : workdir;
37881
38292
  try {
37882
38293
  const reviewResult = await _reconcileDeps.runReview(config2.review, effectiveWorkdir, config2.execution);
37883
38294
  if (!reviewResult.success) {
@@ -69085,9 +69496,9 @@ var require_jsx_dev_runtime = __commonJS((exports, module) => {
69085
69496
 
69086
69497
  // bin/nax.ts
69087
69498
  init_source();
69088
- import { existsSync as existsSync34, mkdirSync as mkdirSync5 } from "fs";
69499
+ import { existsSync as existsSync34, mkdirSync as mkdirSync6 } from "fs";
69089
69500
  import { homedir as homedir8 } from "os";
69090
- import { join as join56 } from "path";
69501
+ import { join as join58 } from "path";
69091
69502
 
69092
69503
  // node_modules/commander/esm.mjs
69093
69504
  var import__ = __toESM(require_commander(), 1);
@@ -69109,14 +69520,14 @@ var {
69109
69520
  init_acceptance();
69110
69521
  init_registry();
69111
69522
  import { existsSync as existsSync8 } from "fs";
69112
- import { join as join8 } from "path";
69523
+ import { join as join9 } from "path";
69113
69524
 
69114
69525
  // src/analyze/scanner.ts
69115
- import { existsSync as existsSync5, readdirSync } from "fs";
69116
- import { join as join6 } from "path";
69526
+ import { existsSync as existsSync5, readdirSync as readdirSync2 } from "fs";
69527
+ import { join as join7 } from "path";
69117
69528
  async function scanCodebase(workdir) {
69118
- const srcPath = join6(workdir, "src");
69119
- const packageJsonPath = join6(workdir, "package.json");
69529
+ const srcPath = join7(workdir, "src");
69530
+ const packageJsonPath = join7(workdir, "package.json");
69120
69531
  const fileTree = existsSync5(srcPath) ? await generateFileTree(srcPath, 3) : "No src/ directory";
69121
69532
  let dependencies = {};
69122
69533
  let devDependencies = {};
@@ -69141,7 +69552,7 @@ async function generateFileTree(dir, maxDepth, currentDepth = 0, prefix = "") {
69141
69552
  }
69142
69553
  const entries = [];
69143
69554
  try {
69144
- const dirEntries = readdirSync(dir, { withFileTypes: true });
69555
+ const dirEntries = readdirSync2(dir, { withFileTypes: true });
69145
69556
  dirEntries.sort((a, b) => {
69146
69557
  if (a.isDirectory() && !b.isDirectory())
69147
69558
  return -1;
@@ -69157,7 +69568,7 @@ async function generateFileTree(dir, maxDepth, currentDepth = 0, prefix = "") {
69157
69568
  const isDir = dirent.isDirectory();
69158
69569
  entries.push(`${prefix}${connector}${dirent.name}${isDir ? "/" : ""}`);
69159
69570
  if (isDir) {
69160
- const subtree = await generateFileTree(join6(dir, dirent.name), maxDepth, currentDepth + 1, prefix + childPrefix);
69571
+ const subtree = await generateFileTree(join7(dir, dirent.name), maxDepth, currentDepth + 1, prefix + childPrefix);
69161
69572
  if (subtree) {
69162
69573
  entries.push(subtree);
69163
69574
  }
@@ -69181,16 +69592,16 @@ function detectTestPatterns(workdir, dependencies, devDependencies) {
69181
69592
  } else {
69182
69593
  patterns.push("Test framework: likely bun:test (no framework dependency)");
69183
69594
  }
69184
- if (existsSync5(join6(workdir, "test"))) {
69595
+ if (existsSync5(join7(workdir, "test"))) {
69185
69596
  patterns.push("Test directory: test/");
69186
69597
  }
69187
- if (existsSync5(join6(workdir, "__tests__"))) {
69598
+ if (existsSync5(join7(workdir, "__tests__"))) {
69188
69599
  patterns.push("Test directory: __tests__/");
69189
69600
  }
69190
- if (existsSync5(join6(workdir, "tests"))) {
69601
+ if (existsSync5(join7(workdir, "tests"))) {
69191
69602
  patterns.push("Test directory: tests/");
69192
69603
  }
69193
- const hasTestFiles = existsSync5(join6(workdir, "test")) || existsSync5(join6(workdir, "src"));
69604
+ const hasTestFiles = existsSync5(join7(workdir, "test")) || existsSync5(join7(workdir, "src"));
69194
69605
  if (hasTestFiles) {
69195
69606
  patterns.push("Test files: *.test.ts, *.spec.ts");
69196
69607
  }
@@ -69206,7 +69617,7 @@ init_version();
69206
69617
  // src/cli/analyze-parser.ts
69207
69618
  init_registry();
69208
69619
  import { existsSync as existsSync7 } from "fs";
69209
- import { join as join7 } from "path";
69620
+ import { join as join8 } from "path";
69210
69621
  init_config();
69211
69622
  init_logger2();
69212
69623
  init_prd();
@@ -69336,7 +69747,7 @@ function estimateLOCFromComplexity(complexity) {
69336
69747
  }
69337
69748
  }
69338
69749
  async function reclassifyExistingPRD(featureDir, featureName, branchName, workdir, config2) {
69339
- const prdPath = join7(featureDir, "prd.json");
69750
+ const prdPath = join8(featureDir, "prd.json");
69340
69751
  if (!existsSync7(prdPath)) {
69341
69752
  throw new Error(`prd.json not found at ${prdPath}. Run analyze without --reclassify first.`);
69342
69753
  }
@@ -69437,11 +69848,11 @@ function reclassifyWithKeywords(story, config2) {
69437
69848
  // src/cli/analyze.ts
69438
69849
  async function analyzeFeature(options) {
69439
69850
  const { featureDir, featureName, branchName, config: config2, specPath: explicitSpecPath, reclassify = false } = options;
69440
- const workdir = join8(featureDir, "../..");
69851
+ const workdir = join9(featureDir, "../..");
69441
69852
  if (reclassify) {
69442
69853
  return await reclassifyExistingPRD(featureDir, featureName, branchName, workdir, config2);
69443
69854
  }
69444
- const specPath = explicitSpecPath || join8(featureDir, "spec.md");
69855
+ const specPath = explicitSpecPath || join9(featureDir, "spec.md");
69445
69856
  if (!existsSync8(specPath))
69446
69857
  throw new Error(`spec.md not found at ${specPath}`);
69447
69858
  const specContent = await Bun.file(specPath).text();
@@ -69558,7 +69969,7 @@ async function generateAcceptanceTestsForFeature(specContent, featureName, featu
69558
69969
  modelDef,
69559
69970
  config: config2
69560
69971
  });
69561
- const acceptanceTestPath = join8(featureDir, config2.acceptance.testPath);
69972
+ const acceptanceTestPath = join9(featureDir, config2.acceptance.testPath);
69562
69973
  await Bun.write(acceptanceTestPath, result.testCode);
69563
69974
  logger.info("cli", "[OK] Acceptance tests generated", {
69564
69975
  criteriaCount: result.criteria.length,
@@ -69571,18 +69982,18 @@ async function generateAcceptanceTestsForFeature(specContent, featureName, featu
69571
69982
  // src/cli/plan.ts
69572
69983
  init_registry();
69573
69984
  import { existsSync as existsSync15 } from "fs";
69574
- import { join as join12 } from "path";
69985
+ import { join as join13 } from "path";
69575
69986
  import { createInterface as createInterface2 } from "readline";
69576
69987
  init_test_strategy();
69577
69988
 
69578
69989
  // src/context/generator.ts
69579
69990
  init_path_security();
69580
69991
  import { existsSync as existsSync10, readFileSync } from "fs";
69581
- import { join as join10, relative } from "path";
69992
+ import { join as join11, relative } from "path";
69582
69993
 
69583
69994
  // src/context/injector.ts
69584
69995
  import { existsSync as existsSync9 } from "fs";
69585
- import { join as join9 } from "path";
69996
+ import { join as join10 } from "path";
69586
69997
  var NOTABLE_NODE_DEPS = [
69587
69998
  "@nestjs",
69588
69999
  "express",
@@ -69612,7 +70023,7 @@ var NOTABLE_NODE_DEPS = [
69612
70023
  "ioredis"
69613
70024
  ];
69614
70025
  async function detectNode(workdir) {
69615
- const pkgPath = join9(workdir, "package.json");
70026
+ const pkgPath = join10(workdir, "package.json");
69616
70027
  if (!existsSync9(pkgPath))
69617
70028
  return null;
69618
70029
  try {
@@ -69629,7 +70040,7 @@ async function detectNode(workdir) {
69629
70040
  }
69630
70041
  }
69631
70042
  async function detectGo(workdir) {
69632
- const goMod = join9(workdir, "go.mod");
70043
+ const goMod = join10(workdir, "go.mod");
69633
70044
  if (!existsSync9(goMod))
69634
70045
  return null;
69635
70046
  try {
@@ -69653,7 +70064,7 @@ async function detectGo(workdir) {
69653
70064
  }
69654
70065
  }
69655
70066
  async function detectRust(workdir) {
69656
- const cargoPath = join9(workdir, "Cargo.toml");
70067
+ const cargoPath = join10(workdir, "Cargo.toml");
69657
70068
  if (!existsSync9(cargoPath))
69658
70069
  return null;
69659
70070
  try {
@@ -69669,8 +70080,8 @@ async function detectRust(workdir) {
69669
70080
  }
69670
70081
  }
69671
70082
  async function detectPython(workdir) {
69672
- const pyproject = join9(workdir, "pyproject.toml");
69673
- const requirements = join9(workdir, "requirements.txt");
70083
+ const pyproject = join10(workdir, "pyproject.toml");
70084
+ const requirements = join10(workdir, "requirements.txt");
69674
70085
  if (!existsSync9(pyproject) && !existsSync9(requirements))
69675
70086
  return null;
69676
70087
  try {
@@ -69689,7 +70100,7 @@ async function detectPython(workdir) {
69689
70100
  }
69690
70101
  }
69691
70102
  async function detectPhp(workdir) {
69692
- const composerPath = join9(workdir, "composer.json");
70103
+ const composerPath = join10(workdir, "composer.json");
69693
70104
  if (!existsSync9(composerPath))
69694
70105
  return null;
69695
70106
  try {
@@ -69702,7 +70113,7 @@ async function detectPhp(workdir) {
69702
70113
  }
69703
70114
  }
69704
70115
  async function detectRuby(workdir) {
69705
- const gemfile = join9(workdir, "Gemfile");
70116
+ const gemfile = join10(workdir, "Gemfile");
69706
70117
  if (!existsSync9(gemfile))
69707
70118
  return null;
69708
70119
  try {
@@ -69714,9 +70125,9 @@ async function detectRuby(workdir) {
69714
70125
  }
69715
70126
  }
69716
70127
  async function detectJvm(workdir) {
69717
- const pom = join9(workdir, "pom.xml");
69718
- const gradle = join9(workdir, "build.gradle");
69719
- const gradleKts = join9(workdir, "build.gradle.kts");
70128
+ const pom = join10(workdir, "pom.xml");
70129
+ const gradle = join10(workdir, "build.gradle");
70130
+ const gradleKts = join10(workdir, "build.gradle.kts");
69720
70131
  if (!existsSync9(pom) && !existsSync9(gradle) && !existsSync9(gradleKts))
69721
70132
  return null;
69722
70133
  try {
@@ -69724,7 +70135,7 @@ async function detectJvm(workdir) {
69724
70135
  const content2 = await Bun.file(pom).text();
69725
70136
  const nameMatch = content2.match(/<artifactId>([^<]+)<\/artifactId>/);
69726
70137
  const deps2 = [...content2.matchAll(/<artifactId>([^<]+)<\/artifactId>/g)].map((m) => m[1]).filter((d) => d !== nameMatch?.[1]).slice(0, 10);
69727
- const lang2 = existsSync9(join9(workdir, "src/main/kotlin")) ? "Kotlin" : "Java";
70138
+ const lang2 = existsSync9(join10(workdir, "src/main/kotlin")) ? "Kotlin" : "Java";
69728
70139
  return { name: nameMatch?.[1], lang: lang2, dependencies: deps2 };
69729
70140
  }
69730
70141
  const gradleFile = existsSync9(gradleKts) ? gradleKts : gradle;
@@ -69951,7 +70362,7 @@ async function generateFor(agent, options, config2) {
69951
70362
  try {
69952
70363
  const context = await loadContextContent(options, config2);
69953
70364
  const content = generator.generate(context);
69954
- const outputPath = join10(options.outputDir, generator.outputFile);
70365
+ const outputPath = join11(options.outputDir, generator.outputFile);
69955
70366
  validateFilePath(outputPath, options.outputDir);
69956
70367
  if (!options.dryRun) {
69957
70368
  await _generatorDeps.writeFile(outputPath, content);
@@ -69969,7 +70380,7 @@ async function generateAll(options, config2, agentFilter) {
69969
70380
  for (const [agentKey, generator] of entries) {
69970
70381
  try {
69971
70382
  const content = generator.generate(context);
69972
- const outputPath = join10(options.outputDir, generator.outputFile);
70383
+ const outputPath = join11(options.outputDir, generator.outputFile);
69973
70384
  validateFilePath(outputPath, options.outputDir);
69974
70385
  if (!options.dryRun) {
69975
70386
  await _generatorDeps.writeFile(outputPath, content);
@@ -69989,7 +70400,7 @@ async function discoverPackages(repoRoot) {
69989
70400
  const glob = new Bun.Glob(pattern);
69990
70401
  for await (const match of glob.scan({ cwd: repoRoot, dot: true })) {
69991
70402
  const pkgRelative = match.replace(/^\.nax\/mono\//, "").replace(/\/context\.md$/, "");
69992
- const pkgAbsolute = join10(repoRoot, pkgRelative);
70403
+ const pkgAbsolute = join11(repoRoot, pkgRelative);
69993
70404
  if (!seen.has(pkgAbsolute)) {
69994
70405
  seen.add(pkgAbsolute);
69995
70406
  packages.push(pkgAbsolute);
@@ -70021,7 +70432,7 @@ async function discoverWorkspacePackages(repoRoot) {
70021
70432
  }
70022
70433
  }
70023
70434
  }
70024
- const turboPath = join10(repoRoot, "turbo.json");
70435
+ const turboPath = join11(repoRoot, "turbo.json");
70025
70436
  if (_generatorDeps.existsSync(turboPath)) {
70026
70437
  try {
70027
70438
  const turbo = JSON.parse(_generatorDeps.readFileSync(turboPath, "utf-8"));
@@ -70030,7 +70441,7 @@ async function discoverWorkspacePackages(repoRoot) {
70030
70441
  }
70031
70442
  } catch {}
70032
70443
  }
70033
- const pkgPath = join10(repoRoot, "package.json");
70444
+ const pkgPath = join11(repoRoot, "package.json");
70034
70445
  if (_generatorDeps.existsSync(pkgPath)) {
70035
70446
  try {
70036
70447
  const pkg = JSON.parse(_generatorDeps.readFileSync(pkgPath, "utf-8"));
@@ -70040,7 +70451,7 @@ async function discoverWorkspacePackages(repoRoot) {
70040
70451
  await resolveGlobs(patterns);
70041
70452
  } catch {}
70042
70453
  }
70043
- const pnpmPath = join10(repoRoot, "pnpm-workspace.yaml");
70454
+ const pnpmPath = join11(repoRoot, "pnpm-workspace.yaml");
70044
70455
  if (_generatorDeps.existsSync(pnpmPath)) {
70045
70456
  try {
70046
70457
  const raw = _generatorDeps.readFileSync(pnpmPath, "utf-8");
@@ -70068,7 +70479,7 @@ async function discoverWorkspacePackages(repoRoot) {
70068
70479
  async function generateForPackage(packageDir, config2, dryRun = false, repoRoot) {
70069
70480
  const resolvedRepoRoot = repoRoot ?? packageDir;
70070
70481
  const relativePkgPath = relative(resolvedRepoRoot, packageDir);
70071
- const contextPath = join10(resolvedRepoRoot, ".nax", "mono", relativePkgPath, "context.md");
70482
+ const contextPath = join11(resolvedRepoRoot, ".nax", "mono", relativePkgPath, "context.md");
70072
70483
  if (!_generatorDeps.existsSync(contextPath)) {
70073
70484
  return [
70074
70485
  {
@@ -70300,7 +70711,7 @@ var _planDeps = {
70300
70711
  writeFile: (path, content) => Bun.write(path, content).then(() => {}),
70301
70712
  scanCodebase: (workdir) => scanCodebase(workdir),
70302
70713
  getAgent: (name, cfg) => cfg ? createAgentRegistry(cfg).getAgent(name) : getAgent(name),
70303
- readPackageJson: (workdir) => Bun.file(join12(workdir, "package.json")).json().catch(() => null),
70714
+ readPackageJson: (workdir) => Bun.file(join13(workdir, "package.json")).json().catch(() => null),
70304
70715
  spawnSync: (cmd, opts) => {
70305
70716
  const result = Bun.spawnSync(cmd, opts ? { cwd: opts.cwd } : {});
70306
70717
  return { stdout: result.stdout, exitCode: result.exitCode };
@@ -70320,7 +70731,7 @@ var _planDeps = {
70320
70731
  planDecompose: (workdir, config2, opts) => planDecomposeCommand(workdir, config2, opts)
70321
70732
  };
70322
70733
  async function planCommand(workdir, config2, options) {
70323
- const naxDir = join12(workdir, ".nax");
70734
+ const naxDir = join13(workdir, ".nax");
70324
70735
  if (!existsSync15(naxDir)) {
70325
70736
  throw new Error(`.nax directory not found. Run 'nax init' first in ${workdir}`);
70326
70737
  }
@@ -70336,13 +70747,13 @@ async function planCommand(workdir, config2, options) {
70336
70747
  const codebaseContext = buildCodebaseContext2(scan);
70337
70748
  const relativePackages = discoveredPackages.map((p) => p.startsWith("/") ? p.replace(`${workdir}/`, "") : p);
70338
70749
  const packageDetails = relativePackages.length > 0 ? await Promise.all(relativePackages.map(async (rel) => {
70339
- const pkgJson = await _planDeps.readPackageJsonAt(join12(workdir, rel, "package.json"));
70750
+ const pkgJson = await _planDeps.readPackageJsonAt(join13(workdir, rel, "package.json"));
70340
70751
  return buildPackageSummary(rel, pkgJson);
70341
70752
  })) : [];
70342
70753
  const projectName = detectProjectName(workdir, pkg);
70343
70754
  const branchName = options.branch ?? `feat/${options.feature}`;
70344
- const outputDir = join12(naxDir, "features", options.feature);
70345
- const outputPath = join12(outputDir, "prd.json");
70755
+ const outputDir = join13(naxDir, "features", options.feature);
70756
+ const outputPath = join13(outputDir, "prd.json");
70346
70757
  await _planDeps.mkdirp(outputDir);
70347
70758
  const agentName = config2?.autoMode?.defaultAgent ?? "claude";
70348
70759
  const timeoutSeconds = config2?.plan?.timeoutSeconds ?? DEFAULT_TIMEOUT_SECONDS2;
@@ -70777,7 +71188,7 @@ Return JSON with this exact structure (no markdown, no explanation \u2014 JSON o
70777
71188
  }`;
70778
71189
  }
70779
71190
  async function planDecomposeCommand(workdir, config2, options) {
70780
- const prdPath = join12(workdir, ".nax", "features", options.feature, "prd.json");
71191
+ const prdPath = join13(workdir, ".nax", "features", options.feature, "prd.json");
70781
71192
  if (!_planDeps.existsSync(prdPath)) {
70782
71193
  throw new NaxError(`PRD not found: ${prdPath}`, "PRD_NOT_FOUND", {
70783
71194
  stage: "decompose",
@@ -70807,6 +71218,15 @@ async function planDecomposeCommand(workdir, config2, options) {
70807
71218
  const adapter = _planDeps.getAgent(agentName, config2);
70808
71219
  if (!adapter)
70809
71220
  throw new Error(`[decompose] No agent adapter found for '${agentName}'`);
71221
+ let decomposeModel;
71222
+ try {
71223
+ const planTier = config2?.plan?.model ?? "balanced";
71224
+ const { resolveModelForAgent: resolveModelForAgent2 } = await Promise.resolve().then(() => (init_schema(), exports_schema));
71225
+ if (config2?.models) {
71226
+ const defaultAgent = config2.autoMode?.defaultAgent ?? "claude";
71227
+ decomposeModel = resolveModelForAgent2(config2.models, defaultAgent, planTier, defaultAgent).model;
71228
+ }
71229
+ } catch {}
70810
71230
  const stages = config2?.debate?.stages;
70811
71231
  const debateEnabled = config2?.debate?.enabled && stages?.decompose?.enabled;
70812
71232
  let rawResponse;
@@ -70828,6 +71248,7 @@ async function planDecomposeCommand(workdir, config2, options) {
70828
71248
  rawResponse = debateResult.output;
70829
71249
  } else {
70830
71250
  const completeResult = await adapter.complete(prompt, {
71251
+ model: decomposeModel,
70831
71252
  jsonMode: true,
70832
71253
  workdir,
70833
71254
  sessionRole: "decompose",
@@ -70839,6 +71260,7 @@ async function planDecomposeCommand(workdir, config2, options) {
70839
71260
  }
70840
71261
  } else {
70841
71262
  const completeResult = await adapter.complete(prompt, {
71263
+ model: decomposeModel,
70842
71264
  jsonMode: true,
70843
71265
  workdir,
70844
71266
  sessionRole: "decompose",
@@ -71068,14 +71490,14 @@ async function displayModelEfficiency(workdir) {
71068
71490
  }
71069
71491
  // src/cli/status-features.ts
71070
71492
  init_source();
71071
- import { existsSync as existsSync17, readdirSync as readdirSync3 } from "fs";
71072
- import { join as join15 } from "path";
71493
+ import { existsSync as existsSync17, readdirSync as readdirSync4 } from "fs";
71494
+ import { join as join16 } from "path";
71073
71495
 
71074
71496
  // src/commands/common.ts
71075
71497
  init_path_security();
71076
71498
  init_errors();
71077
- import { existsSync as existsSync16, readdirSync as readdirSync2, realpathSync as realpathSync2 } from "fs";
71078
- import { join as join13, resolve as resolve4 } from "path";
71499
+ import { existsSync as existsSync16, readdirSync as readdirSync3, realpathSync as realpathSync2 } from "fs";
71500
+ import { join as join14, resolve as resolve4 } from "path";
71079
71501
  function resolveProject(options = {}) {
71080
71502
  const { dir, feature } = options;
71081
71503
  let projectRoot;
@@ -71083,12 +71505,12 @@ function resolveProject(options = {}) {
71083
71505
  let configPath;
71084
71506
  if (dir) {
71085
71507
  projectRoot = realpathSync2(resolve4(dir));
71086
- naxDir = join13(projectRoot, ".nax");
71508
+ naxDir = join14(projectRoot, ".nax");
71087
71509
  if (!existsSync16(naxDir)) {
71088
71510
  throw new NaxError(`Directory does not contain a nax project: ${projectRoot}
71089
71511
  Expected to find: ${naxDir}`, "NAX_DIR_NOT_FOUND", { projectRoot, naxDir });
71090
71512
  }
71091
- configPath = join13(naxDir, "config.json");
71513
+ configPath = join14(naxDir, "config.json");
71092
71514
  if (!existsSync16(configPath)) {
71093
71515
  throw new NaxError(`.nax directory found but config.json is missing: ${naxDir}
71094
71516
  Expected to find: ${configPath}`, "CONFIG_NOT_FOUND", { naxDir, configPath });
@@ -71096,24 +71518,24 @@ Expected to find: ${configPath}`, "CONFIG_NOT_FOUND", { naxDir, configPath });
71096
71518
  } else {
71097
71519
  const found = findProjectRoot(process.cwd());
71098
71520
  if (!found) {
71099
- const cwdNaxDir = join13(process.cwd(), ".nax");
71521
+ const cwdNaxDir = join14(process.cwd(), ".nax");
71100
71522
  if (existsSync16(cwdNaxDir)) {
71101
- const cwdConfigPath = join13(cwdNaxDir, "config.json");
71523
+ const cwdConfigPath = join14(cwdNaxDir, "config.json");
71102
71524
  throw new NaxError(`.nax directory found but config.json is missing: ${cwdNaxDir}
71103
71525
  Expected to find: ${cwdConfigPath}`, "CONFIG_NOT_FOUND", { naxDir: cwdNaxDir, configPath: cwdConfigPath });
71104
71526
  }
71105
71527
  throw new NaxError("No nax project found. Run this command from within a nax project directory, or use -d flag to specify the project path.", "PROJECT_NOT_FOUND", { cwd: process.cwd() });
71106
71528
  }
71107
71529
  projectRoot = found;
71108
- naxDir = join13(projectRoot, ".nax");
71109
- configPath = join13(naxDir, "config.json");
71530
+ naxDir = join14(projectRoot, ".nax");
71531
+ configPath = join14(naxDir, "config.json");
71110
71532
  }
71111
71533
  let featureDir;
71112
71534
  if (feature) {
71113
- const featuresDir = join13(naxDir, "features");
71114
- featureDir = join13(featuresDir, feature);
71535
+ const featuresDir = join14(naxDir, "features");
71536
+ featureDir = join14(featuresDir, feature);
71115
71537
  if (!existsSync16(featureDir)) {
71116
- const availableFeatures = existsSync16(featuresDir) ? readdirSync2(featuresDir, { withFileTypes: true }).filter((entry) => entry.isDirectory()).map((entry) => entry.name) : [];
71538
+ const availableFeatures = existsSync16(featuresDir) ? readdirSync3(featuresDir, { withFileTypes: true }).filter((entry) => entry.isDirectory()).map((entry) => entry.name) : [];
71117
71539
  const availableMsg = availableFeatures.length > 0 ? `
71118
71540
 
71119
71541
  Available features:
@@ -71138,12 +71560,12 @@ function findProjectRoot(startDir) {
71138
71560
  let current = resolve4(startDir);
71139
71561
  let depth = 0;
71140
71562
  while (depth < MAX_DIRECTORY_DEPTH) {
71141
- const naxDir = join13(current, ".nax");
71142
- const configPath = join13(naxDir, "config.json");
71563
+ const naxDir = join14(current, ".nax");
71564
+ const configPath = join14(naxDir, "config.json");
71143
71565
  if (existsSync16(configPath)) {
71144
71566
  return realpathSync2(current);
71145
71567
  }
71146
- const parent = join13(current, "..");
71568
+ const parent = join14(current, "..");
71147
71569
  if (parent === current) {
71148
71570
  break;
71149
71571
  }
@@ -71165,7 +71587,7 @@ function isPidAlive(pid) {
71165
71587
  }
71166
71588
  }
71167
71589
  async function loadStatusFile(featureDir) {
71168
- const statusPath = join15(featureDir, "status.json");
71590
+ const statusPath = join16(featureDir, "status.json");
71169
71591
  if (!existsSync17(statusPath)) {
71170
71592
  return null;
71171
71593
  }
@@ -71177,7 +71599,7 @@ async function loadStatusFile(featureDir) {
71177
71599
  }
71178
71600
  }
71179
71601
  async function loadProjectStatusFile(projectDir) {
71180
- const statusPath = join15(projectDir, ".nax", "status.json");
71602
+ const statusPath = join16(projectDir, ".nax", "status.json");
71181
71603
  if (!existsSync17(statusPath)) {
71182
71604
  return null;
71183
71605
  }
@@ -71189,7 +71611,7 @@ async function loadProjectStatusFile(projectDir) {
71189
71611
  }
71190
71612
  }
71191
71613
  async function getFeatureSummary(featureName, featureDir) {
71192
- const prdPath = join15(featureDir, "prd.json");
71614
+ const prdPath = join16(featureDir, "prd.json");
71193
71615
  if (!existsSync17(prdPath)) {
71194
71616
  return {
71195
71617
  name: featureName,
@@ -71232,9 +71654,9 @@ async function getFeatureSummary(featureName, featureDir) {
71232
71654
  };
71233
71655
  }
71234
71656
  }
71235
- const runsDir = join15(featureDir, "runs");
71657
+ const runsDir = join16(featureDir, "runs");
71236
71658
  if (existsSync17(runsDir)) {
71237
- const runs = readdirSync3(runsDir, { withFileTypes: true }).filter((e) => e.isFile() && e.name.endsWith(".jsonl") && e.name !== "latest.jsonl").map((e) => e.name).sort().reverse();
71659
+ const runs = readdirSync4(runsDir, { withFileTypes: true }).filter((e) => e.isFile() && e.name.endsWith(".jsonl") && e.name !== "latest.jsonl").map((e) => e.name).sort().reverse();
71238
71660
  if (runs.length > 0) {
71239
71661
  const latestRun = runs[0].replace(".jsonl", "");
71240
71662
  summary.lastRun = latestRun;
@@ -71243,12 +71665,12 @@ async function getFeatureSummary(featureName, featureDir) {
71243
71665
  return summary;
71244
71666
  }
71245
71667
  async function displayAllFeatures(projectDir) {
71246
- const featuresDir = join15(projectDir, ".nax", "features");
71668
+ const featuresDir = join16(projectDir, ".nax", "features");
71247
71669
  if (!existsSync17(featuresDir)) {
71248
71670
  console.log(source_default.dim("No features found."));
71249
71671
  return;
71250
71672
  }
71251
- const features = readdirSync3(featuresDir, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => e.name).sort();
71673
+ const features = readdirSync4(featuresDir, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => e.name).sort();
71252
71674
  if (features.length === 0) {
71253
71675
  console.log(source_default.dim("No features found."));
71254
71676
  return;
@@ -71284,7 +71706,7 @@ async function displayAllFeatures(projectDir) {
71284
71706
  console.log();
71285
71707
  }
71286
71708
  }
71287
- const summaries = await Promise.all(features.map((name) => getFeatureSummary(name, join15(featuresDir, name))));
71709
+ const summaries = await Promise.all(features.map((name) => getFeatureSummary(name, join16(featuresDir, name))));
71288
71710
  console.log(source_default.bold(`\uD83D\uDCCA Features
71289
71711
  `));
71290
71712
  const header = ` ${"Feature".padEnd(25)} ${"Done".padEnd(6)} ${"Failed".padEnd(8)} ${"Pending".padEnd(9)} ${"Last Run".padEnd(22)} ${"Cost".padEnd(10)} Status`;
@@ -71310,7 +71732,7 @@ async function displayAllFeatures(projectDir) {
71310
71732
  console.log();
71311
71733
  }
71312
71734
  async function displayFeatureDetails(featureName, featureDir) {
71313
- const prdPath = join15(featureDir, "prd.json");
71735
+ const prdPath = join16(featureDir, "prd.json");
71314
71736
  if (!existsSync17(prdPath)) {
71315
71737
  console.log(source_default.bold(`
71316
71738
  \uD83D\uDCCA ${featureName}
@@ -71431,8 +71853,8 @@ async function displayFeatureStatus(options = {}) {
71431
71853
  // src/cli/runs.ts
71432
71854
  init_errors();
71433
71855
  init_logger2();
71434
- import { existsSync as existsSync18, readdirSync as readdirSync4 } from "fs";
71435
- import { join as join16 } from "path";
71856
+ import { existsSync as existsSync18, readdirSync as readdirSync5 } from "fs";
71857
+ import { join as join17 } from "path";
71436
71858
  async function parseRunLog(logPath) {
71437
71859
  const logger = getLogger();
71438
71860
  try {
@@ -71448,19 +71870,19 @@ async function parseRunLog(logPath) {
71448
71870
  async function runsListCommand(options) {
71449
71871
  const logger = getLogger();
71450
71872
  const { feature, workdir } = options;
71451
- const runsDir = join16(workdir, ".nax", "features", feature, "runs");
71873
+ const runsDir = join17(workdir, ".nax", "features", feature, "runs");
71452
71874
  if (!existsSync18(runsDir)) {
71453
71875
  logger.info("cli", "No runs found for feature", { feature, hint: `Directory not found: ${runsDir}` });
71454
71876
  return;
71455
71877
  }
71456
- const files = readdirSync4(runsDir).filter((f) => f.endsWith(".jsonl"));
71878
+ const files = readdirSync5(runsDir).filter((f) => f.endsWith(".jsonl"));
71457
71879
  if (files.length === 0) {
71458
71880
  logger.info("cli", "No runs found for feature", { feature });
71459
71881
  return;
71460
71882
  }
71461
71883
  logger.info("cli", `Runs for ${feature}`, { count: files.length });
71462
71884
  for (const file3 of files.sort().reverse()) {
71463
- const logPath = join16(runsDir, file3);
71885
+ const logPath = join17(runsDir, file3);
71464
71886
  const entries = await parseRunLog(logPath);
71465
71887
  const startEvent = entries.find((e) => e.message === "run.start");
71466
71888
  const completeEvent = entries.find((e) => e.message === "run.complete");
@@ -71486,7 +71908,7 @@ async function runsListCommand(options) {
71486
71908
  async function runsShowCommand(options) {
71487
71909
  const logger = getLogger();
71488
71910
  const { runId, feature, workdir } = options;
71489
- const logPath = join16(workdir, ".nax", "features", feature, "runs", `${runId}.jsonl`);
71911
+ const logPath = join17(workdir, ".nax", "features", feature, "runs", `${runId}.jsonl`);
71490
71912
  if (!existsSync18(logPath)) {
71491
71913
  logger.error("cli", "Run not found", { runId, feature, logPath });
71492
71914
  throw new NaxError("Run not found", "RUN_NOT_FOUND", { runId, feature, logPath });
@@ -71525,7 +71947,7 @@ async function runsShowCommand(options) {
71525
71947
  // src/cli/prompts-main.ts
71526
71948
  init_logger2();
71527
71949
  import { existsSync as existsSync22, mkdirSync as mkdirSync2 } from "fs";
71528
- import { join as join29 } from "path";
71950
+ import { join as join30 } from "path";
71529
71951
 
71530
71952
  // src/pipeline/index.ts
71531
71953
  init_runner();
@@ -71600,7 +72022,7 @@ function buildFrontmatter(story, ctx, role) {
71600
72022
 
71601
72023
  // src/cli/prompts-tdd.ts
71602
72024
  init_prompts2();
71603
- import { join as join28 } from "path";
72025
+ import { join as join29 } from "path";
71604
72026
  async function handleThreeSessionTddPrompts(story, ctx, outputDir, logger) {
71605
72027
  const [testWriterPrompt, implementerPrompt, verifierPrompt] = await Promise.all([
71606
72028
  PromptBuilder.for("test-writer", { isolation: "strict" }).withLoader(ctx.workdir, ctx.config).story(story).context(ctx.contextMarkdown).constitution(ctx.constitution?.content).testCommand(ctx.config.quality?.commands?.test).build(),
@@ -71619,7 +72041,7 @@ ${frontmatter}---
71619
72041
 
71620
72042
  ${session.prompt}`;
71621
72043
  if (outputDir) {
71622
- const promptFile = join28(outputDir, `${story.id}.${session.role}.md`);
72044
+ const promptFile = join29(outputDir, `${story.id}.${session.role}.md`);
71623
72045
  await Bun.write(promptFile, fullOutput);
71624
72046
  logger.info("cli", "Written TDD prompt file", {
71625
72047
  storyId: story.id,
@@ -71635,7 +72057,7 @@ ${"=".repeat(80)}`);
71635
72057
  }
71636
72058
  }
71637
72059
  if (outputDir && ctx.contextMarkdown) {
71638
- const contextFile = join28(outputDir, `${story.id}.context.md`);
72060
+ const contextFile = join29(outputDir, `${story.id}.context.md`);
71639
72061
  const frontmatter = buildFrontmatter(story, ctx);
71640
72062
  const contextOutput = `---
71641
72063
  ${frontmatter}---
@@ -71649,12 +72071,12 @@ ${ctx.contextMarkdown}`;
71649
72071
  async function promptsCommand(options) {
71650
72072
  const logger = getLogger();
71651
72073
  const { feature, workdir, config: config2, storyId, outputDir } = options;
71652
- const naxDir = join29(workdir, ".nax");
72074
+ const naxDir = join30(workdir, ".nax");
71653
72075
  if (!existsSync22(naxDir)) {
71654
72076
  throw new Error(`.nax directory not found. Run 'nax init' first in ${workdir}`);
71655
72077
  }
71656
- const featureDir = join29(naxDir, "features", feature);
71657
- const prdPath = join29(featureDir, "prd.json");
72078
+ const featureDir = join30(naxDir, "features", feature);
72079
+ const prdPath = join30(featureDir, "prd.json");
71658
72080
  if (!existsSync22(prdPath)) {
71659
72081
  throw new Error(`Feature "${feature}" not found or missing prd.json`);
71660
72082
  }
@@ -71715,10 +72137,10 @@ ${frontmatter}---
71715
72137
 
71716
72138
  ${ctx.prompt}`;
71717
72139
  if (outputDir) {
71718
- const promptFile = join29(outputDir, `${story.id}.prompt.md`);
72140
+ const promptFile = join30(outputDir, `${story.id}.prompt.md`);
71719
72141
  await Bun.write(promptFile, fullOutput);
71720
72142
  if (ctx.contextMarkdown) {
71721
- const contextFile = join29(outputDir, `${story.id}.context.md`);
72143
+ const contextFile = join30(outputDir, `${story.id}.context.md`);
71722
72144
  const contextOutput = `---
71723
72145
  ${frontmatter}---
71724
72146
 
@@ -71745,7 +72167,7 @@ ${"=".repeat(80)}`);
71745
72167
  }
71746
72168
  // src/cli/prompts-init.ts
71747
72169
  import { existsSync as existsSync23, mkdirSync as mkdirSync3 } from "fs";
71748
- import { join as join30 } from "path";
72170
+ import { join as join31 } from "path";
71749
72171
  var TEMPLATE_ROLES = [
71750
72172
  { file: "test-writer.md", role: "test-writer" },
71751
72173
  { file: "implementer.md", role: "implementer", variant: "standard" },
@@ -71769,9 +72191,9 @@ var TEMPLATE_HEADER = `<!--
71769
72191
  `;
71770
72192
  async function promptsInitCommand(options) {
71771
72193
  const { workdir, force = false, autoWireConfig = true } = options;
71772
- const templatesDir = join30(workdir, ".nax", "templates");
72194
+ const templatesDir = join31(workdir, ".nax", "templates");
71773
72195
  mkdirSync3(templatesDir, { recursive: true });
71774
- const existingFiles = TEMPLATE_ROLES.map((t) => t.file).filter((f) => existsSync23(join30(templatesDir, f)));
72196
+ const existingFiles = TEMPLATE_ROLES.map((t) => t.file).filter((f) => existsSync23(join31(templatesDir, f)));
71775
72197
  if (existingFiles.length > 0 && !force) {
71776
72198
  console.warn(`[WARN] nax/templates/ already contains files: ${existingFiles.join(", ")}. No files overwritten.
71777
72199
  Pass --force to overwrite existing templates.`);
@@ -71779,7 +72201,7 @@ async function promptsInitCommand(options) {
71779
72201
  }
71780
72202
  const written = [];
71781
72203
  for (const template of TEMPLATE_ROLES) {
71782
- const filePath = join30(templatesDir, template.file);
72204
+ const filePath = join31(templatesDir, template.file);
71783
72205
  const roleBody = template.role === "implementer" ? buildRoleTaskSection(template.role, template.variant) : buildRoleTaskSection(template.role);
71784
72206
  const content = TEMPLATE_HEADER + roleBody;
71785
72207
  await Bun.write(filePath, content);
@@ -71795,7 +72217,7 @@ async function promptsInitCommand(options) {
71795
72217
  return written;
71796
72218
  }
71797
72219
  async function autoWirePromptsConfig(workdir) {
71798
- const configPath = join30(workdir, "nax.config.json");
72220
+ const configPath = join31(workdir, "nax.config.json");
71799
72221
  if (!existsSync23(configPath)) {
71800
72222
  const exampleConfig = JSON.stringify({
71801
72223
  prompts: {
@@ -71960,8 +72382,8 @@ function pad(str, width) {
71960
72382
  init_config();
71961
72383
  init_logger2();
71962
72384
  init_prd();
71963
- import { existsSync as existsSync25, readdirSync as readdirSync5 } from "fs";
71964
- import { join as join35 } from "path";
72385
+ import { existsSync as existsSync25, readdirSync as readdirSync6 } from "fs";
72386
+ import { join as join36 } from "path";
71965
72387
 
71966
72388
  // src/cli/diagnose-analysis.ts
71967
72389
  function detectFailurePattern(story, prd, status) {
@@ -72160,7 +72582,7 @@ function isProcessAlive2(pid) {
72160
72582
  }
72161
72583
  }
72162
72584
  async function loadStatusFile2(workdir) {
72163
- const statusPath = join35(workdir, ".nax", "status.json");
72585
+ const statusPath = join36(workdir, ".nax", "status.json");
72164
72586
  if (!existsSync25(statusPath))
72165
72587
  return null;
72166
72588
  try {
@@ -72188,7 +72610,7 @@ async function countCommitsSince(workdir, since) {
72188
72610
  }
72189
72611
  }
72190
72612
  async function checkLock(workdir) {
72191
- const lockFile = Bun.file(join35(workdir, "nax.lock"));
72613
+ const lockFile = Bun.file(join36(workdir, "nax.lock"));
72192
72614
  if (!await lockFile.exists())
72193
72615
  return { lockPresent: false };
72194
72616
  try {
@@ -72206,8 +72628,8 @@ async function diagnoseCommand(options = {}) {
72206
72628
  const logger = getLogger();
72207
72629
  const workdir = options.workdir ?? process.cwd();
72208
72630
  const naxSubdir = findProjectDir(workdir);
72209
- let projectDir = naxSubdir ? join35(naxSubdir, "..") : null;
72210
- if (!projectDir && existsSync25(join35(workdir, ".nax"))) {
72631
+ let projectDir = naxSubdir ? join36(naxSubdir, "..") : null;
72632
+ if (!projectDir && existsSync25(join36(workdir, ".nax"))) {
72211
72633
  projectDir = workdir;
72212
72634
  }
72213
72635
  if (!projectDir)
@@ -72218,18 +72640,18 @@ async function diagnoseCommand(options = {}) {
72218
72640
  if (status2) {
72219
72641
  feature = status2.run.feature;
72220
72642
  } else {
72221
- const featuresDir = join35(projectDir, ".nax", "features");
72643
+ const featuresDir = join36(projectDir, ".nax", "features");
72222
72644
  if (!existsSync25(featuresDir))
72223
72645
  throw new Error("No features found in project");
72224
- const features = readdirSync5(featuresDir, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => e.name);
72646
+ const features = readdirSync6(featuresDir, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => e.name);
72225
72647
  if (features.length === 0)
72226
72648
  throw new Error("No features found");
72227
72649
  feature = features[0];
72228
72650
  logger.info("diagnose", "No feature specified, using first found", { feature });
72229
72651
  }
72230
72652
  }
72231
- const featureDir = join35(projectDir, ".nax", "features", feature);
72232
- const prdPath = join35(featureDir, "prd.json");
72653
+ const featureDir = join36(projectDir, ".nax", "features", feature);
72654
+ const prdPath = join36(featureDir, "prd.json");
72233
72655
  if (!existsSync25(prdPath))
72234
72656
  throw new Error(`Feature not found: ${feature}`);
72235
72657
  const prd = await loadPRD(prdPath);
@@ -72272,7 +72694,7 @@ init_interaction();
72272
72694
  init_source();
72273
72695
  init_loader();
72274
72696
  import { existsSync as existsSync26 } from "fs";
72275
- import { join as join36 } from "path";
72697
+ import { join as join37 } from "path";
72276
72698
  var VALID_AGENTS = ["claude", "codex", "opencode", "cursor", "windsurf", "aider", "gemini"];
72277
72699
  async function generateCommand(options) {
72278
72700
  const workdir = options.dir ?? process.cwd();
@@ -72315,7 +72737,7 @@ async function generateCommand(options) {
72315
72737
  return;
72316
72738
  }
72317
72739
  if (options.package) {
72318
- const packageDir = join36(workdir, options.package);
72740
+ const packageDir = join37(workdir, options.package);
72319
72741
  if (dryRun) {
72320
72742
  console.log(source_default.yellow("\u26A0 Dry run \u2014 no files will be written"));
72321
72743
  }
@@ -72335,8 +72757,8 @@ async function generateCommand(options) {
72335
72757
  process.exit(1);
72336
72758
  return;
72337
72759
  }
72338
- const contextPath = options.context ? join36(workdir, options.context) : join36(workdir, ".nax/context.md");
72339
- const outputDir = options.output ? join36(workdir, options.output) : workdir;
72760
+ const contextPath = options.context ? join37(workdir, options.context) : join37(workdir, ".nax/context.md");
72761
+ const outputDir = options.output ? join37(workdir, options.output) : workdir;
72340
72762
  const autoInject = !options.noAutoInject;
72341
72763
  if (!existsSync26(contextPath)) {
72342
72764
  console.error(source_default.red(`\u2717 Context file not found: ${contextPath}`));
@@ -72441,7 +72863,7 @@ async function generateCommand(options) {
72441
72863
  // src/cli/config-display.ts
72442
72864
  init_loader();
72443
72865
  import { existsSync as existsSync28 } from "fs";
72444
- import { join as join38 } from "path";
72866
+ import { join as join39 } from "path";
72445
72867
 
72446
72868
  // src/cli/config-descriptions.ts
72447
72869
  var FIELD_DESCRIPTIONS = {
@@ -72679,7 +73101,7 @@ function deepEqual(a, b) {
72679
73101
  init_defaults();
72680
73102
  init_loader();
72681
73103
  import { existsSync as existsSync27 } from "fs";
72682
- import { join as join37 } from "path";
73104
+ import { join as join38 } from "path";
72683
73105
  async function loadConfigFile(path14) {
72684
73106
  if (!existsSync27(path14))
72685
73107
  return null;
@@ -72701,7 +73123,7 @@ async function loadProjectConfig() {
72701
73123
  const projectDir = findProjectDir();
72702
73124
  if (!projectDir)
72703
73125
  return null;
72704
- const projectPath = join37(projectDir, "config.json");
73126
+ const projectPath = join38(projectDir, "config.json");
72705
73127
  return await loadConfigFile(projectPath);
72706
73128
  }
72707
73129
 
@@ -72761,7 +73183,7 @@ async function configCommand(config2, options = {}) {
72761
73183
  function determineConfigSources() {
72762
73184
  const globalPath = globalConfigPath();
72763
73185
  const projectDir = findProjectDir();
72764
- const projectPath = projectDir ? join38(projectDir, "config.json") : null;
73186
+ const projectPath = projectDir ? join39(projectDir, "config.json") : null;
72765
73187
  return {
72766
73188
  global: fileExists(globalPath) ? globalPath : null,
72767
73189
  project: projectPath && fileExists(projectPath) ? projectPath : null
@@ -72831,6 +73253,26 @@ function displayConfigWithDescriptions(obj, path14, sources, indent = 0) {
72831
73253
  }
72832
73254
  }
72833
73255
  }
73256
+ if (indent === 0 && !entries.find(([k]) => k === "prompts")) {
73257
+ console.log("# prompts: Prompt template overrides (PB-003: PromptBuilder)");
73258
+ const description = FIELD_DESCRIPTIONS["prompts.overrides"];
73259
+ if (description) {
73260
+ console.log(`# prompts.overrides: ${description}`);
73261
+ }
73262
+ const roles = ["test-writer", "implementer", "verifier", "single-session"];
73263
+ console.log("overrides:");
73264
+ for (const role of roles) {
73265
+ const roleDesc = FIELD_DESCRIPTIONS[`prompts.overrides.${role}`];
73266
+ if (roleDesc) {
73267
+ console.log(` # ${roleDesc}`);
73268
+ const match = roleDesc.match(/e\.g\., "([^"]+)"/);
73269
+ if (match) {
73270
+ console.log(` # ${role}: "${match[1]}"`);
73271
+ }
73272
+ }
73273
+ }
73274
+ console.log();
73275
+ }
72834
73276
  }
72835
73277
  function formatValue(value) {
72836
73278
  if (value === null)
@@ -72885,6 +73327,107 @@ function formatValueForTable(value) {
72885
73327
  }
72886
73328
  return String(value);
72887
73329
  }
73330
+ // src/cli/config-profile.ts
73331
+ init_paths();
73332
+ init_profile();
73333
+ import { mkdirSync as mkdirSync4 } from "fs";
73334
+ import { readdirSync as readdirSync7 } from "fs";
73335
+ import { join as join40 } from "path";
73336
+ var _profileCLIDeps = {
73337
+ env: process.env
73338
+ };
73339
+ var SENSITIVE_KEY_PATTERN = /key|token|secret|password|credential/i;
73340
+ var VAR_PATTERN = /\$[A-Za-z_][A-Za-z0-9_]*/;
73341
+ async function profileListCommand(startDir) {
73342
+ const globalProfilesDir = join40(globalConfigDir(), "profiles");
73343
+ const projectProfilesDir = join40(projectConfigDir(startDir), "profiles");
73344
+ const globalProfiles = scanProfileDir(globalProfilesDir);
73345
+ const projectProfiles = scanProfileDir(projectProfilesDir);
73346
+ const activeProfile = await resolveProfileName({}, _profileCLIDeps.env, startDir);
73347
+ const lines = [];
73348
+ lines.push("global:");
73349
+ if (globalProfiles.length === 0) {
73350
+ lines.push(" (none)");
73351
+ } else {
73352
+ for (const name of globalProfiles) {
73353
+ const marker = name === activeProfile ? "* " : " ";
73354
+ lines.push(`${marker}${name}`);
73355
+ }
73356
+ }
73357
+ if (projectProfiles.length > 0) {
73358
+ lines.push("project:");
73359
+ for (const name of projectProfiles) {
73360
+ const marker = name === activeProfile ? "* " : " ";
73361
+ lines.push(`${marker}${name}`);
73362
+ }
73363
+ }
73364
+ return lines.join(`
73365
+ `);
73366
+ }
73367
+ function scanProfileDir(dir) {
73368
+ try {
73369
+ return readdirSync7(dir).filter((f) => f.endsWith(".json")).map((f) => f.replace(/\.json$/, ""));
73370
+ } catch {
73371
+ return [];
73372
+ }
73373
+ }
73374
+ async function profileShowCommand(profileName, startDir, opts) {
73375
+ const rawProfile = await loadProfile(profileName, startDir);
73376
+ const envVars = await loadProfileEnv(profileName, startDir);
73377
+ if (opts.unmask) {
73378
+ const resolved = resolveEnvVars(rawProfile, envVars);
73379
+ const warning = "WARNING: Sensitive values are displayed in plaintext.";
73380
+ return `${warning}
73381
+ ${JSON.stringify(resolved, null, 2)}`;
73382
+ }
73383
+ const masked = maskProfileValues(rawProfile);
73384
+ return JSON.stringify(masked, null, 2);
73385
+ }
73386
+ function maskProfileValues(obj) {
73387
+ const result = {};
73388
+ for (const [key, value] of Object.entries(obj)) {
73389
+ if (SENSITIVE_KEY_PATTERN.test(key)) {
73390
+ result[key] = "***";
73391
+ } else if (typeof value === "string" && VAR_PATTERN.test(value)) {
73392
+ result[key] = "***";
73393
+ } else if (value !== null && typeof value === "object" && !Array.isArray(value)) {
73394
+ result[key] = maskProfileValues(value);
73395
+ } else {
73396
+ result[key] = value;
73397
+ }
73398
+ }
73399
+ return result;
73400
+ }
73401
+ async function profileUseCommand(profileName, startDir) {
73402
+ const configPath = join40(projectConfigDir(startDir), "config.json");
73403
+ const configFile = Bun.file(configPath);
73404
+ let existing = {};
73405
+ if (await configFile.exists()) {
73406
+ existing = await configFile.json();
73407
+ }
73408
+ if (profileName === "default") {
73409
+ const { profile: _removed, ...rest } = existing;
73410
+ await Bun.write(configPath, JSON.stringify(rest, null, 2));
73411
+ return "Profile reset to default.";
73412
+ }
73413
+ const updated = { ...existing, profile: profileName };
73414
+ await Bun.write(configPath, JSON.stringify(updated, null, 2));
73415
+ return `Now using profile: ${profileName}`;
73416
+ }
73417
+ async function profileCurrentCommand(startDir) {
73418
+ return resolveProfileName({}, _profileCLIDeps.env, startDir);
73419
+ }
73420
+ async function profileCreateCommand(profileName, startDir) {
73421
+ const profilesDir = join40(projectConfigDir(startDir), "profiles");
73422
+ const profilePath = join40(profilesDir, `${profileName}.json`);
73423
+ const profileFile = Bun.file(profilePath);
73424
+ if (await profileFile.exists()) {
73425
+ throw new Error(`Profile "${profileName}" already exists at ${profilePath}`);
73426
+ }
73427
+ mkdirSync4(profilesDir, { recursive: true });
73428
+ await Bun.write(profilePath, "{}");
73429
+ return profilePath;
73430
+ }
72888
73431
  // src/cli/agents.ts
72889
73432
  init_registry();
72890
73433
  init_version_detection();
@@ -72941,24 +73484,24 @@ async function diagnose(options) {
72941
73484
 
72942
73485
  // src/commands/logs.ts
72943
73486
  import { existsSync as existsSync30 } from "fs";
72944
- import { join as join42 } from "path";
73487
+ import { join as join44 } from "path";
72945
73488
 
72946
73489
  // src/commands/logs-formatter.ts
72947
73490
  init_source();
72948
73491
  init_formatter();
72949
- import { readdirSync as readdirSync7 } from "fs";
72950
- import { join as join41 } from "path";
73492
+ import { readdirSync as readdirSync9 } from "fs";
73493
+ import { join as join43 } from "path";
72951
73494
 
72952
73495
  // src/commands/logs-reader.ts
72953
- import { existsSync as existsSync29, readdirSync as readdirSync6 } from "fs";
73496
+ import { existsSync as existsSync29, readdirSync as readdirSync8 } from "fs";
72954
73497
  import { readdir as readdir3 } from "fs/promises";
72955
- import { join as join40 } from "path";
73498
+ import { join as join42 } from "path";
72956
73499
 
72957
73500
  // src/utils/paths.ts
72958
73501
  import { homedir as homedir4 } from "os";
72959
- import { join as join39 } from "path";
73502
+ import { join as join41 } from "path";
72960
73503
  function getRunsDir() {
72961
- return process.env.NAX_RUNS_DIR ?? join39(homedir4(), ".nax", "runs");
73504
+ return process.env.NAX_RUNS_DIR ?? join41(homedir4(), ".nax", "runs");
72962
73505
  }
72963
73506
 
72964
73507
  // src/commands/logs-reader.ts
@@ -72975,7 +73518,7 @@ async function resolveRunFileFromRegistry(runId) {
72975
73518
  }
72976
73519
  let matched = null;
72977
73520
  for (const entry of entries) {
72978
- const metaPath = join40(runsDir, entry, "meta.json");
73521
+ const metaPath = join42(runsDir, entry, "meta.json");
72979
73522
  try {
72980
73523
  const meta3 = await Bun.file(metaPath).json();
72981
73524
  if (meta3.runId === runId || meta3.runId.startsWith(runId)) {
@@ -72991,20 +73534,20 @@ async function resolveRunFileFromRegistry(runId) {
72991
73534
  console.log(`Log directory unavailable for run: ${runId}`);
72992
73535
  return null;
72993
73536
  }
72994
- const files = readdirSync6(matched.eventsDir).filter((f) => f.endsWith(".jsonl") && f !== "latest.jsonl").sort().reverse();
73537
+ const files = readdirSync8(matched.eventsDir).filter((f) => f.endsWith(".jsonl") && f !== "latest.jsonl").sort().reverse();
72995
73538
  if (files.length === 0) {
72996
73539
  console.log(`No log files found for run: ${runId}`);
72997
73540
  return null;
72998
73541
  }
72999
73542
  const specificFile = files.find((f) => f === `${matched.runId}.jsonl`);
73000
- return join40(matched.eventsDir, specificFile ?? files[0]);
73543
+ return join42(matched.eventsDir, specificFile ?? files[0]);
73001
73544
  }
73002
73545
  async function selectRunFile(runsDir) {
73003
- const files = readdirSync6(runsDir).filter((f) => f.endsWith(".jsonl") && f !== "latest.jsonl").sort().reverse();
73546
+ const files = readdirSync8(runsDir).filter((f) => f.endsWith(".jsonl") && f !== "latest.jsonl").sort().reverse();
73004
73547
  if (files.length === 0) {
73005
73548
  return null;
73006
73549
  }
73007
- return join40(runsDir, files[0]);
73550
+ return join42(runsDir, files[0]);
73008
73551
  }
73009
73552
  async function extractRunSummary(filePath) {
73010
73553
  const file3 = Bun.file(filePath);
@@ -73078,7 +73621,7 @@ var LOG_LEVEL_PRIORITY2 = {
73078
73621
  error: 3
73079
73622
  };
73080
73623
  async function displayRunsList(runsDir) {
73081
- const files = readdirSync7(runsDir).filter((f) => f.endsWith(".jsonl") && f !== "latest.jsonl").sort().reverse();
73624
+ const files = readdirSync9(runsDir).filter((f) => f.endsWith(".jsonl") && f !== "latest.jsonl").sort().reverse();
73082
73625
  if (files.length === 0) {
73083
73626
  console.log(source_default.dim("No runs found"));
73084
73627
  return;
@@ -73089,7 +73632,7 @@ Runs:
73089
73632
  console.log(source_default.gray(" Timestamp Stories Duration Cost Status"));
73090
73633
  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"));
73091
73634
  for (const file3 of files) {
73092
- const filePath = join41(runsDir, file3);
73635
+ const filePath = join43(runsDir, file3);
73093
73636
  const summary = await extractRunSummary(filePath);
73094
73637
  const timestamp = file3.replace(".jsonl", "");
73095
73638
  const stories = summary ? `${summary.passed}/${summary.total}` : "?/?";
@@ -73203,7 +73746,7 @@ async function logsCommand(options) {
73203
73746
  return;
73204
73747
  }
73205
73748
  const resolved = resolveProject({ dir: options.dir });
73206
- const naxDir = join42(resolved.projectDir, ".nax");
73749
+ const naxDir = join44(resolved.projectDir, ".nax");
73207
73750
  const configPath = resolved.configPath;
73208
73751
  const configFile = Bun.file(configPath);
73209
73752
  const config2 = await configFile.json();
@@ -73211,8 +73754,8 @@ async function logsCommand(options) {
73211
73754
  if (!featureName) {
73212
73755
  throw new Error("No feature specified in config.json");
73213
73756
  }
73214
- const featureDir = join42(naxDir, "features", featureName);
73215
- const runsDir = join42(featureDir, "runs");
73757
+ const featureDir = join44(naxDir, "features", featureName);
73758
+ const runsDir = join44(featureDir, "runs");
73216
73759
  if (!existsSync30(runsDir)) {
73217
73760
  throw new Error(`No runs directory found for feature: ${featureName}`);
73218
73761
  }
@@ -73237,7 +73780,7 @@ init_config();
73237
73780
  init_prd();
73238
73781
  init_precheck();
73239
73782
  import { existsSync as existsSync31 } from "fs";
73240
- import { join as join43 } from "path";
73783
+ import { join as join45 } from "path";
73241
73784
  async function precheckCommand(options) {
73242
73785
  const resolved = resolveProject({
73243
73786
  dir: options.dir,
@@ -73259,9 +73802,9 @@ async function precheckCommand(options) {
73259
73802
  process.exit(1);
73260
73803
  }
73261
73804
  }
73262
- const naxDir = join43(resolved.projectDir, ".nax");
73263
- const featureDir = join43(naxDir, "features", featureName);
73264
- const prdPath = join43(featureDir, "prd.json");
73805
+ const naxDir = join45(resolved.projectDir, ".nax");
73806
+ const featureDir = join45(naxDir, "features", featureName);
73807
+ const prdPath = join45(featureDir, "prd.json");
73265
73808
  if (!existsSync31(featureDir)) {
73266
73809
  console.error(source_default.red(`Feature not found: ${featureName}`));
73267
73810
  process.exit(1);
@@ -73283,7 +73826,7 @@ async function precheckCommand(options) {
73283
73826
  // src/commands/runs.ts
73284
73827
  init_source();
73285
73828
  import { readdir as readdir4 } from "fs/promises";
73286
- import { join as join44 } from "path";
73829
+ import { join as join46 } from "path";
73287
73830
  var DEFAULT_LIMIT = 20;
73288
73831
  var _runsCmdDeps = {
73289
73832
  getRunsDir
@@ -73338,7 +73881,7 @@ async function runsCommand(options = {}) {
73338
73881
  }
73339
73882
  const rows = [];
73340
73883
  for (const entry of entries) {
73341
- const metaPath = join44(runsDir, entry, "meta.json");
73884
+ const metaPath = join46(runsDir, entry, "meta.json");
73342
73885
  let meta3;
73343
73886
  try {
73344
73887
  meta3 = await Bun.file(metaPath).json();
@@ -73415,7 +73958,7 @@ async function runsCommand(options = {}) {
73415
73958
 
73416
73959
  // src/commands/unlock.ts
73417
73960
  init_source();
73418
- import { join as join45 } from "path";
73961
+ import { join as join47 } from "path";
73419
73962
  function isProcessAlive3(pid) {
73420
73963
  try {
73421
73964
  process.kill(pid, 0);
@@ -73430,7 +73973,7 @@ function formatLockAge(ageMs) {
73430
73973
  }
73431
73974
  async function unlockCommand(options) {
73432
73975
  const workdir = options.dir ?? process.cwd();
73433
- const lockPath = join45(workdir, "nax.lock");
73976
+ const lockPath = join47(workdir, "nax.lock");
73434
73977
  const lockFile = Bun.file(lockPath);
73435
73978
  const exists = await lockFile.exists();
73436
73979
  if (!exists) {
@@ -81253,15 +81796,15 @@ Next: nax generate --package ${options.package}`));
81253
81796
  }
81254
81797
  return;
81255
81798
  }
81256
- const naxDir = join56(workdir, ".nax");
81799
+ const naxDir = join58(workdir, ".nax");
81257
81800
  if (existsSync34(naxDir) && !options.force) {
81258
81801
  console.log(source_default.yellow("nax already initialized. Use --force to overwrite."));
81259
81802
  return;
81260
81803
  }
81261
- mkdirSync5(join56(naxDir, "features"), { recursive: true });
81262
- mkdirSync5(join56(naxDir, "hooks"), { recursive: true });
81263
- await Bun.write(join56(naxDir, "config.json"), JSON.stringify(DEFAULT_CONFIG, null, 2));
81264
- await Bun.write(join56(naxDir, "hooks.json"), JSON.stringify({
81804
+ mkdirSync6(join58(naxDir, "features"), { recursive: true });
81805
+ mkdirSync6(join58(naxDir, "hooks"), { recursive: true });
81806
+ await Bun.write(join58(naxDir, "config.json"), JSON.stringify(DEFAULT_CONFIG, null, 2));
81807
+ await Bun.write(join58(naxDir, "hooks.json"), JSON.stringify({
81265
81808
  hooks: {
81266
81809
  "on-start": { command: 'echo "nax started: $NAX_FEATURE"', enabled: false },
81267
81810
  "on-complete": { command: 'echo "nax complete: $NAX_FEATURE"', enabled: false },
@@ -81269,12 +81812,12 @@ Next: nax generate --package ${options.package}`));
81269
81812
  "on-error": { command: 'echo "nax error: $NAX_REASON"', enabled: false }
81270
81813
  }
81271
81814
  }, null, 2));
81272
- await Bun.write(join56(naxDir, ".gitignore"), `# nax temp files
81815
+ await Bun.write(join58(naxDir, ".gitignore"), `# nax temp files
81273
81816
  *.tmp
81274
81817
  .paused.json
81275
81818
  .nax-verifier-verdict.json
81276
81819
  `);
81277
- await Bun.write(join56(naxDir, "context.md"), `# Project Context
81820
+ await Bun.write(join58(naxDir, "context.md"), `# Project Context
81278
81821
 
81279
81822
  This document defines coding standards, architectural decisions, and forbidden patterns for this project.
81280
81823
  Run \`nax generate\` to regenerate agent config files (CLAUDE.md, AGENTS.md, .cursorrules, etc.) from this file.
@@ -81359,7 +81902,7 @@ Run \`nax generate\` to regenerate agent config files (CLAUDE.md, AGENTS.md, .cu
81359
81902
  console.log(source_default.dim(`
81360
81903
  Next: nax features create <name>`));
81361
81904
  });
81362
- 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).action(async (options) => {
81905
+ 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) => {
81363
81906
  let workdir;
81364
81907
  try {
81365
81908
  workdir = validateDirectory(options.dir);
@@ -81395,13 +81938,17 @@ program2.command("run").description("Run the orchestration loop for a feature").
81395
81938
  formatterMode = "quiet";
81396
81939
  }
81397
81940
  const naxDir = findProjectDir(workdir);
81398
- const config2 = await loadConfig(naxDir ?? undefined);
81941
+ const cliOverrides = {};
81942
+ if (options.profile) {
81943
+ cliOverrides.profile = options.profile;
81944
+ }
81945
+ const config2 = await loadConfig(naxDir ?? undefined, cliOverrides);
81399
81946
  if (!naxDir) {
81400
81947
  console.error(source_default.red("nax not initialized. Run: nax init"));
81401
81948
  process.exit(1);
81402
81949
  }
81403
- const featureDir = join56(naxDir, "features", options.feature);
81404
- const prdPath = join56(featureDir, "prd.json");
81950
+ const featureDir = join58(naxDir, "features", options.feature);
81951
+ const prdPath = join58(featureDir, "prd.json");
81405
81952
  if (options.plan && options.from) {
81406
81953
  if (existsSync34(prdPath) && !options.force) {
81407
81954
  console.error(source_default.red(`Error: prd.json already exists for feature "${options.feature}".`));
@@ -81423,10 +81970,10 @@ program2.command("run").description("Run the orchestration loop for a feature").
81423
81970
  }
81424
81971
  }
81425
81972
  try {
81426
- const planLogDir = join56(featureDir, "plan");
81427
- mkdirSync5(planLogDir, { recursive: true });
81973
+ const planLogDir = join58(featureDir, "plan");
81974
+ mkdirSync6(planLogDir, { recursive: true });
81428
81975
  const planLogId = new Date().toISOString().replace(/:/g, "-").replace(/\..+/, "");
81429
- const planLogPath = join56(planLogDir, `${planLogId}.jsonl`);
81976
+ const planLogPath = join58(planLogDir, `${planLogId}.jsonl`);
81430
81977
  initLogger({ level: "info", filePath: planLogPath, useChalk: false, headless: true });
81431
81978
  console.log(source_default.dim(` [Plan log: ${planLogPath}]`));
81432
81979
  console.log(source_default.dim(" [Planning phase: generating PRD from spec]"));
@@ -81470,10 +82017,10 @@ program2.command("run").description("Run the orchestration loop for a feature").
81470
82017
  process.exit(1);
81471
82018
  }
81472
82019
  resetLogger();
81473
- const runsDir = join56(featureDir, "runs");
81474
- mkdirSync5(runsDir, { recursive: true });
82020
+ const runsDir = join58(featureDir, "runs");
82021
+ mkdirSync6(runsDir, { recursive: true });
81475
82022
  const runId = new Date().toISOString().replace(/:/g, "-").replace(/\..+/, "");
81476
- const logFilePath = join56(runsDir, `${runId}.jsonl`);
82023
+ const logFilePath = join58(runsDir, `${runId}.jsonl`);
81477
82024
  const isTTY = process.stdout.isTTY ?? false;
81478
82025
  const headlessFlag = options.headless ?? false;
81479
82026
  const headlessEnv = process.env.NAX_HEADLESS === "1";
@@ -81489,7 +82036,7 @@ program2.command("run").description("Run the orchestration loop for a feature").
81489
82036
  config2.autoMode.defaultAgent = options.agent;
81490
82037
  }
81491
82038
  config2.execution.maxIterations = Number.parseInt(options.maxIterations, 10);
81492
- const globalNaxDir = join56(homedir8(), ".nax");
82039
+ const globalNaxDir = join58(homedir8(), ".nax");
81493
82040
  const hooks = await loadHooksConfig(naxDir, globalNaxDir);
81494
82041
  const eventEmitter = new PipelineEventEmitter;
81495
82042
  let tuiInstance;
@@ -81512,7 +82059,7 @@ program2.command("run").description("Run the orchestration loop for a feature").
81512
82059
  } else {
81513
82060
  console.log(source_default.dim(" [Headless mode \u2014 pipe output]"));
81514
82061
  }
81515
- const statusFilePath = join56(workdir, ".nax", "status.json");
82062
+ const statusFilePath = join58(workdir, ".nax", "status.json");
81516
82063
  let parallel;
81517
82064
  if (options.parallel !== undefined) {
81518
82065
  parallel = Number.parseInt(options.parallel, 10);
@@ -81538,7 +82085,7 @@ program2.command("run").description("Run the orchestration loop for a feature").
81538
82085
  headless: useHeadless,
81539
82086
  skipPrecheck: options.skipPrecheck ?? false
81540
82087
  });
81541
- const latestSymlink = join56(runsDir, "latest.jsonl");
82088
+ const latestSymlink = join58(runsDir, "latest.jsonl");
81542
82089
  try {
81543
82090
  if (existsSync34(latestSymlink)) {
81544
82091
  Bun.spawnSync(["rm", latestSymlink]);
@@ -81576,9 +82123,9 @@ features.command("create <name>").description("Create a new feature").option("-d
81576
82123
  console.error(source_default.red("nax not initialized. Run: nax init"));
81577
82124
  process.exit(1);
81578
82125
  }
81579
- const featureDir = join56(naxDir, "features", name);
81580
- mkdirSync5(featureDir, { recursive: true });
81581
- await Bun.write(join56(featureDir, "spec.md"), `# Feature: ${name}
82126
+ const featureDir = join58(naxDir, "features", name);
82127
+ mkdirSync6(featureDir, { recursive: true });
82128
+ await Bun.write(join58(featureDir, "spec.md"), `# Feature: ${name}
81582
82129
 
81583
82130
  ## Overview
81584
82131
 
@@ -81611,7 +82158,7 @@ features.command("create <name>").description("Create a new feature").option("-d
81611
82158
 
81612
82159
  <!-- What this feature explicitly does NOT cover. -->
81613
82160
  `);
81614
- await Bun.write(join56(featureDir, "progress.txt"), `# Progress: ${name}
82161
+ await Bun.write(join58(featureDir, "progress.txt"), `# Progress: ${name}
81615
82162
 
81616
82163
  Created: ${new Date().toISOString()}
81617
82164
 
@@ -81637,13 +82184,13 @@ features.command("list").description("List all features").option("-d, --dir <pat
81637
82184
  console.error(source_default.red("nax not initialized."));
81638
82185
  process.exit(1);
81639
82186
  }
81640
- const featuresDir = join56(naxDir, "features");
82187
+ const featuresDir = join58(naxDir, "features");
81641
82188
  if (!existsSync34(featuresDir)) {
81642
82189
  console.log(source_default.dim("No features yet."));
81643
82190
  return;
81644
82191
  }
81645
- const { readdirSync: readdirSync8 } = await import("fs");
81646
- const entries = readdirSync8(featuresDir, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => e.name);
82192
+ const { readdirSync: readdirSync10 } = await import("fs");
82193
+ const entries = readdirSync10(featuresDir, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => e.name);
81647
82194
  if (entries.length === 0) {
81648
82195
  console.log(source_default.dim("No features yet."));
81649
82196
  return;
@@ -81652,7 +82199,7 @@ features.command("list").description("List all features").option("-d, --dir <pat
81652
82199
  Features:
81653
82200
  `));
81654
82201
  for (const name of entries) {
81655
- const prdPath = join56(featuresDir, name, "prd.json");
82202
+ const prdPath = join58(featuresDir, name, "prd.json");
81656
82203
  if (existsSync34(prdPath)) {
81657
82204
  const prd = await loadPRD(prdPath);
81658
82205
  const c = countStories(prd);
@@ -81663,7 +82210,7 @@ Features:
81663
82210
  }
81664
82211
  console.log();
81665
82212
  });
81666
- program2.command("plan [description]").description("Generate prd.json from a spec file via LLM one-shot call (replaces deprecated 'nax analyze')").option("--from <spec-path>", "Path to spec file (required unless --decompose is used)").requiredOption("-f, --feature <name>", "Feature name (required)").option("--auto", "Run in one-shot LLM mode (alias: --one-shot)", false).option("--one-shot", "Run in one-shot LLM mode (alias: --auto)", false).option("-b, --branch <branch>", "Override default branch name").option("-d, --dir <path>", "Project directory", process.cwd()).option("--decompose <storyId>", "Decompose an existing story into sub-stories").action(async (description, options) => {
82213
+ program2.command("plan [description]").description("Generate prd.json from a spec file via LLM one-shot call (replaces deprecated 'nax analyze')").option("--from <spec-path>", "Path to spec file (required unless --decompose is used)").requiredOption("-f, --feature <name>", "Feature name (required)").option("--auto", "Run in one-shot LLM mode (alias: --one-shot)", false).option("--one-shot", "Run in one-shot LLM mode (alias: --auto)", false).option("-b, --branch <branch>", "Override default branch name").option("-d, --dir <path>", "Project directory", process.cwd()).option("--decompose <storyId>", "Decompose an existing story into sub-stories").option("--profile <name>", "Profile to use (overrides config.json profile)").action(async (description, options) => {
81667
82214
  if (description) {
81668
82215
  console.error(source_default.red(`Error: Positional args removed in plan v2.
81669
82216
 
@@ -81682,11 +82229,15 @@ Use: nax plan -f <feature> --from <spec>`));
81682
82229
  console.error(source_default.red("nax not initialized. Run: nax init"));
81683
82230
  process.exit(1);
81684
82231
  }
81685
- const config2 = await loadConfig(workdir);
81686
- const featureLogDir = join56(naxDir, "features", options.feature, "plan");
81687
- mkdirSync5(featureLogDir, { recursive: true });
82232
+ const cliOverrides = {};
82233
+ if (options.profile) {
82234
+ cliOverrides.profile = options.profile;
82235
+ }
82236
+ const config2 = await loadConfig(workdir, cliOverrides);
82237
+ const featureLogDir = join58(naxDir, "features", options.feature, "plan");
82238
+ mkdirSync6(featureLogDir, { recursive: true });
81688
82239
  const planLogId = new Date().toISOString().replace(/:/g, "-").replace(/\..+/, "");
81689
- const planLogPath = join56(featureLogDir, `${planLogId}.jsonl`);
82240
+ const planLogPath = join58(featureLogDir, `${planLogId}.jsonl`);
81690
82241
  initLogger({ level: "info", filePath: planLogPath, useChalk: false, headless: true });
81691
82242
  console.log(source_default.dim(` [Plan log: ${planLogPath}]`));
81692
82243
  try {
@@ -81737,7 +82288,7 @@ program2.command("analyze").description("(deprecated) Parse spec.md into prd.jso
81737
82288
  console.error(source_default.red("nax not initialized. Run: nax init"));
81738
82289
  process.exit(1);
81739
82290
  }
81740
- const featureDir = join56(naxDir, "features", options.feature);
82291
+ const featureDir = join58(naxDir, "features", options.feature);
81741
82292
  if (!existsSync34(featureDir)) {
81742
82293
  console.error(source_default.red(`Feature "${options.feature}" not found.`));
81743
82294
  process.exit(1);
@@ -81753,7 +82304,7 @@ program2.command("analyze").description("(deprecated) Parse spec.md into prd.jso
81753
82304
  specPath: options.from,
81754
82305
  reclassify: options.reclassify
81755
82306
  });
81756
- const prdPath = join56(featureDir, "prd.json");
82307
+ const prdPath = join58(featureDir, "prd.json");
81757
82308
  await Bun.write(prdPath, JSON.stringify(prd, null, 2));
81758
82309
  const c = countStories(prd);
81759
82310
  console.log(source_default.green(`
@@ -81786,7 +82337,7 @@ program2.command("agents").description("List available coding agents with status
81786
82337
  process.exit(1);
81787
82338
  }
81788
82339
  });
81789
- program2.command("config").description("Display effective merged configuration").option("-d, --dir <path>", "Project directory", process.cwd()).option("--explain", "Show detailed field descriptions", false).option("--diff", "Show only fields where project overrides global", false).action(async (options) => {
82340
+ var configCmd = program2.command("config").description("Display effective merged configuration").option("-d, --dir <path>", "Project directory", process.cwd()).option("--explain", "Show detailed field descriptions", false).option("--diff", "Show only fields where project overrides global", false).action(async (options) => {
81790
82341
  let workdir;
81791
82342
  try {
81792
82343
  workdir = validateDirectory(options.dir);
@@ -81803,6 +82354,52 @@ program2.command("config").description("Display effective merged configuration")
81803
82354
  process.exit(1);
81804
82355
  }
81805
82356
  });
82357
+ var configProfileCmd = configCmd.command("profile").description("Manage config profiles");
82358
+ configProfileCmd.command("list").description("List all available profiles grouped by scope").option("-d, --dir <path>", "Project directory", process.cwd()).action(async (options) => {
82359
+ try {
82360
+ const output = await profileListCommand(options.dir);
82361
+ console.log(output);
82362
+ } catch (err) {
82363
+ console.error(source_default.red(`Error: ${err.message}`));
82364
+ process.exit(1);
82365
+ }
82366
+ });
82367
+ configProfileCmd.command("show <name>").description("Show resolved profile JSON").option("-d, --dir <path>", "Project directory", process.cwd()).option("--unmask", "Show raw values including secrets", false).action(async (name, options) => {
82368
+ try {
82369
+ const output = await profileShowCommand(name, options.dir, { unmask: options.unmask });
82370
+ console.log(output);
82371
+ } catch (err) {
82372
+ console.error(source_default.red(`Error: ${err.message}`));
82373
+ process.exit(1);
82374
+ }
82375
+ });
82376
+ configProfileCmd.command("use <name>").description("Set the active profile (use 'default' to clear)").option("-d, --dir <path>", "Project directory", process.cwd()).action(async (name, options) => {
82377
+ try {
82378
+ const msg = await profileUseCommand(name, options.dir);
82379
+ console.log(msg);
82380
+ } catch (err) {
82381
+ console.error(source_default.red(`Error: ${err.message}`));
82382
+ process.exit(1);
82383
+ }
82384
+ });
82385
+ configProfileCmd.command("current").description("Show the currently active profile name").option("-d, --dir <path>", "Project directory", process.cwd()).action(async (options) => {
82386
+ try {
82387
+ const name = await profileCurrentCommand(options.dir);
82388
+ console.log(name);
82389
+ } catch (err) {
82390
+ console.error(source_default.red(`Error: ${err.message}`));
82391
+ process.exit(1);
82392
+ }
82393
+ });
82394
+ configProfileCmd.command("create <name>").description("Create a new empty profile").option("-d, --dir <path>", "Project directory", process.cwd()).action(async (name, options) => {
82395
+ try {
82396
+ const path19 = await profileCreateCommand(name, options.dir);
82397
+ console.log(`Created profile at: ${path19}`);
82398
+ } catch (err) {
82399
+ console.error(source_default.red(`Error: ${err.message}`));
82400
+ process.exit(1);
82401
+ }
82402
+ });
81806
82403
  program2.command("status").description("Show current run status").option("-f, --feature <name>", "Feature name").option("-d, --dir <path>", "Project directory", process.cwd()).option("--cost", "Show cost metrics across all runs", false).option("--last", "Show last run metrics (requires --cost)", false).option("--model", "Show per-model efficiency (requires --cost)", false).action(async (options) => {
81807
82404
  let workdir;
81808
82405
  try {