@nathapp/nax 0.50.2 → 0.51.0
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.
- package/CHANGELOG.md +30 -0
- package/dist/nax.js +579 -373
- package/package.json +1 -3
- package/bin/nax.ts +0 -1195
- package/src/acceptance/fix-generator.ts +0 -322
- package/src/acceptance/generator.ts +0 -423
- package/src/acceptance/index.ts +0 -42
- package/src/acceptance/refinement.ts +0 -224
- package/src/acceptance/templates/cli.ts +0 -47
- package/src/acceptance/templates/component.ts +0 -78
- package/src/acceptance/templates/e2e.ts +0 -43
- package/src/acceptance/templates/index.ts +0 -21
- package/src/acceptance/templates/snapshot.ts +0 -50
- package/src/acceptance/templates/unit.ts +0 -48
- package/src/acceptance/types.ts +0 -135
- package/src/agents/acp/adapter.ts +0 -888
- package/src/agents/acp/cost.ts +0 -9
- package/src/agents/acp/index.ts +0 -7
- package/src/agents/acp/interaction-bridge.ts +0 -126
- package/src/agents/acp/parser.ts +0 -119
- package/src/agents/acp/spawn-client.ts +0 -373
- package/src/agents/acp/types.ts +0 -22
- package/src/agents/aider/adapter.ts +0 -135
- package/src/agents/claude/adapter.ts +0 -258
- package/src/agents/claude/complete.ts +0 -80
- package/src/agents/claude/cost.ts +0 -16
- package/src/agents/claude/execution.ts +0 -215
- package/src/agents/claude/index.ts +0 -3
- package/src/agents/claude/interactive.ts +0 -77
- package/src/agents/claude/plan.ts +0 -179
- package/src/agents/codex/adapter.ts +0 -153
- package/src/agents/cost/calculate.ts +0 -154
- package/src/agents/cost/index.ts +0 -10
- package/src/agents/cost/parse.ts +0 -97
- package/src/agents/cost/pricing.ts +0 -59
- package/src/agents/cost/types.ts +0 -45
- package/src/agents/gemini/adapter.ts +0 -177
- package/src/agents/index.ts +0 -18
- package/src/agents/opencode/adapter.ts +0 -106
- package/src/agents/registry.ts +0 -136
- package/src/agents/shared/decompose.ts +0 -154
- package/src/agents/shared/model-resolution.ts +0 -43
- package/src/agents/shared/types-extended.ts +0 -164
- package/src/agents/shared/validation.ts +0 -69
- package/src/agents/shared/version-detection.ts +0 -109
- package/src/agents/types.ts +0 -205
- package/src/analyze/classifier.ts +0 -282
- package/src/analyze/index.ts +0 -16
- package/src/analyze/scanner.ts +0 -171
- package/src/analyze/types.ts +0 -51
- package/src/cli/accept.ts +0 -108
- package/src/cli/agents.ts +0 -87
- package/src/cli/analyze-parser.ts +0 -291
- package/src/cli/analyze.ts +0 -352
- package/src/cli/config-descriptions.ts +0 -218
- package/src/cli/config-diff.ts +0 -103
- package/src/cli/config-display.ts +0 -285
- package/src/cli/config-get.ts +0 -55
- package/src/cli/config.ts +0 -14
- package/src/cli/constitution.ts +0 -17
- package/src/cli/diagnose-analysis.ts +0 -159
- package/src/cli/diagnose-formatter.ts +0 -87
- package/src/cli/diagnose.ts +0 -203
- package/src/cli/generate.ts +0 -250
- package/src/cli/index.ts +0 -42
- package/src/cli/init-context.ts +0 -405
- package/src/cli/init-detect.ts +0 -303
- package/src/cli/init.ts +0 -296
- package/src/cli/interact.ts +0 -295
- package/src/cli/plan.ts +0 -509
- package/src/cli/plugins.ts +0 -122
- package/src/cli/prompts-export.ts +0 -58
- package/src/cli/prompts-init.ts +0 -200
- package/src/cli/prompts-main.ts +0 -183
- package/src/cli/prompts-shared.ts +0 -70
- package/src/cli/prompts-tdd.ts +0 -88
- package/src/cli/prompts.ts +0 -17
- package/src/cli/runs.ts +0 -174
- package/src/cli/status-cost.ts +0 -151
- package/src/cli/status-features.ts +0 -405
- package/src/cli/status.ts +0 -13
- package/src/commands/common.ts +0 -171
- package/src/commands/diagnose.ts +0 -17
- package/src/commands/index.ts +0 -9
- package/src/commands/logs-formatter.ts +0 -201
- package/src/commands/logs-reader.ts +0 -171
- package/src/commands/logs.ts +0 -103
- package/src/commands/precheck.ts +0 -86
- package/src/commands/runs.ts +0 -220
- package/src/commands/unlock.ts +0 -96
- package/src/config/defaults.ts +0 -217
- package/src/config/index.ts +0 -22
- package/src/config/loader.ts +0 -143
- package/src/config/merge.ts +0 -106
- package/src/config/merger.ts +0 -147
- package/src/config/path-security.ts +0 -121
- package/src/config/paths.ts +0 -27
- package/src/config/permissions.ts +0 -63
- package/src/config/runtime-types.ts +0 -520
- package/src/config/schema-types.ts +0 -53
- package/src/config/schema.ts +0 -60
- package/src/config/schemas.ts +0 -425
- package/src/config/test-strategy.ts +0 -71
- package/src/config/types.ts +0 -57
- package/src/config/validate.ts +0 -103
- package/src/constitution/generator.ts +0 -158
- package/src/constitution/generators/aider.ts +0 -41
- package/src/constitution/generators/claude.ts +0 -35
- package/src/constitution/generators/cursor.ts +0 -36
- package/src/constitution/generators/opencode.ts +0 -38
- package/src/constitution/generators/types.ts +0 -33
- package/src/constitution/generators/windsurf.ts +0 -36
- package/src/constitution/index.ts +0 -11
- package/src/constitution/loader.ts +0 -121
- package/src/constitution/types.ts +0 -31
- package/src/context/auto-detect.ts +0 -228
- package/src/context/builder.ts +0 -299
- package/src/context/elements.ts +0 -122
- package/src/context/formatter.ts +0 -107
- package/src/context/generator.ts +0 -343
- package/src/context/generators/aider.ts +0 -34
- package/src/context/generators/claude.ts +0 -28
- package/src/context/generators/codex.ts +0 -28
- package/src/context/generators/cursor.ts +0 -28
- package/src/context/generators/gemini.ts +0 -28
- package/src/context/generators/opencode.ts +0 -30
- package/src/context/generators/windsurf.ts +0 -28
- package/src/context/greenfield.ts +0 -114
- package/src/context/index.ts +0 -34
- package/src/context/injector.ts +0 -279
- package/src/context/parent-context.ts +0 -39
- package/src/context/test-scanner.ts +0 -370
- package/src/context/types.ts +0 -98
- package/src/decompose/apply.ts +0 -50
- package/src/decompose/builder.ts +0 -181
- package/src/decompose/index.ts +0 -8
- package/src/decompose/sections/codebase.ts +0 -26
- package/src/decompose/sections/constraints.ts +0 -32
- package/src/decompose/sections/index.ts +0 -4
- package/src/decompose/sections/sibling-stories.ts +0 -25
- package/src/decompose/sections/target-story.ts +0 -31
- package/src/decompose/types.ts +0 -55
- package/src/decompose/validators/complexity.ts +0 -45
- package/src/decompose/validators/coverage.ts +0 -134
- package/src/decompose/validators/dependency.ts +0 -91
- package/src/decompose/validators/index.ts +0 -35
- package/src/decompose/validators/overlap.ts +0 -128
- package/src/errors.ts +0 -67
- package/src/execution/batching.ts +0 -157
- package/src/execution/crash-heartbeat.ts +0 -77
- package/src/execution/crash-recovery.ts +0 -79
- package/src/execution/crash-signals.ts +0 -165
- package/src/execution/crash-writer.ts +0 -154
- package/src/execution/deferred-review.ts +0 -105
- package/src/execution/dry-run.ts +0 -81
- package/src/execution/escalation/escalation.ts +0 -46
- package/src/execution/escalation/index.ts +0 -13
- package/src/execution/escalation/tier-escalation.ts +0 -346
- package/src/execution/escalation/tier-outcome.ts +0 -143
- package/src/execution/executor-types.ts +0 -73
- package/src/execution/helpers.ts +0 -38
- package/src/execution/index.ts +0 -27
- package/src/execution/iteration-runner.ts +0 -160
- package/src/execution/lifecycle/acceptance-loop.ts +0 -280
- package/src/execution/lifecycle/headless-formatter.ts +0 -83
- package/src/execution/lifecycle/index.ts +0 -11
- package/src/execution/lifecycle/parallel-lifecycle.ts +0 -101
- package/src/execution/lifecycle/precheck-runner.ts +0 -140
- package/src/execution/lifecycle/run-cleanup.ts +0 -81
- package/src/execution/lifecycle/run-completion.ts +0 -247
- package/src/execution/lifecycle/run-initialization.ts +0 -187
- package/src/execution/lifecycle/run-regression.ts +0 -305
- package/src/execution/lifecycle/run-setup.ts +0 -240
- package/src/execution/lifecycle/story-size-prompts.ts +0 -123
- package/src/execution/lock.ts +0 -129
- package/src/execution/parallel-coordinator.ts +0 -281
- package/src/execution/parallel-executor-rectification-pass.ts +0 -117
- package/src/execution/parallel-executor-rectify.ts +0 -136
- package/src/execution/parallel-executor.ts +0 -330
- package/src/execution/parallel-worker.ts +0 -149
- package/src/execution/parallel.ts +0 -13
- package/src/execution/pid-registry.ts +0 -275
- package/src/execution/pipeline-result-handler.ts +0 -221
- package/src/execution/progress.ts +0 -27
- package/src/execution/queue-handler.ts +0 -109
- package/src/execution/runner-completion.ts +0 -171
- package/src/execution/runner-execution.ts +0 -243
- package/src/execution/runner-setup.ts +0 -86
- package/src/execution/runner.ts +0 -265
- package/src/execution/sequential-executor.ts +0 -219
- package/src/execution/status-file.ts +0 -264
- package/src/execution/status-writer.ts +0 -181
- package/src/execution/story-context.ts +0 -266
- package/src/execution/story-selector.ts +0 -76
- package/src/execution/test-output-parser.ts +0 -14
- package/src/execution/timeout-handler.ts +0 -100
- package/src/hooks/index.ts +0 -2
- package/src/hooks/runner.ts +0 -280
- package/src/hooks/types.ts +0 -79
- package/src/interaction/chain.ts +0 -170
- package/src/interaction/index.ts +0 -61
- package/src/interaction/init.ts +0 -84
- package/src/interaction/plugins/auto.ts +0 -243
- package/src/interaction/plugins/cli.ts +0 -300
- package/src/interaction/plugins/telegram.ts +0 -384
- package/src/interaction/plugins/webhook.ts +0 -286
- package/src/interaction/state.ts +0 -171
- package/src/interaction/triggers.ts +0 -250
- package/src/interaction/types.ts +0 -170
- package/src/logger/formatters.ts +0 -84
- package/src/logger/index.ts +0 -16
- package/src/logger/logger.ts +0 -296
- package/src/logger/types.ts +0 -48
- package/src/logging/formatter.ts +0 -355
- package/src/logging/index.ts +0 -22
- package/src/logging/types.ts +0 -93
- package/src/metrics/aggregator.ts +0 -191
- package/src/metrics/index.ts +0 -14
- package/src/metrics/tracker.ts +0 -200
- package/src/metrics/types.ts +0 -115
- package/src/optimizer/index.ts +0 -63
- package/src/optimizer/noop.optimizer.ts +0 -24
- package/src/optimizer/rule-based.optimizer.ts +0 -248
- package/src/optimizer/types.ts +0 -53
- package/src/pipeline/event-bus.ts +0 -297
- package/src/pipeline/events.ts +0 -130
- package/src/pipeline/index.ts +0 -19
- package/src/pipeline/runner.ts +0 -149
- package/src/pipeline/stages/acceptance-setup.ts +0 -140
- package/src/pipeline/stages/acceptance.ts +0 -215
- package/src/pipeline/stages/autofix.ts +0 -262
- package/src/pipeline/stages/completion.ts +0 -110
- package/src/pipeline/stages/constitution.ts +0 -63
- package/src/pipeline/stages/context.ts +0 -122
- package/src/pipeline/stages/execution.ts +0 -359
- package/src/pipeline/stages/index.ts +0 -86
- package/src/pipeline/stages/optimizer.ts +0 -74
- package/src/pipeline/stages/prompt.ts +0 -79
- package/src/pipeline/stages/queue-check.ts +0 -103
- package/src/pipeline/stages/rectify.ts +0 -101
- package/src/pipeline/stages/regression.ts +0 -99
- package/src/pipeline/stages/review.ts +0 -94
- package/src/pipeline/stages/routing.ts +0 -276
- package/src/pipeline/stages/verify.ts +0 -286
- package/src/pipeline/subscribers/events-writer.ts +0 -135
- package/src/pipeline/subscribers/hooks.ts +0 -179
- package/src/pipeline/subscribers/interaction.ts +0 -103
- package/src/pipeline/subscribers/registry.ts +0 -73
- package/src/pipeline/subscribers/reporters.ts +0 -174
- package/src/pipeline/types.ts +0 -220
- package/src/plugins/extensions.ts +0 -225
- package/src/plugins/index.ts +0 -33
- package/src/plugins/loader.ts +0 -352
- package/src/plugins/plugin-logger.ts +0 -41
- package/src/plugins/registry.ts +0 -168
- package/src/plugins/types.ts +0 -206
- package/src/plugins/validator.ts +0 -352
- package/src/prd/index.ts +0 -220
- package/src/prd/schema.ts +0 -268
- package/src/prd/types.ts +0 -273
- package/src/prd/validate.ts +0 -41
- package/src/precheck/checks-agents.ts +0 -63
- package/src/precheck/checks-blockers.ts +0 -23
- package/src/precheck/checks-cli.ts +0 -68
- package/src/precheck/checks-config.ts +0 -102
- package/src/precheck/checks-git.ts +0 -117
- package/src/precheck/checks-system.ts +0 -101
- package/src/precheck/checks-warnings.ts +0 -221
- package/src/precheck/checks.ts +0 -36
- package/src/precheck/index.ts +0 -374
- package/src/precheck/story-size-gate.ts +0 -144
- package/src/precheck/types.ts +0 -31
- package/src/prompts/builder.ts +0 -166
- package/src/prompts/index.ts +0 -2
- package/src/prompts/loader.ts +0 -43
- package/src/prompts/sections/conventions.ts +0 -19
- package/src/prompts/sections/hermetic.ts +0 -41
- package/src/prompts/sections/index.ts +0 -12
- package/src/prompts/sections/isolation.ts +0 -70
- package/src/prompts/sections/role-task.ts +0 -182
- package/src/prompts/sections/story.ts +0 -55
- package/src/prompts/sections/verdict.ts +0 -70
- package/src/prompts/types.ts +0 -21
- package/src/queue/index.ts +0 -2
- package/src/queue/manager.ts +0 -254
- package/src/queue/types.ts +0 -54
- package/src/review/index.ts +0 -8
- package/src/review/orchestrator.ts +0 -154
- package/src/review/runner.ts +0 -303
- package/src/review/types.ts +0 -70
- package/src/routing/batch-route.ts +0 -35
- package/src/routing/builder.ts +0 -81
- package/src/routing/chain.ts +0 -75
- package/src/routing/content-hash.ts +0 -25
- package/src/routing/index.ts +0 -20
- package/src/routing/loader.ts +0 -62
- package/src/routing/router.ts +0 -305
- package/src/routing/strategies/adaptive.ts +0 -215
- package/src/routing/strategies/index.ts +0 -8
- package/src/routing/strategies/keyword.ts +0 -180
- package/src/routing/strategies/llm-prompts.ts +0 -224
- package/src/routing/strategies/llm.ts +0 -320
- package/src/routing/strategies/manual.ts +0 -50
- package/src/routing/strategy.ts +0 -102
- package/src/tdd/cleanup.ts +0 -120
- package/src/tdd/index.ts +0 -22
- package/src/tdd/isolation.ts +0 -117
- package/src/tdd/orchestrator.ts +0 -406
- package/src/tdd/prompts.ts +0 -40
- package/src/tdd/rectification-gate.ts +0 -274
- package/src/tdd/session-runner.ts +0 -263
- package/src/tdd/types.ts +0 -84
- package/src/tdd/verdict-reader.ts +0 -266
- package/src/tdd/verdict.ts +0 -152
- package/src/tui/App.tsx +0 -265
- package/src/tui/components/AgentPanel.tsx +0 -75
- package/src/tui/components/CostOverlay.tsx +0 -118
- package/src/tui/components/HelpOverlay.tsx +0 -107
- package/src/tui/components/StatusBar.tsx +0 -63
- package/src/tui/components/StoriesPanel.tsx +0 -177
- package/src/tui/hooks/useKeyboard.ts +0 -142
- package/src/tui/hooks/useLayout.ts +0 -137
- package/src/tui/hooks/usePipelineEvents.ts +0 -183
- package/src/tui/hooks/usePty.ts +0 -189
- package/src/tui/index.tsx +0 -38
- package/src/tui/types.ts +0 -76
- package/src/utils/errors.ts +0 -12
- package/src/utils/git.ts +0 -245
- package/src/utils/json-file.ts +0 -72
- package/src/utils/log-test-output.ts +0 -25
- package/src/utils/path-security.ts +0 -73
- package/src/utils/queue-writer.ts +0 -54
- package/src/verification/crash-detector.ts +0 -34
- package/src/verification/executor.ts +0 -250
- package/src/verification/index.ts +0 -12
- package/src/verification/orchestrator-types.ts +0 -154
- package/src/verification/orchestrator.ts +0 -76
- package/src/verification/parser.ts +0 -220
- package/src/verification/rectification-loop.ts +0 -172
- package/src/verification/rectification.ts +0 -108
- package/src/verification/runners.ts +0 -129
- package/src/verification/smart-runner.ts +0 -307
- package/src/verification/strategies/acceptance.ts +0 -136
- package/src/verification/strategies/regression.ts +0 -90
- package/src/verification/strategies/scoped.ts +0 -154
- package/src/verification/types.ts +0 -117
- package/src/version.ts +0 -40
- package/src/worktree/dispatcher.ts +0 -6
- package/src/worktree/index.ts +0 -2
- package/src/worktree/manager.ts +0 -193
- package/src/worktree/merge.ts +0 -302
- package/src/worktree/types.ts +0 -4
package/dist/nax.js
CHANGED
|
@@ -3246,6 +3246,8 @@ function resolveTestStrategy(raw) {
|
|
|
3246
3246
|
return "test-after";
|
|
3247
3247
|
if (VALID_TEST_STRATEGIES.includes(raw))
|
|
3248
3248
|
return raw;
|
|
3249
|
+
if (raw === "none")
|
|
3250
|
+
return "no-test";
|
|
3249
3251
|
if (raw === "tdd")
|
|
3250
3252
|
return "tdd-simple";
|
|
3251
3253
|
if (raw === "three-session")
|
|
@@ -3256,6 +3258,9 @@ function resolveTestStrategy(raw) {
|
|
|
3256
3258
|
}
|
|
3257
3259
|
var VALID_TEST_STRATEGIES, COMPLEXITY_GUIDE = `## Complexity Classification Guide
|
|
3258
3260
|
|
|
3261
|
+
- no-test: Config-only changes, documentation, CI/build files, dependency bumps, pure refactors
|
|
3262
|
+
with NO behavioral change. MUST include noTestJustification explaining why tests are unnecessary.
|
|
3263
|
+
If any user-facing behavior changes, use tdd-simple or higher.
|
|
3259
3264
|
- simple: \u226450 LOC, single-file change, purely additive, no new dependencies \u2192 tdd-simple
|
|
3260
3265
|
- medium: 50\u2013200 LOC, 2\u20135 files, standard patterns, clear requirements \u2192 three-session-tdd-lite
|
|
3261
3266
|
- complex: 200\u2013500 LOC, multiple modules, new abstractions or integrations \u2192 three-session-tdd
|
|
@@ -3266,6 +3271,9 @@ var VALID_TEST_STRATEGIES, COMPLEXITY_GUIDE = `## Complexity Classification Guid
|
|
|
3266
3271
|
Security-critical functions (authentication, cryptography, tokens, sessions, credentials,
|
|
3267
3272
|
password hashing, access control) must use three-session-tdd regardless of complexity.`, TEST_STRATEGY_GUIDE = `## Test Strategy Guide
|
|
3268
3273
|
|
|
3274
|
+
- no-test: Stories with zero behavioral change \u2014 config files, documentation, CI/build changes,
|
|
3275
|
+
dependency bumps, pure structural refactors. REQUIRES noTestJustification field. If any runtime
|
|
3276
|
+
behavior changes, use tdd-simple or higher. When in doubt, use tdd-simple.
|
|
3269
3277
|
- tdd-simple: Simple stories (\u226450 LOC). Write failing tests first, then implement to pass them \u2014 all in one session.
|
|
3270
3278
|
- three-session-tdd-lite: Medium stories, or complex stories involving UI/CLI/integration. 3 sessions: (1) test-writer writes failing tests and may create minimal src/ stubs for imports, (2) implementer makes tests pass and may replace stubs, (3) verifier confirms correctness.
|
|
3271
3279
|
- three-session-tdd: Complex/expert stories or security-critical code. 3 sessions with strict isolation: (1) test-writer writes failing tests \u2014 no src/ changes allowed, (2) implementer makes them pass without modifying test files, (3) verifier confirms correctness.
|
|
@@ -3283,6 +3291,7 @@ password hashing, access control) must use three-session-tdd regardless of compl
|
|
|
3283
3291
|
- Aim for coherent units of value. Maximum recommended stories: 10-15 per feature.`;
|
|
3284
3292
|
var init_test_strategy = __esm(() => {
|
|
3285
3293
|
VALID_TEST_STRATEGIES = [
|
|
3294
|
+
"no-test",
|
|
3286
3295
|
"test-after",
|
|
3287
3296
|
"tdd-simple",
|
|
3288
3297
|
"three-session-tdd",
|
|
@@ -17874,7 +17883,8 @@ var init_schemas3 = __esm(() => {
|
|
|
17874
17883
|
refinement: exports_external.boolean().default(true),
|
|
17875
17884
|
redGate: exports_external.boolean().default(true),
|
|
17876
17885
|
testStrategy: exports_external.enum(["unit", "component", "cli", "e2e", "snapshot"]).optional(),
|
|
17877
|
-
testFramework: exports_external.string().min(1, "acceptance.testFramework must be non-empty").optional()
|
|
17886
|
+
testFramework: exports_external.string().min(1, "acceptance.testFramework must be non-empty").optional(),
|
|
17887
|
+
timeoutMs: exports_external.number().int().min(30000).max(3600000).default(1800000)
|
|
17878
17888
|
});
|
|
17879
17889
|
TestCoverageConfigSchema = exports_external.object({
|
|
17880
17890
|
enabled: exports_external.boolean().default(true),
|
|
@@ -17966,8 +17976,8 @@ var init_schemas3 = __esm(() => {
|
|
|
17966
17976
|
storySizeGate: StorySizeGateConfigSchema
|
|
17967
17977
|
});
|
|
17968
17978
|
PromptsConfigSchema = exports_external.object({
|
|
17969
|
-
overrides: exports_external.record(exports_external.string().refine((key) => ["test-writer", "implementer", "verifier", "single-session", "tdd-simple"].includes(key), {
|
|
17970
|
-
message: "Role must be one of: test-writer, implementer, verifier, single-session, tdd-simple"
|
|
17979
|
+
overrides: exports_external.record(exports_external.string().refine((key) => ["no-test", "test-writer", "implementer", "verifier", "single-session", "tdd-simple"].includes(key), {
|
|
17980
|
+
message: "Role must be one of: no-test, test-writer, implementer, verifier, single-session, tdd-simple"
|
|
17971
17981
|
}), exports_external.string().min(1, "Override path must be non-empty")).optional()
|
|
17972
17982
|
});
|
|
17973
17983
|
DecomposeConfigSchema = exports_external.object({
|
|
@@ -18163,7 +18173,8 @@ var init_defaults = __esm(() => {
|
|
|
18163
18173
|
testPath: "acceptance.test.ts",
|
|
18164
18174
|
model: "fast",
|
|
18165
18175
|
refinement: true,
|
|
18166
|
-
redGate: true
|
|
18176
|
+
redGate: true,
|
|
18177
|
+
timeoutMs: 1800000
|
|
18167
18178
|
},
|
|
18168
18179
|
context: {
|
|
18169
18180
|
fileInjection: "disabled",
|
|
@@ -18725,32 +18736,49 @@ async function generateFromPRD(_stories, refinedCriteria, options) {
|
|
|
18725
18736
|
}
|
|
18726
18737
|
const criteriaList = refinedCriteria.map((c, i) => `AC-${i + 1}: ${c.refined}`).join(`
|
|
18727
18738
|
`);
|
|
18728
|
-
const
|
|
18729
|
-
|
|
18739
|
+
const frameworkOverrideLine = options.testFramework ? `
|
|
18740
|
+
[FRAMEWORK OVERRIDE: Use ${options.testFramework} as the test framework regardless of what you detect.]` : "";
|
|
18741
|
+
const basePrompt = `You are a senior test engineer. Your task is to generate a complete acceptance test file for the "${options.featureName}" feature.
|
|
18730
18742
|
|
|
18731
|
-
|
|
18732
|
-
|
|
18743
|
+
## Step 1: Understand and Classify the Acceptance Criteria
|
|
18744
|
+
|
|
18745
|
+
Read each AC below and classify its verification type:
|
|
18746
|
+
- **file-check**: Verify by reading source files (e.g. "no @nestjs/jwt imports", "file exists", "module registered", "uses registerAs pattern")
|
|
18747
|
+
- **runtime-check**: Load and invoke code directly, assert on return values or behavior
|
|
18748
|
+
- **integration-check**: Requires a running service (e.g. HTTP endpoint returns 200, 11th request returns 429, database query succeeds)
|
|
18733
18749
|
|
|
18734
|
-
ACCEPTANCE CRITERIA
|
|
18750
|
+
ACCEPTANCE CRITERIA:
|
|
18735
18751
|
${criteriaList}
|
|
18736
18752
|
|
|
18737
|
-
|
|
18753
|
+
## Step 2: Explore the Project
|
|
18738
18754
|
|
|
18739
|
-
|
|
18755
|
+
Before writing any tests, examine the project to understand:
|
|
18756
|
+
1. **Language and test framework** \u2014 check dependency manifests (package.json, go.mod, Gemfile, pyproject.toml, Cargo.toml, build.gradle, etc.) to identify the language and test runner
|
|
18757
|
+
2. **Existing test patterns** \u2014 read 1-2 existing test files to understand import style, describe/test/it conventions, and available helpers
|
|
18758
|
+
3. **Project structure** \u2014 identify relevant source directories to determine correct import or load paths
|
|
18740
18759
|
|
|
18741
|
-
|
|
18760
|
+
${frameworkOverrideLine}
|
|
18742
18761
|
|
|
18743
|
-
|
|
18744
|
-
|
|
18745
|
-
|
|
18746
|
-
});
|
|
18747
|
-
});
|
|
18762
|
+
## Step 3: Generate the Acceptance Test File
|
|
18763
|
+
|
|
18764
|
+
Write the complete acceptance test file using the framework identified in Step 2.
|
|
18748
18765
|
|
|
18749
|
-
|
|
18766
|
+
Rules:
|
|
18767
|
+
- **One test per AC**, named exactly "AC-N: <description>"
|
|
18768
|
+
- **file-check ACs** \u2192 read source files using the language's standard file I/O, assert with string or regex checks. Do not start the application.
|
|
18769
|
+
- **runtime-check ACs** \u2192 load or import the module directly and invoke it, assert on the return value or observable side effects
|
|
18770
|
+
- **integration-check ACs** \u2192 use the language's HTTP client or existing test helpers; add a clear setup block (beforeAll/setup/TestMain/etc.) explaining what must be running
|
|
18771
|
+
- **NEVER use placeholder assertions** \u2014 no always-passing or always-failing stubs, no TODO comments as the only content, no empty test bodies
|
|
18772
|
+
- Every test MUST have real assertions that PASS when the feature is correctly implemented and FAIL when it is broken
|
|
18773
|
+
- Output raw code only \u2014 no markdown fences, start directly with the language's import or package declaration
|
|
18774
|
+
- **Path anchor (CRITICAL)**: This test file will be saved at \`<repo-root>/nax/features/${options.featureName}/acceptance.test.ts\` and will ALWAYS run from the repo root via \`bun test <absolute-path>\`. The repo root is exactly 3 \`../\` levels above \`__dirname\`: \`join(__dirname, '..', '..', '..')\`. Never use 4 or more \`../\` \u2014 that would escape the repo. For monorepo projects, navigate into packages from root (e.g. \`join(root, 'apps/api/src')\`).`;
|
|
18775
|
+
const prompt = basePrompt;
|
|
18750
18776
|
logger.info("acceptance", "Generating tests from PRD refined criteria", { count: refinedCriteria.length });
|
|
18751
|
-
const rawOutput = await _generatorPRDDeps.adapter.complete(prompt, {
|
|
18777
|
+
const rawOutput = await (options.adapter ?? _generatorPRDDeps.adapter).complete(prompt, {
|
|
18752
18778
|
model: options.modelDef.model,
|
|
18753
|
-
config: options.config
|
|
18779
|
+
config: options.config,
|
|
18780
|
+
timeoutMs: options.config?.acceptance?.timeoutMs ?? 1800000,
|
|
18781
|
+
workdir: options.workdir
|
|
18754
18782
|
});
|
|
18755
18783
|
const testCode = extractTestCode(rawOutput);
|
|
18756
18784
|
if (!testCode) {
|
|
@@ -18774,40 +18802,6 @@ IMPORTANT: Output raw TypeScript code only. Do NOT use markdown code fences (\`\
|
|
|
18774
18802
|
await _generatorPRDDeps.writeFile(join2(options.featureDir, "acceptance-refined.json"), refinedJsonContent);
|
|
18775
18803
|
return { testCode, criteria };
|
|
18776
18804
|
}
|
|
18777
|
-
function buildStrategyInstructions(strategy, framework) {
|
|
18778
|
-
switch (strategy) {
|
|
18779
|
-
case "component": {
|
|
18780
|
-
const fw = framework ?? "ink-testing-library";
|
|
18781
|
-
if (fw === "react") {
|
|
18782
|
-
return `TEST STRATEGY: component (react)
|
|
18783
|
-
Import render and screen from @testing-library/react. Render the component and use screen.getByText to assert on output.
|
|
18784
|
-
|
|
18785
|
-
`;
|
|
18786
|
-
}
|
|
18787
|
-
return `TEST STRATEGY: component (ink-testing-library)
|
|
18788
|
-
Import render from ink-testing-library. Render the component and use lastFrame() to assert on output.
|
|
18789
|
-
|
|
18790
|
-
`;
|
|
18791
|
-
}
|
|
18792
|
-
case "cli":
|
|
18793
|
-
return `TEST STRATEGY: cli
|
|
18794
|
-
Use Bun.spawn to run the binary. Read stdout and assert on the text output.
|
|
18795
|
-
|
|
18796
|
-
`;
|
|
18797
|
-
case "e2e":
|
|
18798
|
-
return `TEST STRATEGY: e2e
|
|
18799
|
-
Use fetch() against http://localhost to call the running service. Assert on response body using response.text() or response.json().
|
|
18800
|
-
|
|
18801
|
-
`;
|
|
18802
|
-
case "snapshot":
|
|
18803
|
-
return `TEST STRATEGY: snapshot
|
|
18804
|
-
Render the component and use toMatchSnapshot() to capture and compare snapshots.
|
|
18805
|
-
|
|
18806
|
-
`;
|
|
18807
|
-
default:
|
|
18808
|
-
return "";
|
|
18809
|
-
}
|
|
18810
|
-
}
|
|
18811
18805
|
function parseAcceptanceCriteria(specContent) {
|
|
18812
18806
|
const criteria = [];
|
|
18813
18807
|
const lines = specContent.split(`
|
|
@@ -18831,46 +18825,39 @@ function parseAcceptanceCriteria(specContent) {
|
|
|
18831
18825
|
function buildAcceptanceTestPrompt(criteria, featureName, codebaseContext) {
|
|
18832
18826
|
const criteriaList = criteria.map((ac) => `${ac.id}: ${ac.text}`).join(`
|
|
18833
18827
|
`);
|
|
18834
|
-
return `You are a test engineer.
|
|
18828
|
+
return `You are a senior test engineer. Your task is to generate a complete acceptance test file for the "${featureName}" feature.
|
|
18835
18829
|
|
|
18836
|
-
|
|
18837
|
-
|
|
18830
|
+
## Step 1: Understand and Classify the Acceptance Criteria
|
|
18831
|
+
|
|
18832
|
+
Read each AC below and classify its verification type:
|
|
18833
|
+
- **file-check**: Verify by reading source files (e.g. "no @nestjs/jwt imports", "file exists", "module registered", "uses registerAs pattern")
|
|
18834
|
+
- **runtime-check**: Load and invoke code directly, assert on return values or behavior
|
|
18835
|
+
- **integration-check**: Requires a running service (e.g. HTTP endpoint returns 200, 11th request returns 429, database query succeeds)
|
|
18838
18836
|
|
|
18839
18837
|
ACCEPTANCE CRITERIA:
|
|
18840
18838
|
${criteriaList}
|
|
18841
18839
|
|
|
18842
|
-
|
|
18840
|
+
## Step 2: Explore the Project
|
|
18843
18841
|
|
|
18844
|
-
|
|
18845
|
-
|
|
18846
|
-
|
|
18847
|
-
|
|
18848
|
-
5. **Clear test names**: Use format "AC-N: <description>" for test names
|
|
18849
|
-
6. **Async where needed**: Use async/await for operations that may be asynchronous
|
|
18842
|
+
Before writing any tests, examine the project to understand:
|
|
18843
|
+
1. **Language and test framework** \u2014 check dependency manifests (package.json, go.mod, Gemfile, pyproject.toml, Cargo.toml, build.gradle, etc.) to identify the language and test runner
|
|
18844
|
+
2. **Existing test patterns** \u2014 read 1-2 existing test files to understand import style, describe/test/it conventions, and available helpers
|
|
18845
|
+
3. **Project structure** \u2014 identify relevant source directories to determine correct import or load paths
|
|
18850
18846
|
|
|
18851
|
-
Use this structure:
|
|
18852
18847
|
|
|
18853
|
-
|
|
18854
|
-
import { describe, test, expect } from "bun:test";
|
|
18848
|
+
## Step 3: Generate the Acceptance Test File
|
|
18855
18849
|
|
|
18856
|
-
|
|
18857
|
-
test("AC-1: <description>", async () => {
|
|
18858
|
-
// Test implementation
|
|
18859
|
-
});
|
|
18850
|
+
Write the complete acceptance test file using the framework identified in Step 2.
|
|
18860
18851
|
|
|
18861
|
-
|
|
18862
|
-
|
|
18863
|
-
|
|
18864
|
-
|
|
18865
|
-
|
|
18866
|
-
|
|
18867
|
-
|
|
18868
|
-
-
|
|
18869
|
-
-
|
|
18870
|
-
- Use expect() assertions to verify behavior
|
|
18871
|
-
- Clean up resources if needed (close connections, delete temp files)
|
|
18872
|
-
|
|
18873
|
-
Respond with ONLY the TypeScript test code (no markdown code fences, no explanation).`;
|
|
18852
|
+
Rules:
|
|
18853
|
+
- **One test per AC**, named exactly "AC-N: <description>"
|
|
18854
|
+
- **file-check ACs** \u2192 read source files using the language's standard file I/O, assert with string or regex checks. Do not start the application.
|
|
18855
|
+
- **runtime-check ACs** \u2192 load or import the module directly and invoke it, assert on the return value or observable side effects
|
|
18856
|
+
- **integration-check ACs** \u2192 use the language's HTTP client or existing test helpers; add a clear setup block (beforeAll/setup/TestMain/etc.) explaining what must be running
|
|
18857
|
+
- **NEVER use placeholder assertions** \u2014 no always-passing or always-failing stubs, no TODO comments as the only content, no empty test bodies
|
|
18858
|
+
- Every test MUST have real assertions that PASS when the feature is correctly implemented and FAIL when it is broken
|
|
18859
|
+
- Output raw code only \u2014 no markdown fences, start directly with the language's import or package declaration
|
|
18860
|
+
- **Path anchor (CRITICAL)**: This test file will be saved at \`<repo-root>/nax/features/${featureName}/acceptance.test.ts\` and will ALWAYS run from the repo root via \`bun test <absolute-path>\`. The repo root is exactly 3 \`../\` levels above \`__dirname\`: \`join(__dirname, '..', '..', '..')\`. Never use 4 or more \`../\` \u2014 that would escape the repo. For monorepo projects, navigate into packages from root (e.g. \`join(root, 'apps/api/src')\`).`;
|
|
18874
18861
|
}
|
|
18875
18862
|
async function generateAcceptanceTests(adapter, options) {
|
|
18876
18863
|
const logger = getLogger();
|
|
@@ -18887,7 +18874,9 @@ async function generateAcceptanceTests(adapter, options) {
|
|
|
18887
18874
|
try {
|
|
18888
18875
|
const output = await adapter.complete(prompt, {
|
|
18889
18876
|
model: options.modelDef.model,
|
|
18890
|
-
config: options.config
|
|
18877
|
+
config: options.config,
|
|
18878
|
+
timeoutMs: options.config?.acceptance?.timeoutMs ?? 1800000,
|
|
18879
|
+
workdir: options.workdir
|
|
18891
18880
|
});
|
|
18892
18881
|
const testCode = extractTestCode(output);
|
|
18893
18882
|
if (!testCode) {
|
|
@@ -18983,7 +18972,34 @@ function findRelatedStories(failedAC, prd) {
|
|
|
18983
18972
|
const passedStories = prd.userStories.filter((s) => s.status === "passed").map((s) => s.id);
|
|
18984
18973
|
return passedStories.slice(0, 5);
|
|
18985
18974
|
}
|
|
18986
|
-
function
|
|
18975
|
+
function groupACsByRelatedStories(failedACs, prd) {
|
|
18976
|
+
const groups = new Map;
|
|
18977
|
+
for (const ac of failedACs) {
|
|
18978
|
+
const related = findRelatedStories(ac, prd);
|
|
18979
|
+
const key = [...related].sort().join(",");
|
|
18980
|
+
if (!groups.has(key)) {
|
|
18981
|
+
groups.set(key, { acs: [], relatedStories: related });
|
|
18982
|
+
}
|
|
18983
|
+
groups.get(key)?.acs.push(ac);
|
|
18984
|
+
}
|
|
18985
|
+
const result = Array.from(groups.values());
|
|
18986
|
+
while (result.length > MAX_FIX_STORIES) {
|
|
18987
|
+
result.sort((a, b) => a.acs.length - b.acs.length);
|
|
18988
|
+
const smallest = result.shift();
|
|
18989
|
+
if (!smallest)
|
|
18990
|
+
break;
|
|
18991
|
+
result[0].acs.push(...smallest.acs);
|
|
18992
|
+
for (const s of smallest.relatedStories) {
|
|
18993
|
+
if (!result[0].relatedStories.includes(s)) {
|
|
18994
|
+
result[0].relatedStories.push(s);
|
|
18995
|
+
}
|
|
18996
|
+
}
|
|
18997
|
+
}
|
|
18998
|
+
return result;
|
|
18999
|
+
}
|
|
19000
|
+
function buildFixPrompt(batchedACs, acTextMap, testOutput, relatedStories, prd, testFilePath) {
|
|
19001
|
+
const acList = batchedACs.map((ac) => `${ac}: ${acTextMap[ac] || "No description available"}`).join(`
|
|
19002
|
+
`);
|
|
18987
19003
|
const relatedStoriesText = relatedStories.map((id) => {
|
|
18988
19004
|
const story = prd.userStories.find((s) => s.id === id);
|
|
18989
19005
|
if (!story)
|
|
@@ -18993,43 +19009,47 @@ function buildFixPrompt(failedAC, acText, testOutput, relatedStories, prd) {
|
|
|
18993
19009
|
}).filter(Boolean).join(`
|
|
18994
19010
|
|
|
18995
19011
|
`);
|
|
18996
|
-
|
|
18997
|
-
|
|
18998
|
-
|
|
18999
|
-
|
|
19012
|
+
const testFileSection = testFilePath ? `
|
|
19013
|
+
ACCEPTANCE TEST FILE: ${testFilePath}
|
|
19014
|
+
(Read this file first to understand what each test expects)
|
|
19015
|
+
` : "";
|
|
19016
|
+
return `You are a debugging expert. Feature acceptance tests have failed.${testFileSection}
|
|
19017
|
+
FAILED ACCEPTANCE CRITERIA (${batchedACs.length} total):
|
|
19018
|
+
${acList}
|
|
19000
19019
|
|
|
19001
19020
|
TEST FAILURE OUTPUT:
|
|
19002
|
-
${testOutput}
|
|
19021
|
+
${testOutput.slice(0, 2000)}
|
|
19003
19022
|
|
|
19004
19023
|
RELATED STORIES (implemented this functionality):
|
|
19005
19024
|
${relatedStoriesText}
|
|
19006
19025
|
|
|
19007
|
-
Your task: Generate a fix
|
|
19026
|
+
Your task: Generate a fix description that will make these acceptance tests pass.
|
|
19008
19027
|
|
|
19009
19028
|
Requirements:
|
|
19010
|
-
1.
|
|
19011
|
-
2. Identify
|
|
19012
|
-
3.
|
|
19013
|
-
4.
|
|
19029
|
+
1. Read the acceptance test file first to understand what each failing test expects
|
|
19030
|
+
2. Identify the root cause based on the test failure output
|
|
19031
|
+
3. Find and fix the relevant implementation code (do NOT modify the test file)
|
|
19032
|
+
4. Write a clear, actionable fix description (2-4 sentences)
|
|
19014
19033
|
5. Reference the relevant story IDs if needed
|
|
19015
19034
|
|
|
19016
19035
|
Respond with ONLY the fix description (no JSON, no markdown, just the description text).`;
|
|
19017
19036
|
}
|
|
19018
19037
|
async function generateFixStories(adapter, options) {
|
|
19019
|
-
const { failedACs, testOutput, prd, specContent, modelDef } = options;
|
|
19020
|
-
const fixStories = [];
|
|
19021
|
-
const acTextMap = parseACTextFromSpec(specContent);
|
|
19038
|
+
const { failedACs, testOutput, prd, specContent, modelDef, testFilePath } = options;
|
|
19022
19039
|
const logger = getLogger();
|
|
19023
|
-
|
|
19024
|
-
|
|
19025
|
-
|
|
19026
|
-
|
|
19027
|
-
const relatedStories =
|
|
19040
|
+
const acTextMap = parseACTextFromSpec(specContent);
|
|
19041
|
+
const groups = groupACsByRelatedStories(failedACs, prd);
|
|
19042
|
+
const fixStories = [];
|
|
19043
|
+
for (let i = 0;i < groups.length; i++) {
|
|
19044
|
+
const { acs: batchedACs, relatedStories } = groups[i];
|
|
19028
19045
|
if (relatedStories.length === 0) {
|
|
19029
|
-
logger.warn("acceptance", "
|
|
19046
|
+
logger.warn("acceptance", "[WARN] No related stories found for AC group \u2014 skipping", { batchedACs });
|
|
19030
19047
|
continue;
|
|
19031
19048
|
}
|
|
19032
|
-
|
|
19049
|
+
logger.info("acceptance", "Generating fix for AC group", { batchedACs });
|
|
19050
|
+
const prompt = buildFixPrompt(batchedACs, acTextMap, testOutput, relatedStories, prd, testFilePath);
|
|
19051
|
+
const relatedStory = prd.userStories.find((s) => relatedStories.includes(s.id) && s.workdir);
|
|
19052
|
+
const workdir = relatedStory?.workdir;
|
|
19033
19053
|
try {
|
|
19034
19054
|
const fixDescription = await adapter.complete(prompt, {
|
|
19035
19055
|
model: modelDef.model,
|
|
@@ -19037,25 +19057,31 @@ async function generateFixStories(adapter, options) {
|
|
|
19037
19057
|
});
|
|
19038
19058
|
fixStories.push({
|
|
19039
19059
|
id: `US-FIX-${String(i + 1).padStart(3, "0")}`,
|
|
19040
|
-
title: `Fix: ${
|
|
19041
|
-
failedAC,
|
|
19060
|
+
title: `Fix: ${batchedACs.join(", ")} \u2014 ${(acTextMap[batchedACs[0]] || "").slice(0, 40)}`,
|
|
19061
|
+
failedAC: batchedACs[0],
|
|
19062
|
+
batchedACs,
|
|
19042
19063
|
testOutput,
|
|
19043
19064
|
relatedStories,
|
|
19044
|
-
description: fixDescription
|
|
19065
|
+
description: fixDescription,
|
|
19066
|
+
testFilePath,
|
|
19067
|
+
workdir
|
|
19045
19068
|
});
|
|
19046
|
-
logger.info("acceptance", "
|
|
19069
|
+
logger.info("acceptance", "[OK] Generated fix story", { storyId: fixStories[fixStories.length - 1].id });
|
|
19047
19070
|
} catch (error48) {
|
|
19048
|
-
logger.warn("acceptance", "
|
|
19049
|
-
|
|
19071
|
+
logger.warn("acceptance", "[WARN] Error generating fix", {
|
|
19072
|
+
batchedACs,
|
|
19050
19073
|
error: error48.message
|
|
19051
19074
|
});
|
|
19052
19075
|
fixStories.push({
|
|
19053
19076
|
id: `US-FIX-${String(i + 1).padStart(3, "0")}`,
|
|
19054
|
-
title: `Fix: ${
|
|
19055
|
-
failedAC,
|
|
19077
|
+
title: `Fix: ${batchedACs.join(", ")}`,
|
|
19078
|
+
failedAC: batchedACs[0],
|
|
19079
|
+
batchedACs,
|
|
19056
19080
|
testOutput,
|
|
19057
19081
|
relatedStories,
|
|
19058
|
-
description: `Fix the implementation to make ${
|
|
19082
|
+
description: `Fix the implementation to make ${batchedACs.join(", ")} pass. Related stories: ${relatedStories.join(", ")}.`,
|
|
19083
|
+
testFilePath,
|
|
19084
|
+
workdir
|
|
19059
19085
|
});
|
|
19060
19086
|
}
|
|
19061
19087
|
}
|
|
@@ -19076,20 +19102,40 @@ function parseACTextFromSpec(specContent) {
|
|
|
19076
19102
|
return map2;
|
|
19077
19103
|
}
|
|
19078
19104
|
function convertFixStoryToUserStory(fixStory) {
|
|
19105
|
+
const batchedACs = fixStory.batchedACs ?? [fixStory.failedAC];
|
|
19106
|
+
const acList = batchedACs.join(", ");
|
|
19107
|
+
const truncatedOutput = fixStory.testOutput.slice(0, 1000);
|
|
19108
|
+
const testFilePath = fixStory.testFilePath ?? "acceptance.test.ts";
|
|
19109
|
+
const enrichedDescription = [
|
|
19110
|
+
fixStory.description,
|
|
19111
|
+
"",
|
|
19112
|
+
`ACCEPTANCE TEST FILE: ${testFilePath}`,
|
|
19113
|
+
`FAILED ACCEPTANCE CRITERIA: ${acList}`,
|
|
19114
|
+
"",
|
|
19115
|
+
"TEST FAILURE OUTPUT:",
|
|
19116
|
+
truncatedOutput,
|
|
19117
|
+
"",
|
|
19118
|
+
"Instructions: Read the acceptance test file first to understand what each failing test expects.",
|
|
19119
|
+
"Then find the relevant source code and fix the implementation.",
|
|
19120
|
+
"Do NOT modify the test file."
|
|
19121
|
+
].join(`
|
|
19122
|
+
`);
|
|
19079
19123
|
return {
|
|
19080
19124
|
id: fixStory.id,
|
|
19081
19125
|
title: fixStory.title,
|
|
19082
|
-
description:
|
|
19083
|
-
acceptanceCriteria:
|
|
19126
|
+
description: enrichedDescription,
|
|
19127
|
+
acceptanceCriteria: batchedACs.map((ac) => `Fix ${ac}`),
|
|
19084
19128
|
tags: ["fix", "acceptance-failure"],
|
|
19085
19129
|
dependencies: fixStory.relatedStories,
|
|
19086
19130
|
status: "pending",
|
|
19087
19131
|
passes: false,
|
|
19088
19132
|
escalations: [],
|
|
19089
19133
|
attempts: 0,
|
|
19090
|
-
contextFiles: []
|
|
19134
|
+
contextFiles: [],
|
|
19135
|
+
workdir: fixStory.workdir
|
|
19091
19136
|
};
|
|
19092
19137
|
}
|
|
19138
|
+
var MAX_FIX_STORIES = 8;
|
|
19093
19139
|
var init_fix_generator = __esm(() => {
|
|
19094
19140
|
init_logger2();
|
|
19095
19141
|
});
|
|
@@ -19482,7 +19528,7 @@ async function closeAcpSession(session) {
|
|
|
19482
19528
|
}
|
|
19483
19529
|
}
|
|
19484
19530
|
function acpSessionsPath(workdir, featureName) {
|
|
19485
|
-
return join3(workdir, "nax", "features", featureName, "acp-sessions.json");
|
|
19531
|
+
return join3(workdir, ".nax", "features", featureName, "acp-sessions.json");
|
|
19486
19532
|
}
|
|
19487
19533
|
function sidecarSessionName(entry) {
|
|
19488
19534
|
return typeof entry === "string" ? entry : entry.sessionName;
|
|
@@ -20993,6 +21039,7 @@ import { join as join6, resolve as resolve4 } from "path";
|
|
|
20993
21039
|
function globalConfigDir() {
|
|
20994
21040
|
return join6(homedir3(), ".nax");
|
|
20995
21041
|
}
|
|
21042
|
+
var PROJECT_NAX_DIR = ".nax";
|
|
20996
21043
|
var init_paths = () => {};
|
|
20997
21044
|
|
|
20998
21045
|
// src/config/loader.ts
|
|
@@ -21005,7 +21052,7 @@ function findProjectDir(startDir = process.cwd()) {
|
|
|
21005
21052
|
let dir = resolve5(startDir);
|
|
21006
21053
|
let depth = 0;
|
|
21007
21054
|
while (depth < MAX_DIRECTORY_DEPTH) {
|
|
21008
|
-
const candidate = join7(dir,
|
|
21055
|
+
const candidate = join7(dir, PROJECT_NAX_DIR);
|
|
21009
21056
|
if (existsSync5(join7(candidate, "config.json"))) {
|
|
21010
21057
|
return candidate;
|
|
21011
21058
|
}
|
|
@@ -21075,7 +21122,7 @@ async function loadConfigForWorkdir(rootConfigPath, packageDir) {
|
|
|
21075
21122
|
return rootConfig;
|
|
21076
21123
|
}
|
|
21077
21124
|
const repoRoot = dirname2(rootNaxDir);
|
|
21078
|
-
const packageConfigPath = join7(repoRoot,
|
|
21125
|
+
const packageConfigPath = join7(repoRoot, PROJECT_NAX_DIR, "packages", packageDir, "config.json");
|
|
21079
21126
|
const packageOverride = await loadJsonFile(packageConfigPath, "config");
|
|
21080
21127
|
if (!packageOverride) {
|
|
21081
21128
|
return rootConfig;
|
|
@@ -22351,7 +22398,7 @@ var package_default;
|
|
|
22351
22398
|
var init_package = __esm(() => {
|
|
22352
22399
|
package_default = {
|
|
22353
22400
|
name: "@nathapp/nax",
|
|
22354
|
-
version: "0.
|
|
22401
|
+
version: "0.51.0",
|
|
22355
22402
|
description: "AI Coding Agent Orchestrator \u2014 loops until done",
|
|
22356
22403
|
type: "module",
|
|
22357
22404
|
bin: {
|
|
@@ -22402,8 +22449,6 @@ var init_package = __esm(() => {
|
|
|
22402
22449
|
],
|
|
22403
22450
|
files: [
|
|
22404
22451
|
"dist/",
|
|
22405
|
-
"src/",
|
|
22406
|
-
"bin/",
|
|
22407
22452
|
"README.md",
|
|
22408
22453
|
"CHANGELOG.md"
|
|
22409
22454
|
],
|
|
@@ -22425,8 +22470,8 @@ var init_version = __esm(() => {
|
|
|
22425
22470
|
NAX_VERSION = package_default.version;
|
|
22426
22471
|
NAX_COMMIT = (() => {
|
|
22427
22472
|
try {
|
|
22428
|
-
if (/^[0-9a-f]{6,10}$/.test("
|
|
22429
|
-
return "
|
|
22473
|
+
if (/^[0-9a-f]{6,10}$/.test("bcb69c6"))
|
|
22474
|
+
return "bcb69c6";
|
|
22430
22475
|
} catch {}
|
|
22431
22476
|
try {
|
|
22432
22477
|
const result = Bun.spawnSync(["git", "rev-parse", "--short", "HEAD"], {
|
|
@@ -22575,14 +22620,14 @@ function collectBatchMetrics(ctx, storyStartTime) {
|
|
|
22575
22620
|
});
|
|
22576
22621
|
}
|
|
22577
22622
|
async function saveRunMetrics(workdir, runMetrics) {
|
|
22578
|
-
const metricsPath = path2.join(workdir, "nax", "metrics.json");
|
|
22623
|
+
const metricsPath = path2.join(workdir, ".nax", "metrics.json");
|
|
22579
22624
|
const existing = await loadJsonFile(metricsPath, "metrics");
|
|
22580
22625
|
const allMetrics = Array.isArray(existing) ? existing : [];
|
|
22581
22626
|
allMetrics.push(runMetrics);
|
|
22582
22627
|
await saveJsonFile(metricsPath, allMetrics, "metrics");
|
|
22583
22628
|
}
|
|
22584
22629
|
async function loadRunMetrics(workdir) {
|
|
22585
|
-
const metricsPath = path2.join(workdir, "nax", "metrics.json");
|
|
22630
|
+
const metricsPath = path2.join(workdir, ".nax", "metrics.json");
|
|
22586
22631
|
const content = await loadJsonFile(metricsPath, "metrics");
|
|
22587
22632
|
return Array.isArray(content) ? content : [];
|
|
22588
22633
|
}
|
|
@@ -24180,8 +24225,114 @@ ${stderr}`;
|
|
|
24180
24225
|
};
|
|
24181
24226
|
});
|
|
24182
24227
|
|
|
24228
|
+
// src/agents/shared/validation.ts
|
|
24229
|
+
function validateAgentForTier(agent, tier) {
|
|
24230
|
+
return agent.capabilities.supportedTiers.includes(tier);
|
|
24231
|
+
}
|
|
24232
|
+
function validateAgentFeature(agent, feature) {
|
|
24233
|
+
return agent.capabilities.features.has(feature);
|
|
24234
|
+
}
|
|
24235
|
+
function describeAgentCapabilities(agent) {
|
|
24236
|
+
const tiers = agent.capabilities.supportedTiers.join(",");
|
|
24237
|
+
const features = Array.from(agent.capabilities.features).join(",");
|
|
24238
|
+
const maxTokens = agent.capabilities.maxContextTokens;
|
|
24239
|
+
return `${agent.name}: tiers=[${tiers}], maxTokens=${maxTokens}, features=[${features}]`;
|
|
24240
|
+
}
|
|
24241
|
+
|
|
24242
|
+
// src/agents/shared/version-detection.ts
|
|
24243
|
+
async function getAgentVersion(binaryName) {
|
|
24244
|
+
try {
|
|
24245
|
+
const proc = _versionDetectionDeps.spawn([binaryName, "--version"], {
|
|
24246
|
+
stdout: "pipe",
|
|
24247
|
+
stderr: "pipe"
|
|
24248
|
+
});
|
|
24249
|
+
const exitCode = await proc.exited;
|
|
24250
|
+
if (exitCode !== 0) {
|
|
24251
|
+
return null;
|
|
24252
|
+
}
|
|
24253
|
+
const stdout = await new Response(proc.stdout).text();
|
|
24254
|
+
const versionLine = stdout.trim().split(`
|
|
24255
|
+
`)[0];
|
|
24256
|
+
const versionMatch = versionLine.match(/v?(\d+\.\d+(?:\.\d+)?(?:[-+][\w.]+)?)/);
|
|
24257
|
+
if (versionMatch) {
|
|
24258
|
+
return versionMatch[0];
|
|
24259
|
+
}
|
|
24260
|
+
return versionLine || null;
|
|
24261
|
+
} catch {
|
|
24262
|
+
return null;
|
|
24263
|
+
}
|
|
24264
|
+
}
|
|
24265
|
+
async function getAgentVersions() {
|
|
24266
|
+
const agents = await getInstalledAgents();
|
|
24267
|
+
const agentsByName = new Map(agents.map((a) => [a.name, a]));
|
|
24268
|
+
const { ALL_AGENTS: ALL_AGENTS2 } = await Promise.resolve().then(() => (init_registry(), exports_registry));
|
|
24269
|
+
const versions2 = await Promise.all(ALL_AGENTS2.map(async (agent) => {
|
|
24270
|
+
const version2 = agentsByName.has(agent.name) ? await getAgentVersion(agent.binary) : null;
|
|
24271
|
+
return {
|
|
24272
|
+
name: agent.name,
|
|
24273
|
+
displayName: agent.displayName,
|
|
24274
|
+
version: version2,
|
|
24275
|
+
installed: agentsByName.has(agent.name)
|
|
24276
|
+
};
|
|
24277
|
+
}));
|
|
24278
|
+
return versions2;
|
|
24279
|
+
}
|
|
24280
|
+
var _versionDetectionDeps;
|
|
24281
|
+
var init_version_detection = __esm(() => {
|
|
24282
|
+
init_registry();
|
|
24283
|
+
_versionDetectionDeps = {
|
|
24284
|
+
spawn(cmd, opts) {
|
|
24285
|
+
return Bun.spawn(cmd, opts);
|
|
24286
|
+
}
|
|
24287
|
+
};
|
|
24288
|
+
});
|
|
24289
|
+
|
|
24290
|
+
// src/agents/index.ts
|
|
24291
|
+
var exports_agents = {};
|
|
24292
|
+
__export(exports_agents, {
|
|
24293
|
+
validateAgentForTier: () => validateAgentForTier,
|
|
24294
|
+
validateAgentFeature: () => validateAgentFeature,
|
|
24295
|
+
parseTokenUsage: () => parseTokenUsage,
|
|
24296
|
+
getInstalledAgents: () => getInstalledAgents,
|
|
24297
|
+
getAllAgentNames: () => getAllAgentNames,
|
|
24298
|
+
getAgentVersions: () => getAgentVersions,
|
|
24299
|
+
getAgentVersion: () => getAgentVersion,
|
|
24300
|
+
getAgent: () => getAgent,
|
|
24301
|
+
formatCostWithConfidence: () => formatCostWithConfidence,
|
|
24302
|
+
estimateCostFromTokenUsage: () => estimateCostFromTokenUsage,
|
|
24303
|
+
estimateCostFromOutput: () => estimateCostFromOutput,
|
|
24304
|
+
estimateCostByDuration: () => estimateCostByDuration,
|
|
24305
|
+
estimateCost: () => estimateCost,
|
|
24306
|
+
describeAgentCapabilities: () => describeAgentCapabilities,
|
|
24307
|
+
checkAgentHealth: () => checkAgentHealth,
|
|
24308
|
+
MODEL_PRICING: () => MODEL_PRICING,
|
|
24309
|
+
CompleteError: () => CompleteError,
|
|
24310
|
+
ClaudeCodeAdapter: () => ClaudeCodeAdapter,
|
|
24311
|
+
COST_RATES: () => COST_RATES
|
|
24312
|
+
});
|
|
24313
|
+
var init_agents = __esm(() => {
|
|
24314
|
+
init_types2();
|
|
24315
|
+
init_claude();
|
|
24316
|
+
init_registry();
|
|
24317
|
+
init_cost();
|
|
24318
|
+
init_version_detection();
|
|
24319
|
+
});
|
|
24320
|
+
|
|
24183
24321
|
// src/pipeline/stages/acceptance-setup.ts
|
|
24322
|
+
var exports_acceptance_setup = {};
|
|
24323
|
+
__export(exports_acceptance_setup, {
|
|
24324
|
+
computeACFingerprint: () => computeACFingerprint,
|
|
24325
|
+
acceptanceSetupStage: () => acceptanceSetupStage,
|
|
24326
|
+
_acceptanceSetupDeps: () => _acceptanceSetupDeps
|
|
24327
|
+
});
|
|
24184
24328
|
import path5 from "path";
|
|
24329
|
+
function computeACFingerprint(criteria) {
|
|
24330
|
+
const sorted = [...criteria].sort().join(`
|
|
24331
|
+
`);
|
|
24332
|
+
const hasher = new Bun.CryptoHasher("sha256");
|
|
24333
|
+
hasher.update(sorted);
|
|
24334
|
+
return `sha256:${hasher.digest("hex")}`;
|
|
24335
|
+
}
|
|
24185
24336
|
var _acceptanceSetupDeps, acceptanceSetupStage;
|
|
24186
24337
|
var init_acceptance_setup = __esm(() => {
|
|
24187
24338
|
init_config();
|
|
@@ -24193,6 +24344,27 @@ var init_acceptance_setup = __esm(() => {
|
|
|
24193
24344
|
writeFile: async (filePath, content) => {
|
|
24194
24345
|
await Bun.write(filePath, content);
|
|
24195
24346
|
},
|
|
24347
|
+
copyFile: async (src, dest) => {
|
|
24348
|
+
const content = await Bun.file(src).text();
|
|
24349
|
+
await Bun.write(dest, content);
|
|
24350
|
+
},
|
|
24351
|
+
deleteFile: async (filePath) => {
|
|
24352
|
+
const { unlink } = await import("fs/promises");
|
|
24353
|
+
await unlink(filePath);
|
|
24354
|
+
},
|
|
24355
|
+
readMeta: async (metaPath) => {
|
|
24356
|
+
const f = Bun.file(metaPath);
|
|
24357
|
+
if (!await f.exists())
|
|
24358
|
+
return null;
|
|
24359
|
+
try {
|
|
24360
|
+
return JSON.parse(await f.text());
|
|
24361
|
+
} catch {
|
|
24362
|
+
return null;
|
|
24363
|
+
}
|
|
24364
|
+
},
|
|
24365
|
+
writeMeta: async (metaPath, meta3) => {
|
|
24366
|
+
await Bun.write(metaPath, JSON.stringify(meta3, null, 2));
|
|
24367
|
+
},
|
|
24196
24368
|
runTest: async (_testPath, _workdir) => {
|
|
24197
24369
|
const proc = Bun.spawn(["bun", "test", _testPath], {
|
|
24198
24370
|
cwd: _workdir,
|
|
@@ -24226,12 +24398,25 @@ ${stderr}` };
|
|
|
24226
24398
|
return { action: "fail", reason: "[acceptance-setup] featureDir is not set" };
|
|
24227
24399
|
}
|
|
24228
24400
|
const testPath = path5.join(ctx.featureDir, "acceptance.test.ts");
|
|
24229
|
-
const
|
|
24401
|
+
const metaPath = path5.join(ctx.featureDir, "acceptance-meta.json");
|
|
24402
|
+
const allCriteria = ctx.prd.userStories.flatMap((s) => s.acceptanceCriteria);
|
|
24230
24403
|
let totalCriteria = 0;
|
|
24231
24404
|
let testableCount = 0;
|
|
24232
|
-
|
|
24233
|
-
|
|
24405
|
+
const fileExists = await _acceptanceSetupDeps.fileExists(testPath);
|
|
24406
|
+
let shouldGenerate = !fileExists;
|
|
24407
|
+
if (fileExists) {
|
|
24408
|
+
const fingerprint = computeACFingerprint(allCriteria);
|
|
24409
|
+
const meta3 = await _acceptanceSetupDeps.readMeta(metaPath);
|
|
24410
|
+
if (!meta3 || meta3.acFingerprint !== fingerprint) {
|
|
24411
|
+
await _acceptanceSetupDeps.copyFile(testPath, `${testPath}.bak`);
|
|
24412
|
+
await _acceptanceSetupDeps.deleteFile(testPath);
|
|
24413
|
+
shouldGenerate = true;
|
|
24414
|
+
}
|
|
24415
|
+
}
|
|
24416
|
+
if (shouldGenerate) {
|
|
24234
24417
|
totalCriteria = allCriteria.length;
|
|
24418
|
+
const { getAgent: getAgent2 } = await Promise.resolve().then(() => (init_agents(), exports_agents));
|
|
24419
|
+
const agent = (ctx.agentGetFn ?? getAgent2)(ctx.config.autoMode.defaultAgent);
|
|
24235
24420
|
let refinedCriteria;
|
|
24236
24421
|
if (ctx.config.acceptance.refinement) {
|
|
24237
24422
|
refinedCriteria = await _acceptanceSetupDeps.refine(allCriteria, {
|
|
@@ -24259,9 +24444,18 @@ ${stderr}` };
|
|
|
24259
24444
|
modelDef: resolveModel(ctx.config.models[ctx.config.acceptance.model ?? "fast"]),
|
|
24260
24445
|
config: ctx.config,
|
|
24261
24446
|
testStrategy: ctx.config.acceptance.testStrategy,
|
|
24262
|
-
testFramework: ctx.config.acceptance.testFramework
|
|
24447
|
+
testFramework: ctx.config.acceptance.testFramework,
|
|
24448
|
+
adapter: agent ?? undefined
|
|
24263
24449
|
});
|
|
24264
24450
|
await _acceptanceSetupDeps.writeFile(testPath, result.testCode);
|
|
24451
|
+
const fingerprint = computeACFingerprint(allCriteria);
|
|
24452
|
+
await _acceptanceSetupDeps.writeMeta(metaPath, {
|
|
24453
|
+
generatedAt: new Date().toISOString(),
|
|
24454
|
+
acFingerprint: fingerprint,
|
|
24455
|
+
storyCount: ctx.prd.userStories.length,
|
|
24456
|
+
acCount: totalCriteria,
|
|
24457
|
+
generator: "nax"
|
|
24458
|
+
});
|
|
24265
24459
|
}
|
|
24266
24460
|
if (ctx.config.acceptance.redGate === false) {
|
|
24267
24461
|
ctx.acceptanceSetup = { totalCriteria, testableCount, redFailCount: 0 };
|
|
@@ -24281,99 +24475,6 @@ ${stderr}` };
|
|
|
24281
24475
|
};
|
|
24282
24476
|
});
|
|
24283
24477
|
|
|
24284
|
-
// src/agents/shared/validation.ts
|
|
24285
|
-
function validateAgentForTier(agent, tier) {
|
|
24286
|
-
return agent.capabilities.supportedTiers.includes(tier);
|
|
24287
|
-
}
|
|
24288
|
-
function validateAgentFeature(agent, feature) {
|
|
24289
|
-
return agent.capabilities.features.has(feature);
|
|
24290
|
-
}
|
|
24291
|
-
function describeAgentCapabilities(agent) {
|
|
24292
|
-
const tiers = agent.capabilities.supportedTiers.join(",");
|
|
24293
|
-
const features = Array.from(agent.capabilities.features).join(",");
|
|
24294
|
-
const maxTokens = agent.capabilities.maxContextTokens;
|
|
24295
|
-
return `${agent.name}: tiers=[${tiers}], maxTokens=${maxTokens}, features=[${features}]`;
|
|
24296
|
-
}
|
|
24297
|
-
|
|
24298
|
-
// src/agents/shared/version-detection.ts
|
|
24299
|
-
async function getAgentVersion(binaryName) {
|
|
24300
|
-
try {
|
|
24301
|
-
const proc = _versionDetectionDeps.spawn([binaryName, "--version"], {
|
|
24302
|
-
stdout: "pipe",
|
|
24303
|
-
stderr: "pipe"
|
|
24304
|
-
});
|
|
24305
|
-
const exitCode = await proc.exited;
|
|
24306
|
-
if (exitCode !== 0) {
|
|
24307
|
-
return null;
|
|
24308
|
-
}
|
|
24309
|
-
const stdout = await new Response(proc.stdout).text();
|
|
24310
|
-
const versionLine = stdout.trim().split(`
|
|
24311
|
-
`)[0];
|
|
24312
|
-
const versionMatch = versionLine.match(/v?(\d+\.\d+(?:\.\d+)?(?:[-+][\w.]+)?)/);
|
|
24313
|
-
if (versionMatch) {
|
|
24314
|
-
return versionMatch[0];
|
|
24315
|
-
}
|
|
24316
|
-
return versionLine || null;
|
|
24317
|
-
} catch {
|
|
24318
|
-
return null;
|
|
24319
|
-
}
|
|
24320
|
-
}
|
|
24321
|
-
async function getAgentVersions() {
|
|
24322
|
-
const agents = await getInstalledAgents();
|
|
24323
|
-
const agentsByName = new Map(agents.map((a) => [a.name, a]));
|
|
24324
|
-
const { ALL_AGENTS: ALL_AGENTS2 } = await Promise.resolve().then(() => (init_registry(), exports_registry));
|
|
24325
|
-
const versions2 = await Promise.all(ALL_AGENTS2.map(async (agent) => {
|
|
24326
|
-
const version2 = agentsByName.has(agent.name) ? await getAgentVersion(agent.binary) : null;
|
|
24327
|
-
return {
|
|
24328
|
-
name: agent.name,
|
|
24329
|
-
displayName: agent.displayName,
|
|
24330
|
-
version: version2,
|
|
24331
|
-
installed: agentsByName.has(agent.name)
|
|
24332
|
-
};
|
|
24333
|
-
}));
|
|
24334
|
-
return versions2;
|
|
24335
|
-
}
|
|
24336
|
-
var _versionDetectionDeps;
|
|
24337
|
-
var init_version_detection = __esm(() => {
|
|
24338
|
-
init_registry();
|
|
24339
|
-
_versionDetectionDeps = {
|
|
24340
|
-
spawn(cmd, opts) {
|
|
24341
|
-
return Bun.spawn(cmd, opts);
|
|
24342
|
-
}
|
|
24343
|
-
};
|
|
24344
|
-
});
|
|
24345
|
-
|
|
24346
|
-
// src/agents/index.ts
|
|
24347
|
-
var exports_agents = {};
|
|
24348
|
-
__export(exports_agents, {
|
|
24349
|
-
validateAgentForTier: () => validateAgentForTier,
|
|
24350
|
-
validateAgentFeature: () => validateAgentFeature,
|
|
24351
|
-
parseTokenUsage: () => parseTokenUsage,
|
|
24352
|
-
getInstalledAgents: () => getInstalledAgents,
|
|
24353
|
-
getAllAgentNames: () => getAllAgentNames,
|
|
24354
|
-
getAgentVersions: () => getAgentVersions,
|
|
24355
|
-
getAgentVersion: () => getAgentVersion,
|
|
24356
|
-
getAgent: () => getAgent,
|
|
24357
|
-
formatCostWithConfidence: () => formatCostWithConfidence,
|
|
24358
|
-
estimateCostFromTokenUsage: () => estimateCostFromTokenUsage,
|
|
24359
|
-
estimateCostFromOutput: () => estimateCostFromOutput,
|
|
24360
|
-
estimateCostByDuration: () => estimateCostByDuration,
|
|
24361
|
-
estimateCost: () => estimateCost,
|
|
24362
|
-
describeAgentCapabilities: () => describeAgentCapabilities,
|
|
24363
|
-
checkAgentHealth: () => checkAgentHealth,
|
|
24364
|
-
MODEL_PRICING: () => MODEL_PRICING,
|
|
24365
|
-
CompleteError: () => CompleteError,
|
|
24366
|
-
ClaudeCodeAdapter: () => ClaudeCodeAdapter,
|
|
24367
|
-
COST_RATES: () => COST_RATES
|
|
24368
|
-
});
|
|
24369
|
-
var init_agents = __esm(() => {
|
|
24370
|
-
init_types2();
|
|
24371
|
-
init_claude();
|
|
24372
|
-
init_registry();
|
|
24373
|
-
init_cost();
|
|
24374
|
-
init_version_detection();
|
|
24375
|
-
});
|
|
24376
|
-
|
|
24377
24478
|
// src/pipeline/event-bus.ts
|
|
24378
24479
|
class PipelineEventBus {
|
|
24379
24480
|
subscribers = new Map;
|
|
@@ -24458,7 +24559,7 @@ function hasScript(packageJson, scriptName) {
|
|
|
24458
24559
|
return false;
|
|
24459
24560
|
return scriptName in scripts;
|
|
24460
24561
|
}
|
|
24461
|
-
async function resolveCommand(check2, config2, executionConfig, workdir) {
|
|
24562
|
+
async function resolveCommand(check2, config2, executionConfig, workdir, qualityCommands) {
|
|
24462
24563
|
if (executionConfig) {
|
|
24463
24564
|
if (check2 === "lint" && executionConfig.lintCommand !== undefined) {
|
|
24464
24565
|
return executionConfig.lintCommand;
|
|
@@ -24470,6 +24571,10 @@ async function resolveCommand(check2, config2, executionConfig, workdir) {
|
|
|
24470
24571
|
if (config2.commands[check2]) {
|
|
24471
24572
|
return config2.commands[check2] ?? null;
|
|
24472
24573
|
}
|
|
24574
|
+
const qualityCmd = qualityCommands?.[check2];
|
|
24575
|
+
if (qualityCmd) {
|
|
24576
|
+
return qualityCmd;
|
|
24577
|
+
}
|
|
24473
24578
|
const packageJson = await loadPackageJson(workdir);
|
|
24474
24579
|
if (hasScript(packageJson, check2)) {
|
|
24475
24580
|
return `bun run ${check2}`;
|
|
@@ -24567,7 +24672,7 @@ async function getUncommittedFilesImpl(workdir) {
|
|
|
24567
24672
|
return [];
|
|
24568
24673
|
}
|
|
24569
24674
|
}
|
|
24570
|
-
async function runReview(config2, workdir, executionConfig) {
|
|
24675
|
+
async function runReview(config2, workdir, executionConfig, qualityCommands) {
|
|
24571
24676
|
const startTime = Date.now();
|
|
24572
24677
|
const logger = getSafeLogger();
|
|
24573
24678
|
const checks3 = [];
|
|
@@ -24605,7 +24710,7 @@ Stage and commit these files before running review.`
|
|
|
24605
24710
|
};
|
|
24606
24711
|
}
|
|
24607
24712
|
for (const checkName of config2.checks) {
|
|
24608
|
-
const command = await resolveCommand(checkName, config2, executionConfig, workdir);
|
|
24713
|
+
const command = await resolveCommand(checkName, config2, executionConfig, workdir, qualityCommands);
|
|
24609
24714
|
if (command === null) {
|
|
24610
24715
|
getSafeLogger()?.warn("review", `Skipping ${checkName} check (command not configured or disabled)`);
|
|
24611
24716
|
continue;
|
|
@@ -24667,9 +24772,9 @@ async function getChangedFiles(workdir, baseRef) {
|
|
|
24667
24772
|
}
|
|
24668
24773
|
|
|
24669
24774
|
class ReviewOrchestrator {
|
|
24670
|
-
async review(reviewConfig, workdir, executionConfig, plugins, storyGitRef, scopePrefix) {
|
|
24775
|
+
async review(reviewConfig, workdir, executionConfig, plugins, storyGitRef, scopePrefix, qualityCommands) {
|
|
24671
24776
|
const logger = getSafeLogger();
|
|
24672
|
-
const builtIn = await runReview(reviewConfig, workdir, executionConfig);
|
|
24777
|
+
const builtIn = await runReview(reviewConfig, workdir, executionConfig, qualityCommands);
|
|
24673
24778
|
if (!builtIn.success) {
|
|
24674
24779
|
return { builtIn, success: false, failureReason: builtIn.failureReason, pluginFailed: false };
|
|
24675
24780
|
}
|
|
@@ -24759,7 +24864,7 @@ var init_review = __esm(() => {
|
|
|
24759
24864
|
const effectiveConfig = ctx.effectiveConfig ?? ctx.config;
|
|
24760
24865
|
logger.info("review", "Running review phase", { storyId: ctx.story.id });
|
|
24761
24866
|
const effectiveWorkdir = ctx.story.workdir ? join17(ctx.workdir, ctx.story.workdir) : ctx.workdir;
|
|
24762
|
-
const result = await reviewOrchestrator.review(effectiveConfig.review, effectiveWorkdir, effectiveConfig.execution, ctx.plugins, ctx.storyGitRef, ctx.story.workdir);
|
|
24867
|
+
const result = await reviewOrchestrator.review(effectiveConfig.review, effectiveWorkdir, effectiveConfig.execution, ctx.plugins, ctx.storyGitRef, ctx.story.workdir, effectiveConfig.quality?.commands);
|
|
24763
24868
|
ctx.reviewResult = result.builtIn;
|
|
24764
24869
|
if (!result.success) {
|
|
24765
24870
|
const allFindings = result.builtIn.pluginReviewers?.flatMap((pr) => pr.findings ?? []) ?? [];
|
|
@@ -25976,7 +26081,7 @@ function hookCtx(feature, opts) {
|
|
|
25976
26081
|
};
|
|
25977
26082
|
}
|
|
25978
26083
|
async function loadPackageContextMd(packageWorkdir) {
|
|
25979
|
-
const contextPath = `${packageWorkdir}
|
|
26084
|
+
const contextPath = `${packageWorkdir}/.nax/context.md`;
|
|
25980
26085
|
const file2 = Bun.file(contextPath);
|
|
25981
26086
|
if (!await file2.exists())
|
|
25982
26087
|
return null;
|
|
@@ -27158,6 +27263,11 @@ function buildIsolationSection(roleOrMode, mode, testCommand) {
|
|
|
27158
27263
|
const footer = `
|
|
27159
27264
|
|
|
27160
27265
|
${buildTestFilterRule(testCmd)}`;
|
|
27266
|
+
if (role === "no-test") {
|
|
27267
|
+
return `${header}
|
|
27268
|
+
|
|
27269
|
+
isolation scope: Implement changes in src/ and other non-test directories. Do NOT create or modify any files in the test/ directory.${footer}`;
|
|
27270
|
+
}
|
|
27161
27271
|
if (role === "test-writer") {
|
|
27162
27272
|
const m = mode ?? "strict";
|
|
27163
27273
|
if (m === "strict") {
|
|
@@ -27205,13 +27315,26 @@ function buildTestFrameworkHint(testCommand) {
|
|
|
27205
27315
|
return "Use Jest (describe/test/expect)";
|
|
27206
27316
|
return "Use your project's test framework";
|
|
27207
27317
|
}
|
|
27208
|
-
function buildRoleTaskSection(roleOrVariant, variant, testCommand, isolation) {
|
|
27318
|
+
function buildRoleTaskSection(roleOrVariant, variant, testCommand, isolation, noTestJustification) {
|
|
27209
27319
|
if ((roleOrVariant === "standard" || roleOrVariant === "lite") && variant === undefined) {
|
|
27210
27320
|
return buildRoleTaskSection("implementer", roleOrVariant, testCommand, isolation);
|
|
27211
27321
|
}
|
|
27212
27322
|
const role = roleOrVariant;
|
|
27213
27323
|
const testCmd = testCommand ?? DEFAULT_TEST_CMD2;
|
|
27214
27324
|
const frameworkHint = buildTestFrameworkHint(testCmd);
|
|
27325
|
+
if (role === "no-test") {
|
|
27326
|
+
const justification = noTestJustification ?? "No behavioral changes \u2014 tests not required";
|
|
27327
|
+
return `# Role: Implementer (No Tests)
|
|
27328
|
+
|
|
27329
|
+
Your task: implement the change as described. This story has no behavioral changes and does not require test modifications.
|
|
27330
|
+
|
|
27331
|
+
Instructions:
|
|
27332
|
+
- Implement the change as described in the story
|
|
27333
|
+
- Do NOT create or modify test files
|
|
27334
|
+
- Justification for no tests: ${justification}
|
|
27335
|
+
- When done, stage and commit ALL changed files with: git commit -m 'feat: <description>'
|
|
27336
|
+
- Goal: change implemented, no test files created or modified, all changes committed`;
|
|
27337
|
+
}
|
|
27215
27338
|
if (role === "implementer") {
|
|
27216
27339
|
const v = variant ?? "standard";
|
|
27217
27340
|
if (v === "standard") {
|
|
@@ -27486,6 +27609,7 @@ class PromptBuilder {
|
|
|
27486
27609
|
_loaderConfig;
|
|
27487
27610
|
_testCommand;
|
|
27488
27611
|
_hermeticConfig;
|
|
27612
|
+
_noTestJustification;
|
|
27489
27613
|
constructor(role, options = {}) {
|
|
27490
27614
|
this._role = role;
|
|
27491
27615
|
this._options = options;
|
|
@@ -27529,6 +27653,10 @@ class PromptBuilder {
|
|
|
27529
27653
|
this._hermeticConfig = config2;
|
|
27530
27654
|
return this;
|
|
27531
27655
|
}
|
|
27656
|
+
noTestJustification(justification) {
|
|
27657
|
+
this._noTestJustification = justification;
|
|
27658
|
+
return this;
|
|
27659
|
+
}
|
|
27532
27660
|
async build() {
|
|
27533
27661
|
const sections = [];
|
|
27534
27662
|
if (this._constitution) {
|
|
@@ -27588,7 +27716,7 @@ ${this._contextMd}
|
|
|
27588
27716
|
}
|
|
27589
27717
|
const variant = this._options.variant;
|
|
27590
27718
|
const isolation = this._options.isolation;
|
|
27591
|
-
return buildRoleTaskSection(this._role, variant, this._testCommand, isolation);
|
|
27719
|
+
return buildRoleTaskSection(this._role, variant, this._testCommand, isolation, this._noTestJustification);
|
|
27592
27720
|
}
|
|
27593
27721
|
}
|
|
27594
27722
|
var SECTION_SEP2 = `
|
|
@@ -28791,8 +28919,8 @@ var init_prompt = __esm(() => {
|
|
|
28791
28919
|
const builder = PromptBuilder.for("batch").withLoader(ctx.workdir, ctx.config).stories(ctx.stories).context(ctx.contextMarkdown).constitution(ctx.constitution?.content).testCommand(effectiveConfig.quality?.commands?.test).hermeticConfig(effectiveConfig.quality?.testing);
|
|
28792
28920
|
prompt = await builder.build();
|
|
28793
28921
|
} else {
|
|
28794
|
-
const role = "tdd-simple";
|
|
28795
|
-
const builder = PromptBuilder.for(role).withLoader(ctx.workdir, ctx.config).story(ctx.story).context(ctx.contextMarkdown).constitution(ctx.constitution?.content).testCommand(effectiveConfig.quality?.commands?.test).hermeticConfig(effectiveConfig.quality?.testing);
|
|
28922
|
+
const role = ctx.routing.testStrategy === "no-test" ? "no-test" : "tdd-simple";
|
|
28923
|
+
const builder = PromptBuilder.for(role).withLoader(ctx.workdir, ctx.config).story(ctx.story).context(ctx.contextMarkdown).constitution(ctx.constitution?.content).testCommand(effectiveConfig.quality?.commands?.test).hermeticConfig(effectiveConfig.quality?.testing).noTestJustification(ctx.story.routing?.noTestJustification);
|
|
28796
28924
|
prompt = await builder.build();
|
|
28797
28925
|
}
|
|
28798
28926
|
ctx.prompt = prompt;
|
|
@@ -30425,8 +30553,7 @@ function generatePackageContextTemplate(packagePath) {
|
|
|
30425
30553
|
}
|
|
30426
30554
|
async function initPackage(repoRoot, packagePath, force = false) {
|
|
30427
30555
|
const logger = getLogger();
|
|
30428
|
-
const
|
|
30429
|
-
const naxDir = join31(packageDir, "nax");
|
|
30556
|
+
const naxDir = join31(repoRoot, ".nax", "packages", packagePath);
|
|
30430
30557
|
const contextPath = join31(naxDir, "context.md");
|
|
30431
30558
|
if (existsSync20(contextPath) && !force) {
|
|
30432
30559
|
logger.info("init", "Package context.md already exists (use --force to overwrite)", { path: contextPath });
|
|
@@ -30441,7 +30568,7 @@ async function initPackage(repoRoot, packagePath, force = false) {
|
|
|
30441
30568
|
}
|
|
30442
30569
|
async function initContext(projectRoot, options = {}) {
|
|
30443
30570
|
const logger = getLogger();
|
|
30444
|
-
const naxDir = join31(projectRoot, "nax");
|
|
30571
|
+
const naxDir = join31(projectRoot, ".nax");
|
|
30445
30572
|
const contextPath = join31(naxDir, "context.md");
|
|
30446
30573
|
if (existsSync20(contextPath) && !options.force) {
|
|
30447
30574
|
logger.info("init", "context.md already exists, skipping (use --force to overwrite)", { path: contextPath });
|
|
@@ -30458,7 +30585,7 @@ async function initContext(projectRoot, options = {}) {
|
|
|
30458
30585
|
content = generateContextTemplate(scan);
|
|
30459
30586
|
}
|
|
30460
30587
|
await Bun.write(contextPath, content);
|
|
30461
|
-
logger.info("init", "Generated nax/context.md template from project scan", { path: contextPath });
|
|
30588
|
+
logger.info("init", "Generated .nax/context.md template from project scan", { path: contextPath });
|
|
30462
30589
|
}
|
|
30463
30590
|
var _deps6;
|
|
30464
30591
|
var init_init_context = __esm(() => {
|
|
@@ -31029,18 +31156,18 @@ var NAX_RUNTIME_PATTERNS;
|
|
|
31029
31156
|
var init_checks_git = __esm(() => {
|
|
31030
31157
|
NAX_RUNTIME_PATTERNS = [
|
|
31031
31158
|
/^.{2} nax\.lock$/,
|
|
31032
|
-
/^.{2} nax\/$/,
|
|
31033
|
-
/^.{2} nax\/metrics\.json$/,
|
|
31034
|
-
/^.{2} nax\/features\/$/,
|
|
31035
|
-
/^.{2} nax\/features\/[^/]+\/$/,
|
|
31036
|
-
/^.{2} nax\/features\/[^/]+\/status\.json$/,
|
|
31037
|
-
/^.{2} nax\/features\/[^/]+\/prd\.json$/,
|
|
31038
|
-
/^.{2} nax\/features\/[^/]+\/runs\//,
|
|
31039
|
-
/^.{2} nax\/features\/[^/]+\/plan\//,
|
|
31040
|
-
/^.{2} nax\/features\/[^/]+\/acp-sessions\.json$/,
|
|
31041
|
-
/^.{2} nax\/features\/[^/]+\/interactions\//,
|
|
31042
|
-
/^.{2} nax\/features\/[^/]+\/progress\.txt$/,
|
|
31043
|
-
/^.{2} nax\/features\/[^/]+\/acceptance-refined\.json$/,
|
|
31159
|
+
/^.{2} \.nax\/$/,
|
|
31160
|
+
/^.{2} \.nax\/metrics\.json$/,
|
|
31161
|
+
/^.{2} \.nax\/features\/$/,
|
|
31162
|
+
/^.{2} \.nax\/features\/[^/]+\/$/,
|
|
31163
|
+
/^.{2} \.nax\/features\/[^/]+\/status\.json$/,
|
|
31164
|
+
/^.{2} \.nax\/features\/[^/]+\/prd\.json$/,
|
|
31165
|
+
/^.{2} \.nax\/features\/[^/]+\/runs\//,
|
|
31166
|
+
/^.{2} \.nax\/features\/[^/]+\/plan\//,
|
|
31167
|
+
/^.{2} \.nax\/features\/[^/]+\/acp-sessions\.json$/,
|
|
31168
|
+
/^.{2} \.nax\/features\/[^/]+\/interactions\//,
|
|
31169
|
+
/^.{2} \.nax\/features\/[^/]+\/progress\.txt$/,
|
|
31170
|
+
/^.{2} \.nax\/features\/[^/]+\/acceptance-refined\.json$/,
|
|
31044
31171
|
/^.{2} \.nax-verifier-verdict\.json$/,
|
|
31045
31172
|
/^.{2} \.nax-pids$/,
|
|
31046
31173
|
/^.{2} \.nax-wt\//
|
|
@@ -31351,9 +31478,9 @@ async function checkGitignoreCoversNax(workdir) {
|
|
|
31351
31478
|
const content = await file2.text();
|
|
31352
31479
|
const patterns = [
|
|
31353
31480
|
"nax.lock",
|
|
31354
|
-
"nax/**/runs/",
|
|
31355
|
-
"nax/metrics.json",
|
|
31356
|
-
"nax/features/*/status.json",
|
|
31481
|
+
".nax/**/runs/",
|
|
31482
|
+
".nax/metrics.json",
|
|
31483
|
+
".nax/features/*/status.json",
|
|
31357
31484
|
".nax-pids",
|
|
31358
31485
|
".nax-wt/"
|
|
31359
31486
|
];
|
|
@@ -32201,9 +32328,21 @@ var init_crash_recovery = __esm(() => {
|
|
|
32201
32328
|
// src/execution/lifecycle/acceptance-loop.ts
|
|
32202
32329
|
var exports_acceptance_loop = {};
|
|
32203
32330
|
__export(exports_acceptance_loop, {
|
|
32204
|
-
runAcceptanceLoop: () => runAcceptanceLoop
|
|
32331
|
+
runAcceptanceLoop: () => runAcceptanceLoop,
|
|
32332
|
+
isTestLevelFailure: () => isTestLevelFailure,
|
|
32333
|
+
isStubTestFile: () => isStubTestFile
|
|
32205
32334
|
});
|
|
32206
|
-
import path14 from "path";
|
|
32335
|
+
import path14, { join as join45 } from "path";
|
|
32336
|
+
function isStubTestFile(content) {
|
|
32337
|
+
return /expect\s*\(\s*true\s*\)\s*\.\s*toBe\s*\(\s*(?:false|true)\s*\)/.test(content);
|
|
32338
|
+
}
|
|
32339
|
+
function isTestLevelFailure(failedACs, totalACs) {
|
|
32340
|
+
if (failedACs.includes("AC-ERROR"))
|
|
32341
|
+
return true;
|
|
32342
|
+
if (totalACs === 0)
|
|
32343
|
+
return false;
|
|
32344
|
+
return failedACs.length / totalACs > 0.8;
|
|
32345
|
+
}
|
|
32207
32346
|
async function loadSpecContent(featureDir) {
|
|
32208
32347
|
if (!featureDir)
|
|
32209
32348
|
return "";
|
|
@@ -32223,6 +32362,7 @@ async function generateAndAddFixStories(ctx, failures, prd) {
|
|
|
32223
32362
|
return null;
|
|
32224
32363
|
}
|
|
32225
32364
|
const modelDef = resolveModel(ctx.config.models[ctx.config.analyze.model]);
|
|
32365
|
+
const testFilePath = ctx.featureDir ? path14.join(ctx.featureDir, "acceptance.test.ts") : undefined;
|
|
32226
32366
|
const fixStories = await generateFixStories(agent, {
|
|
32227
32367
|
failedACs: failures.failedACs,
|
|
32228
32368
|
testOutput: failures.testOutput,
|
|
@@ -32230,7 +32370,8 @@ async function generateAndAddFixStories(ctx, failures, prd) {
|
|
|
32230
32370
|
specContent: await loadSpecContent(ctx.featureDir),
|
|
32231
32371
|
workdir: ctx.workdir,
|
|
32232
32372
|
modelDef,
|
|
32233
|
-
config: ctx.config
|
|
32373
|
+
config: ctx.config,
|
|
32374
|
+
testFilePath
|
|
32234
32375
|
});
|
|
32235
32376
|
if (fixStories.length === 0) {
|
|
32236
32377
|
logger?.error("acceptance", "Failed to generate fix stories");
|
|
@@ -32254,9 +32395,10 @@ async function executeFixStory(ctx, story, prd, iterations) {
|
|
|
32254
32395
|
agent: ctx.config.autoMode.defaultAgent,
|
|
32255
32396
|
iteration: iterations
|
|
32256
32397
|
}), ctx.workdir);
|
|
32398
|
+
const fixEffectiveConfig = story.workdir ? await loadConfigForWorkdir(join45(ctx.workdir, ".nax", "config.json"), story.workdir) : ctx.config;
|
|
32257
32399
|
const fixContext = {
|
|
32258
32400
|
config: ctx.config,
|
|
32259
|
-
effectiveConfig:
|
|
32401
|
+
effectiveConfig: fixEffectiveConfig,
|
|
32260
32402
|
prd,
|
|
32261
32403
|
story,
|
|
32262
32404
|
stories: [story],
|
|
@@ -32276,6 +32418,23 @@ async function executeFixStory(ctx, story, prd, iterations) {
|
|
|
32276
32418
|
metrics: result.context.storyMetrics
|
|
32277
32419
|
};
|
|
32278
32420
|
}
|
|
32421
|
+
async function regenerateAcceptanceTest(testPath, acceptanceContext) {
|
|
32422
|
+
const logger = getSafeLogger();
|
|
32423
|
+
const bakPath = `${testPath}.bak`;
|
|
32424
|
+
const content = await Bun.file(testPath).text();
|
|
32425
|
+
await Bun.write(bakPath, content);
|
|
32426
|
+
logger?.info("acceptance", `Backed up acceptance test -> ${bakPath}`);
|
|
32427
|
+
const { unlink: unlink3 } = await import("fs/promises");
|
|
32428
|
+
await unlink3(testPath);
|
|
32429
|
+
const { acceptanceSetupStage: acceptanceSetupStage2 } = await Promise.resolve().then(() => (init_acceptance_setup(), exports_acceptance_setup));
|
|
32430
|
+
await acceptanceSetupStage2.execute(acceptanceContext);
|
|
32431
|
+
if (!await Bun.file(testPath).exists()) {
|
|
32432
|
+
logger?.error("acceptance", "Acceptance test regeneration failed \u2014 manual intervention required");
|
|
32433
|
+
return false;
|
|
32434
|
+
}
|
|
32435
|
+
logger?.info("acceptance", "Acceptance test regenerated successfully");
|
|
32436
|
+
return true;
|
|
32437
|
+
}
|
|
32279
32438
|
async function runAcceptanceLoop(ctx) {
|
|
32280
32439
|
const logger = getSafeLogger();
|
|
32281
32440
|
const maxRetries = ctx.config.acceptance.maxRetries;
|
|
@@ -32337,6 +32496,39 @@ async function runAcceptanceLoop(ctx) {
|
|
|
32337
32496
|
}), ctx.workdir);
|
|
32338
32497
|
return buildResult(false, prd, totalCost, iterations, storiesCompleted, prdDirty);
|
|
32339
32498
|
}
|
|
32499
|
+
if (ctx.featureDir) {
|
|
32500
|
+
const testPath = path14.join(ctx.featureDir, "acceptance.test.ts");
|
|
32501
|
+
const testFile = Bun.file(testPath);
|
|
32502
|
+
if (await testFile.exists()) {
|
|
32503
|
+
const testContent = await testFile.text();
|
|
32504
|
+
if (isStubTestFile(testContent)) {
|
|
32505
|
+
logger?.warn("acceptance", "Stub tests detected \u2014 re-generating acceptance tests");
|
|
32506
|
+
const { unlink: unlink3 } = await import("fs/promises");
|
|
32507
|
+
await unlink3(testPath);
|
|
32508
|
+
const { acceptanceSetupStage: acceptanceSetupStage2 } = await Promise.resolve().then(() => (init_acceptance_setup(), exports_acceptance_setup));
|
|
32509
|
+
await acceptanceSetupStage2.execute(acceptanceContext);
|
|
32510
|
+
const newContent = await Bun.file(testPath).text();
|
|
32511
|
+
if (isStubTestFile(newContent)) {
|
|
32512
|
+
logger?.error("acceptance", "Acceptance test generation failed after retry \u2014 manual implementation required");
|
|
32513
|
+
return buildResult(false, prd, totalCost, iterations, storiesCompleted, prdDirty);
|
|
32514
|
+
}
|
|
32515
|
+
continue;
|
|
32516
|
+
}
|
|
32517
|
+
}
|
|
32518
|
+
}
|
|
32519
|
+
const totalACs = prd.userStories.filter((s) => !s.id.startsWith("US-FIX-")).flatMap((s) => s.acceptanceCriteria).length;
|
|
32520
|
+
if (ctx.featureDir && isTestLevelFailure(failures.failedACs, totalACs)) {
|
|
32521
|
+
logger?.warn("acceptance", `Test-level failure detected (${failures.failedACs.length}/${totalACs} ACs failed) \u2014 regenerating acceptance test`);
|
|
32522
|
+
const testPath = path14.join(ctx.featureDir, "acceptance.test.ts");
|
|
32523
|
+
const testFile = Bun.file(testPath);
|
|
32524
|
+
if (await testFile.exists()) {
|
|
32525
|
+
const regenerated = await regenerateAcceptanceTest(testPath, acceptanceContext);
|
|
32526
|
+
if (!regenerated) {
|
|
32527
|
+
return buildResult(false, prd, totalCost, iterations, storiesCompleted, prdDirty);
|
|
32528
|
+
}
|
|
32529
|
+
continue;
|
|
32530
|
+
}
|
|
32531
|
+
}
|
|
32340
32532
|
logger?.info("acceptance", "Generating fix stories...");
|
|
32341
32533
|
const fixStories = await generateAndAddFixStories(ctx, failures, prd);
|
|
32342
32534
|
if (!fixStories) {
|
|
@@ -32367,6 +32559,7 @@ async function runAcceptanceLoop(ctx) {
|
|
|
32367
32559
|
}
|
|
32368
32560
|
var init_acceptance_loop = __esm(() => {
|
|
32369
32561
|
init_acceptance();
|
|
32562
|
+
init_loader2();
|
|
32370
32563
|
init_schema();
|
|
32371
32564
|
init_hooks();
|
|
32372
32565
|
init_logger2();
|
|
@@ -32801,12 +32994,12 @@ __export(exports_manager, {
|
|
|
32801
32994
|
WorktreeManager: () => WorktreeManager
|
|
32802
32995
|
});
|
|
32803
32996
|
import { existsSync as existsSync32, symlinkSync } from "fs";
|
|
32804
|
-
import { join as
|
|
32997
|
+
import { join as join46 } from "path";
|
|
32805
32998
|
|
|
32806
32999
|
class WorktreeManager {
|
|
32807
33000
|
async create(projectRoot, storyId) {
|
|
32808
33001
|
validateStoryId(storyId);
|
|
32809
|
-
const worktreePath =
|
|
33002
|
+
const worktreePath = join46(projectRoot, ".nax-wt", storyId);
|
|
32810
33003
|
const branchName = `nax/${storyId}`;
|
|
32811
33004
|
try {
|
|
32812
33005
|
const proc = Bun.spawn(["git", "worktree", "add", worktreePath, "-b", branchName], {
|
|
@@ -32831,9 +33024,9 @@ class WorktreeManager {
|
|
|
32831
33024
|
}
|
|
32832
33025
|
throw new Error(`Failed to create worktree: ${String(error48)}`);
|
|
32833
33026
|
}
|
|
32834
|
-
const nodeModulesSource =
|
|
33027
|
+
const nodeModulesSource = join46(projectRoot, "node_modules");
|
|
32835
33028
|
if (existsSync32(nodeModulesSource)) {
|
|
32836
|
-
const nodeModulesTarget =
|
|
33029
|
+
const nodeModulesTarget = join46(worktreePath, "node_modules");
|
|
32837
33030
|
try {
|
|
32838
33031
|
symlinkSync(nodeModulesSource, nodeModulesTarget, "dir");
|
|
32839
33032
|
} catch (error48) {
|
|
@@ -32841,9 +33034,9 @@ class WorktreeManager {
|
|
|
32841
33034
|
throw new Error(`Failed to symlink node_modules: ${errorMessage(error48)}`);
|
|
32842
33035
|
}
|
|
32843
33036
|
}
|
|
32844
|
-
const envSource =
|
|
33037
|
+
const envSource = join46(projectRoot, ".env");
|
|
32845
33038
|
if (existsSync32(envSource)) {
|
|
32846
|
-
const envTarget =
|
|
33039
|
+
const envTarget = join46(worktreePath, ".env");
|
|
32847
33040
|
try {
|
|
32848
33041
|
symlinkSync(envSource, envTarget, "file");
|
|
32849
33042
|
} catch (error48) {
|
|
@@ -32854,7 +33047,7 @@ class WorktreeManager {
|
|
|
32854
33047
|
}
|
|
32855
33048
|
async remove(projectRoot, storyId) {
|
|
32856
33049
|
validateStoryId(storyId);
|
|
32857
|
-
const worktreePath =
|
|
33050
|
+
const worktreePath = join46(projectRoot, ".nax-wt", storyId);
|
|
32858
33051
|
const branchName = `nax/${storyId}`;
|
|
32859
33052
|
try {
|
|
32860
33053
|
const proc = Bun.spawn(["git", "worktree", "remove", worktreePath, "--force"], {
|
|
@@ -33245,7 +33438,7 @@ var init_parallel_worker = __esm(() => {
|
|
|
33245
33438
|
|
|
33246
33439
|
// src/execution/parallel-coordinator.ts
|
|
33247
33440
|
import os3 from "os";
|
|
33248
|
-
import { join as
|
|
33441
|
+
import { join as join47 } from "path";
|
|
33249
33442
|
function groupStoriesByDependencies(stories) {
|
|
33250
33443
|
const batches = [];
|
|
33251
33444
|
const processed = new Set;
|
|
@@ -33324,7 +33517,7 @@ async function executeParallel(stories, prdPath, projectRoot, config2, hooks, pl
|
|
|
33324
33517
|
};
|
|
33325
33518
|
const worktreePaths = new Map;
|
|
33326
33519
|
for (const story of batch) {
|
|
33327
|
-
const worktreePath =
|
|
33520
|
+
const worktreePath = join47(projectRoot, ".nax-wt", story.id);
|
|
33328
33521
|
try {
|
|
33329
33522
|
await worktreeManager.create(projectRoot, story.id);
|
|
33330
33523
|
worktreePaths.set(story.id, worktreePath);
|
|
@@ -33373,7 +33566,7 @@ async function executeParallel(stories, prdPath, projectRoot, config2, hooks, pl
|
|
|
33373
33566
|
});
|
|
33374
33567
|
logger?.warn("parallel", "Worktree preserved for manual conflict resolution", {
|
|
33375
33568
|
storyId: mergeResult.storyId,
|
|
33376
|
-
worktreePath:
|
|
33569
|
+
worktreePath: join47(projectRoot, ".nax-wt", mergeResult.storyId)
|
|
33377
33570
|
});
|
|
33378
33571
|
}
|
|
33379
33572
|
}
|
|
@@ -33833,12 +34026,12 @@ var init_parallel_executor = __esm(() => {
|
|
|
33833
34026
|
// src/pipeline/subscribers/events-writer.ts
|
|
33834
34027
|
import { appendFile as appendFile2, mkdir as mkdir2 } from "fs/promises";
|
|
33835
34028
|
import { homedir as homedir7 } from "os";
|
|
33836
|
-
import { basename as basename5, join as
|
|
34029
|
+
import { basename as basename5, join as join48 } from "path";
|
|
33837
34030
|
function wireEventsWriter(bus, feature, runId, workdir) {
|
|
33838
34031
|
const logger = getSafeLogger();
|
|
33839
34032
|
const project = basename5(workdir);
|
|
33840
|
-
const eventsDir =
|
|
33841
|
-
const eventsFile =
|
|
34033
|
+
const eventsDir = join48(homedir7(), ".nax", "events", project);
|
|
34034
|
+
const eventsFile = join48(eventsDir, "events.jsonl");
|
|
33842
34035
|
let dirReady = false;
|
|
33843
34036
|
const write = (line) => {
|
|
33844
34037
|
(async () => {
|
|
@@ -34012,12 +34205,12 @@ var init_interaction2 = __esm(() => {
|
|
|
34012
34205
|
// src/pipeline/subscribers/registry.ts
|
|
34013
34206
|
import { mkdir as mkdir3, writeFile } from "fs/promises";
|
|
34014
34207
|
import { homedir as homedir8 } from "os";
|
|
34015
|
-
import { basename as basename6, join as
|
|
34208
|
+
import { basename as basename6, join as join49 } from "path";
|
|
34016
34209
|
function wireRegistry(bus, feature, runId, workdir) {
|
|
34017
34210
|
const logger = getSafeLogger();
|
|
34018
34211
|
const project = basename6(workdir);
|
|
34019
|
-
const runDir =
|
|
34020
|
-
const metaFile =
|
|
34212
|
+
const runDir = join49(homedir8(), ".nax", "runs", `${project}-${feature}-${runId}`);
|
|
34213
|
+
const metaFile = join49(runDir, "meta.json");
|
|
34021
34214
|
const unsub = bus.on("run:started", (_ev) => {
|
|
34022
34215
|
(async () => {
|
|
34023
34216
|
try {
|
|
@@ -34027,8 +34220,8 @@ function wireRegistry(bus, feature, runId, workdir) {
|
|
|
34027
34220
|
project,
|
|
34028
34221
|
feature,
|
|
34029
34222
|
workdir,
|
|
34030
|
-
statusPath:
|
|
34031
|
-
eventsDir:
|
|
34223
|
+
statusPath: join49(workdir, ".nax", "features", feature, "status.json"),
|
|
34224
|
+
eventsDir: join49(workdir, ".nax", "features", feature, "runs"),
|
|
34032
34225
|
registeredAt: new Date().toISOString()
|
|
34033
34226
|
};
|
|
34034
34227
|
await writeFile(metaFile, JSON.stringify(meta3, null, 2));
|
|
@@ -34455,7 +34648,7 @@ async function handleTierEscalation(ctx) {
|
|
|
34455
34648
|
}
|
|
34456
34649
|
for (const s of storiesToEscalate) {
|
|
34457
34650
|
const currentTestStrategy = s.routing?.testStrategy ?? ctx.routing.testStrategy;
|
|
34458
|
-
const shouldSwitchToTestAfter = escalateRetryAsTestAfter && currentTestStrategy !== "test-after";
|
|
34651
|
+
const shouldSwitchToTestAfter = escalateRetryAsTestAfter && currentTestStrategy !== "test-after" && currentTestStrategy !== "no-test";
|
|
34459
34652
|
if (shouldSwitchToTestAfter) {
|
|
34460
34653
|
logger?.warn("escalation", "Switching strategy to test-after (greenfield-no-tests fallback)", {
|
|
34461
34654
|
storyId: s.id,
|
|
@@ -34479,7 +34672,7 @@ async function handleTierEscalation(ctx) {
|
|
|
34479
34672
|
if (!shouldEscalate)
|
|
34480
34673
|
return s;
|
|
34481
34674
|
const currentTestStrategy = s.routing?.testStrategy ?? ctx.routing.testStrategy;
|
|
34482
|
-
const shouldSwitchToTestAfter = escalateRetryAsTestAfter && currentTestStrategy !== "test-after";
|
|
34675
|
+
const shouldSwitchToTestAfter = escalateRetryAsTestAfter && currentTestStrategy !== "test-after" && currentTestStrategy !== "no-test";
|
|
34483
34676
|
const baseRouting = s.routing ?? { ...ctx.routing };
|
|
34484
34677
|
const updatedRouting = {
|
|
34485
34678
|
...baseRouting,
|
|
@@ -34681,7 +34874,7 @@ var init_pipeline_result_handler = __esm(() => {
|
|
|
34681
34874
|
});
|
|
34682
34875
|
|
|
34683
34876
|
// src/execution/iteration-runner.ts
|
|
34684
|
-
import { join as
|
|
34877
|
+
import { join as join50 } from "path";
|
|
34685
34878
|
async function runIteration(ctx, prd, selection, iterations, totalCost, allStoryMetrics) {
|
|
34686
34879
|
const logger = getSafeLogger();
|
|
34687
34880
|
const { story, storiesToExecute, routing, isBatchExecution } = selection;
|
|
@@ -34707,7 +34900,7 @@ async function runIteration(ctx, prd, selection, iterations, totalCost, allStory
|
|
|
34707
34900
|
const storyStartTime = Date.now();
|
|
34708
34901
|
const storyGitRef = await captureGitRef(ctx.workdir);
|
|
34709
34902
|
const accumulatedAttemptCost = (story.priorFailures || []).reduce((sum, f) => sum + (f.cost || 0), 0);
|
|
34710
|
-
const effectiveConfig = story.workdir ? await _iterationRunnerDeps.loadConfigForWorkdir(
|
|
34903
|
+
const effectiveConfig = story.workdir ? await _iterationRunnerDeps.loadConfigForWorkdir(join50(ctx.workdir, ".nax", "config.json"), story.workdir) : ctx.config;
|
|
34711
34904
|
const pipelineContext = {
|
|
34712
34905
|
config: ctx.config,
|
|
34713
34906
|
effectiveConfig,
|
|
@@ -35073,7 +35266,7 @@ async function writeStatusFile(filePath, status) {
|
|
|
35073
35266
|
var init_status_file = () => {};
|
|
35074
35267
|
|
|
35075
35268
|
// src/execution/status-writer.ts
|
|
35076
|
-
import { join as
|
|
35269
|
+
import { join as join51 } from "path";
|
|
35077
35270
|
|
|
35078
35271
|
class StatusWriter {
|
|
35079
35272
|
statusFile;
|
|
@@ -35141,7 +35334,7 @@ class StatusWriter {
|
|
|
35141
35334
|
if (!this._prd)
|
|
35142
35335
|
return;
|
|
35143
35336
|
const safeLogger = getSafeLogger();
|
|
35144
|
-
const featureStatusPath =
|
|
35337
|
+
const featureStatusPath = join51(featureDir, "status.json");
|
|
35145
35338
|
try {
|
|
35146
35339
|
const base = this.getSnapshot(totalCost, iterations);
|
|
35147
35340
|
if (!base) {
|
|
@@ -35349,7 +35542,7 @@ __export(exports_run_initialization, {
|
|
|
35349
35542
|
initializeRun: () => initializeRun,
|
|
35350
35543
|
_reconcileDeps: () => _reconcileDeps
|
|
35351
35544
|
});
|
|
35352
|
-
import { join as
|
|
35545
|
+
import { join as join52 } from "path";
|
|
35353
35546
|
async function reconcileState(prd, prdPath, workdir, config2) {
|
|
35354
35547
|
const logger = getSafeLogger();
|
|
35355
35548
|
let reconciledCount = 0;
|
|
@@ -35361,7 +35554,7 @@ async function reconcileState(prd, prdPath, workdir, config2) {
|
|
|
35361
35554
|
if (!hasCommits)
|
|
35362
35555
|
continue;
|
|
35363
35556
|
if (story.failureStage === "review" || story.failureStage === "autofix") {
|
|
35364
|
-
const effectiveWorkdir = story.workdir ?
|
|
35557
|
+
const effectiveWorkdir = story.workdir ? join52(workdir, story.workdir) : workdir;
|
|
35365
35558
|
try {
|
|
35366
35559
|
const reviewResult = await _reconcileDeps.runReview(config2.review, effectiveWorkdir, config2.execution);
|
|
35367
35560
|
if (!reviewResult.success) {
|
|
@@ -35542,7 +35735,7 @@ async function setupRun(options) {
|
|
|
35542
35735
|
}
|
|
35543
35736
|
try {
|
|
35544
35737
|
const globalPluginsDir = path18.join(os5.homedir(), ".nax", "plugins");
|
|
35545
|
-
const projectPluginsDir = path18.join(workdir, "nax", "plugins");
|
|
35738
|
+
const projectPluginsDir = path18.join(workdir, ".nax", "plugins");
|
|
35546
35739
|
const configPlugins = config2.plugins || [];
|
|
35547
35740
|
const pluginRegistry = await loadPlugins(globalPluginsDir, projectPluginsDir, configPlugins, workdir, config2.disabledPlugins);
|
|
35548
35741
|
logger?.info("plugins", `Loaded ${pluginRegistry.plugins.length} plugins`, {
|
|
@@ -66495,7 +66688,7 @@ var require_jsx_dev_runtime = __commonJS((exports, module) => {
|
|
|
66495
66688
|
init_source();
|
|
66496
66689
|
import { existsSync as existsSync34, mkdirSync as mkdirSync6 } from "fs";
|
|
66497
66690
|
import { homedir as homedir10 } from "os";
|
|
66498
|
-
import { join as
|
|
66691
|
+
import { join as join53 } from "path";
|
|
66499
66692
|
|
|
66500
66693
|
// node_modules/commander/esm.mjs
|
|
66501
66694
|
var import__ = __toESM(require_commander(), 1);
|
|
@@ -67191,7 +67384,7 @@ function formatMetadataSection(metadata) {
|
|
|
67191
67384
|
// src/context/generators/aider.ts
|
|
67192
67385
|
function generateAiderConfig(context) {
|
|
67193
67386
|
const header = `# Aider Configuration
|
|
67194
|
-
# Auto-generated from nax/context.md \u2014 run \`nax generate\` to regenerate.
|
|
67387
|
+
# Auto-generated from .nax/context.md \u2014 run \`nax generate\` to regenerate.
|
|
67195
67388
|
# DO NOT EDIT MANUALLY
|
|
67196
67389
|
|
|
67197
67390
|
# Project instructions
|
|
@@ -67215,7 +67408,7 @@ var aiderGenerator = {
|
|
|
67215
67408
|
function generateClaudeConfig(context) {
|
|
67216
67409
|
const header = `# Project Context
|
|
67217
67410
|
|
|
67218
|
-
This file is auto-generated from
|
|
67411
|
+
This file is auto-generated from \`.nax/context.md\`.
|
|
67219
67412
|
DO NOT EDIT MANUALLY \u2014 run \`nax generate\` to regenerate.
|
|
67220
67413
|
|
|
67221
67414
|
---
|
|
@@ -67234,7 +67427,7 @@ var claudeGenerator = {
|
|
|
67234
67427
|
function generateCodexConfig(context) {
|
|
67235
67428
|
const header = `# Codex Instructions
|
|
67236
67429
|
|
|
67237
|
-
This file is auto-generated from
|
|
67430
|
+
This file is auto-generated from \`.nax/context.md\`.
|
|
67238
67431
|
DO NOT EDIT MANUALLY \u2014 run \`nax generate\` to regenerate.
|
|
67239
67432
|
|
|
67240
67433
|
---
|
|
@@ -67253,7 +67446,7 @@ var codexGenerator = {
|
|
|
67253
67446
|
function generateCursorRules(context) {
|
|
67254
67447
|
const header = `# Project Rules
|
|
67255
67448
|
|
|
67256
|
-
Auto-generated from nax/context.md \u2014 run \`nax generate\` to regenerate.
|
|
67449
|
+
Auto-generated from .nax/context.md \u2014 run \`nax generate\` to regenerate.
|
|
67257
67450
|
DO NOT EDIT MANUALLY
|
|
67258
67451
|
|
|
67259
67452
|
---
|
|
@@ -67272,7 +67465,7 @@ var cursorGenerator = {
|
|
|
67272
67465
|
function generateGeminiConfig(context) {
|
|
67273
67466
|
const header = `# Gemini CLI Context
|
|
67274
67467
|
|
|
67275
|
-
This file is auto-generated from
|
|
67468
|
+
This file is auto-generated from \`.nax/context.md\`.
|
|
67276
67469
|
DO NOT EDIT MANUALLY \u2014 run \`nax generate\` to regenerate.
|
|
67277
67470
|
|
|
67278
67471
|
---
|
|
@@ -67291,7 +67484,7 @@ var geminiGenerator = {
|
|
|
67291
67484
|
function generateOpencodeConfig(context) {
|
|
67292
67485
|
const header = `# Agent Instructions
|
|
67293
67486
|
|
|
67294
|
-
This file is auto-generated from
|
|
67487
|
+
This file is auto-generated from \`.nax/context.md\`.
|
|
67295
67488
|
DO NOT EDIT MANUALLY \u2014 run \`nax generate\` to regenerate.
|
|
67296
67489
|
|
|
67297
67490
|
These instructions apply to all AI coding agents in this project.
|
|
@@ -67312,7 +67505,7 @@ var opencodeGenerator = {
|
|
|
67312
67505
|
function generateWindsurfRules(context) {
|
|
67313
67506
|
const header = `# Windsurf Project Rules
|
|
67314
67507
|
|
|
67315
|
-
Auto-generated from nax/context.md \u2014 run \`nax generate\` to regenerate.
|
|
67508
|
+
Auto-generated from .nax/context.md \u2014 run \`nax generate\` to regenerate.
|
|
67316
67509
|
DO NOT EDIT MANUALLY
|
|
67317
67510
|
|
|
67318
67511
|
---
|
|
@@ -67389,10 +67582,10 @@ async function generateAll(options, config2, agentFilter) {
|
|
|
67389
67582
|
async function discoverPackages(repoRoot) {
|
|
67390
67583
|
const packages = [];
|
|
67391
67584
|
const seen = new Set;
|
|
67392
|
-
for (const pattern of ["
|
|
67585
|
+
for (const pattern of [".nax/packages/*/context.md", ".nax/packages/*/*/context.md"]) {
|
|
67393
67586
|
const glob = new Bun.Glob(pattern);
|
|
67394
|
-
for await (const match of glob.scan(repoRoot)) {
|
|
67395
|
-
const pkgRelative = match.replace(
|
|
67587
|
+
for await (const match of glob.scan({ cwd: repoRoot, dot: true })) {
|
|
67588
|
+
const pkgRelative = match.replace(/^\.nax\/packages\//, "").replace(/\/context\.md$/, "");
|
|
67396
67589
|
const pkgAbsolute = join11(repoRoot, pkgRelative);
|
|
67397
67590
|
if (!seen.has(pkgAbsolute)) {
|
|
67398
67591
|
seen.add(pkgAbsolute);
|
|
@@ -67470,7 +67663,7 @@ async function discoverWorkspacePackages(repoRoot) {
|
|
|
67470
67663
|
return results.sort();
|
|
67471
67664
|
}
|
|
67472
67665
|
async function generateForPackage(packageDir, config2, dryRun = false) {
|
|
67473
|
-
const contextPath = join11(packageDir, "nax", "context.md");
|
|
67666
|
+
const contextPath = join11(packageDir, ".nax", "context.md");
|
|
67474
67667
|
if (!existsSync10(contextPath)) {
|
|
67475
67668
|
return [
|
|
67476
67669
|
{
|
|
@@ -67587,6 +67780,13 @@ function validateStory(raw, index, allIds) {
|
|
|
67587
67780
|
}
|
|
67588
67781
|
const rawTestStrategy = routing.testStrategy ?? s.testStrategy;
|
|
67589
67782
|
const testStrategy = resolveTestStrategy(typeof rawTestStrategy === "string" ? rawTestStrategy : undefined);
|
|
67783
|
+
const rawJustification = routing.noTestJustification ?? s.noTestJustification;
|
|
67784
|
+
if (testStrategy === "no-test") {
|
|
67785
|
+
if (!rawJustification || typeof rawJustification !== "string" || rawJustification.trim() === "") {
|
|
67786
|
+
throw new Error(`[schema] story[${index}].routing.noTestJustification is required when testStrategy is "no-test"`);
|
|
67787
|
+
}
|
|
67788
|
+
}
|
|
67789
|
+
const noTestJustification = typeof rawJustification === "string" && rawJustification.trim() !== "" ? rawJustification.trim() : undefined;
|
|
67590
67790
|
const rawDeps = s.dependencies;
|
|
67591
67791
|
const dependencies = Array.isArray(rawDeps) ? rawDeps : [];
|
|
67592
67792
|
for (const dep of dependencies) {
|
|
@@ -67626,7 +67826,8 @@ function validateStory(raw, index, allIds) {
|
|
|
67626
67826
|
routing: {
|
|
67627
67827
|
complexity,
|
|
67628
67828
|
testStrategy,
|
|
67629
|
-
reasoning: "validated from LLM output"
|
|
67829
|
+
reasoning: "validated from LLM output",
|
|
67830
|
+
...noTestJustification !== undefined ? { noTestJustification } : {}
|
|
67630
67831
|
},
|
|
67631
67832
|
...workdir !== undefined ? { workdir } : {},
|
|
67632
67833
|
...contextFiles.length > 0 ? { contextFiles } : {}
|
|
@@ -67693,9 +67894,9 @@ var _deps2 = {
|
|
|
67693
67894
|
createInteractionBridge: () => createCliInteractionBridge()
|
|
67694
67895
|
};
|
|
67695
67896
|
async function planCommand(workdir, config2, options) {
|
|
67696
|
-
const naxDir = join12(workdir, "nax");
|
|
67897
|
+
const naxDir = join12(workdir, ".nax");
|
|
67697
67898
|
if (!existsSync11(naxDir)) {
|
|
67698
|
-
throw new Error(
|
|
67899
|
+
throw new Error(`.nax directory not found. Run 'nax init' first in ${workdir}`);
|
|
67699
67900
|
}
|
|
67700
67901
|
const logger = getLogger();
|
|
67701
67902
|
logger?.info("plan", "Reading spec", { from: options.from });
|
|
@@ -68029,7 +68230,7 @@ async function acceptCommand(options) {
|
|
|
68029
68230
|
logger.error("cli", "Invalid project directory", { error: err.message });
|
|
68030
68231
|
throw new NaxError("Invalid project directory", "INVALID_DIRECTORY", { error: err.message });
|
|
68031
68232
|
}
|
|
68032
|
-
const featureDir = path.join(projectDir, "nax", "features", feature);
|
|
68233
|
+
const featureDir = path.join(projectDir, ".nax", "features", feature);
|
|
68033
68234
|
const prdPath = path.join(featureDir, "prd.json");
|
|
68034
68235
|
const prdFile = Bun.file(prdPath);
|
|
68035
68236
|
if (!await prdFile.exists()) {
|
|
@@ -68165,29 +68366,29 @@ function resolveProject(options = {}) {
|
|
|
68165
68366
|
let configPath;
|
|
68166
68367
|
if (dir) {
|
|
68167
68368
|
projectRoot = realpathSync3(resolve6(dir));
|
|
68168
|
-
naxDir = join13(projectRoot, "nax");
|
|
68369
|
+
naxDir = join13(projectRoot, ".nax");
|
|
68169
68370
|
if (!existsSync12(naxDir)) {
|
|
68170
68371
|
throw new NaxError(`Directory does not contain a nax project: ${projectRoot}
|
|
68171
68372
|
Expected to find: ${naxDir}`, "NAX_DIR_NOT_FOUND", { projectRoot, naxDir });
|
|
68172
68373
|
}
|
|
68173
68374
|
configPath = join13(naxDir, "config.json");
|
|
68174
68375
|
if (!existsSync12(configPath)) {
|
|
68175
|
-
throw new NaxError(
|
|
68376
|
+
throw new NaxError(`.nax directory found but config.json is missing: ${naxDir}
|
|
68176
68377
|
Expected to find: ${configPath}`, "CONFIG_NOT_FOUND", { naxDir, configPath });
|
|
68177
68378
|
}
|
|
68178
68379
|
} else {
|
|
68179
68380
|
const found = findProjectRoot(process.cwd());
|
|
68180
68381
|
if (!found) {
|
|
68181
|
-
const cwdNaxDir = join13(process.cwd(), "nax");
|
|
68382
|
+
const cwdNaxDir = join13(process.cwd(), ".nax");
|
|
68182
68383
|
if (existsSync12(cwdNaxDir)) {
|
|
68183
68384
|
const cwdConfigPath = join13(cwdNaxDir, "config.json");
|
|
68184
|
-
throw new NaxError(
|
|
68385
|
+
throw new NaxError(`.nax directory found but config.json is missing: ${cwdNaxDir}
|
|
68185
68386
|
Expected to find: ${cwdConfigPath}`, "CONFIG_NOT_FOUND", { naxDir: cwdNaxDir, configPath: cwdConfigPath });
|
|
68186
68387
|
}
|
|
68187
68388
|
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() });
|
|
68188
68389
|
}
|
|
68189
68390
|
projectRoot = found;
|
|
68190
|
-
naxDir = join13(projectRoot, "nax");
|
|
68391
|
+
naxDir = join13(projectRoot, ".nax");
|
|
68191
68392
|
configPath = join13(naxDir, "config.json");
|
|
68192
68393
|
}
|
|
68193
68394
|
let featureDir;
|
|
@@ -68220,7 +68421,7 @@ function findProjectRoot(startDir) {
|
|
|
68220
68421
|
let current = resolve6(startDir);
|
|
68221
68422
|
let depth = 0;
|
|
68222
68423
|
while (depth < MAX_DIRECTORY_DEPTH) {
|
|
68223
|
-
const naxDir = join13(current, "nax");
|
|
68424
|
+
const naxDir = join13(current, ".nax");
|
|
68224
68425
|
const configPath = join13(naxDir, "config.json");
|
|
68225
68426
|
if (existsSync12(configPath)) {
|
|
68226
68427
|
return realpathSync3(current);
|
|
@@ -68259,7 +68460,7 @@ async function loadStatusFile(featureDir) {
|
|
|
68259
68460
|
}
|
|
68260
68461
|
}
|
|
68261
68462
|
async function loadProjectStatusFile(projectDir) {
|
|
68262
|
-
const statusPath = join15(projectDir, "nax", "status.json");
|
|
68463
|
+
const statusPath = join15(projectDir, ".nax", "status.json");
|
|
68263
68464
|
if (!existsSync13(statusPath)) {
|
|
68264
68465
|
return null;
|
|
68265
68466
|
}
|
|
@@ -68325,7 +68526,7 @@ async function getFeatureSummary(featureName, featureDir) {
|
|
|
68325
68526
|
return summary;
|
|
68326
68527
|
}
|
|
68327
68528
|
async function displayAllFeatures(projectDir) {
|
|
68328
|
-
const featuresDir = join15(projectDir, "nax", "features");
|
|
68529
|
+
const featuresDir = join15(projectDir, ".nax", "features");
|
|
68329
68530
|
if (!existsSync13(featuresDir)) {
|
|
68330
68531
|
console.log(source_default.dim("No features found."));
|
|
68331
68532
|
return;
|
|
@@ -68530,7 +68731,7 @@ async function parseRunLog(logPath) {
|
|
|
68530
68731
|
async function runsListCommand(options) {
|
|
68531
68732
|
const logger = getLogger();
|
|
68532
68733
|
const { feature, workdir } = options;
|
|
68533
|
-
const runsDir = join16(workdir, "nax", "features", feature, "runs");
|
|
68734
|
+
const runsDir = join16(workdir, ".nax", "features", feature, "runs");
|
|
68534
68735
|
if (!existsSync14(runsDir)) {
|
|
68535
68736
|
logger.info("cli", "No runs found for feature", { feature, hint: `Directory not found: ${runsDir}` });
|
|
68536
68737
|
return;
|
|
@@ -68568,7 +68769,7 @@ async function runsListCommand(options) {
|
|
|
68568
68769
|
async function runsShowCommand(options) {
|
|
68569
68770
|
const logger = getLogger();
|
|
68570
68771
|
const { runId, feature, workdir } = options;
|
|
68571
|
-
const logPath = join16(workdir, "nax", "features", feature, "runs", `${runId}.jsonl`);
|
|
68772
|
+
const logPath = join16(workdir, ".nax", "features", feature, "runs", `${runId}.jsonl`);
|
|
68572
68773
|
if (!existsSync14(logPath)) {
|
|
68573
68774
|
logger.error("cli", "Run not found", { runId, feature, logPath });
|
|
68574
68775
|
throw new NaxError("Run not found", "RUN_NOT_FOUND", { runId, feature, logPath });
|
|
@@ -68731,9 +68932,9 @@ ${ctx.contextMarkdown}`;
|
|
|
68731
68932
|
async function promptsCommand(options) {
|
|
68732
68933
|
const logger = getLogger();
|
|
68733
68934
|
const { feature, workdir, config: config2, storyId, outputDir } = options;
|
|
68734
|
-
const naxDir = join29(workdir, "nax");
|
|
68935
|
+
const naxDir = join29(workdir, ".nax");
|
|
68735
68936
|
if (!existsSync18(naxDir)) {
|
|
68736
|
-
throw new Error(
|
|
68937
|
+
throw new Error(`.nax directory not found. Run 'nax init' first in ${workdir}`);
|
|
68737
68938
|
}
|
|
68738
68939
|
const featureDir = join29(naxDir, "features", feature);
|
|
68739
68940
|
const prdPath = join29(featureDir, "prd.json");
|
|
@@ -68845,13 +69046,13 @@ var TEMPLATE_HEADER = `<!--
|
|
|
68845
69046
|
- Conventions (project coding standards)
|
|
68846
69047
|
|
|
68847
69048
|
To activate overrides, add to your nax/config.json:
|
|
68848
|
-
{ "prompts": { "overrides": { "<role>": "nax/templates/<role>.md" } } }
|
|
69049
|
+
{ "prompts": { "overrides": { "<role>": ".nax/templates/<role>.md" } } }
|
|
68849
69050
|
-->
|
|
68850
69051
|
|
|
68851
69052
|
`;
|
|
68852
69053
|
async function promptsInitCommand(options) {
|
|
68853
69054
|
const { workdir, force = false, autoWireConfig = true } = options;
|
|
68854
|
-
const templatesDir = join30(workdir, "nax", "templates");
|
|
69055
|
+
const templatesDir = join30(workdir, ".nax", "templates");
|
|
68855
69056
|
mkdirSync4(templatesDir, { recursive: true });
|
|
68856
69057
|
const existingFiles = TEMPLATE_ROLES.map((t) => t.file).filter((f) => existsSync19(join30(templatesDir, f)));
|
|
68857
69058
|
if (existingFiles.length > 0 && !force) {
|
|
@@ -68882,11 +69083,11 @@ async function autoWirePromptsConfig(workdir) {
|
|
|
68882
69083
|
const exampleConfig = JSON.stringify({
|
|
68883
69084
|
prompts: {
|
|
68884
69085
|
overrides: {
|
|
68885
|
-
"test-writer": "nax/templates/test-writer.md",
|
|
68886
|
-
implementer: "nax/templates/implementer.md",
|
|
68887
|
-
verifier: "nax/templates/verifier.md",
|
|
68888
|
-
"single-session": "nax/templates/single-session.md",
|
|
68889
|
-
"tdd-simple": "nax/templates/tdd-simple.md"
|
|
69086
|
+
"test-writer": ".nax/templates/test-writer.md",
|
|
69087
|
+
implementer: ".nax/templates/implementer.md",
|
|
69088
|
+
verifier: ".nax/templates/verifier.md",
|
|
69089
|
+
"single-session": ".nax/templates/single-session.md",
|
|
69090
|
+
"tdd-simple": ".nax/templates/tdd-simple.md"
|
|
68890
69091
|
}
|
|
68891
69092
|
}
|
|
68892
69093
|
}, null, 2);
|
|
@@ -68904,11 +69105,11 @@ ${exampleConfig}`);
|
|
|
68904
69105
|
return;
|
|
68905
69106
|
}
|
|
68906
69107
|
const overrides = {
|
|
68907
|
-
"test-writer": "nax/templates/test-writer.md",
|
|
68908
|
-
implementer: "nax/templates/implementer.md",
|
|
68909
|
-
verifier: "nax/templates/verifier.md",
|
|
68910
|
-
"single-session": "nax/templates/single-session.md",
|
|
68911
|
-
"tdd-simple": "nax/templates/tdd-simple.md"
|
|
69108
|
+
"test-writer": ".nax/templates/test-writer.md",
|
|
69109
|
+
implementer: ".nax/templates/implementer.md",
|
|
69110
|
+
verifier: ".nax/templates/verifier.md",
|
|
69111
|
+
"single-session": ".nax/templates/single-session.md",
|
|
69112
|
+
"tdd-simple": ".nax/templates/tdd-simple.md"
|
|
68912
69113
|
};
|
|
68913
69114
|
if (!config2.prompts) {
|
|
68914
69115
|
config2.prompts = {};
|
|
@@ -68980,7 +69181,7 @@ import * as os2 from "os";
|
|
|
68980
69181
|
import * as path13 from "path";
|
|
68981
69182
|
async function pluginsListCommand(config2, workdir, overrideGlobalPluginsDir) {
|
|
68982
69183
|
const globalPluginsDir = overrideGlobalPluginsDir ?? path13.join(os2.homedir(), ".nax", "plugins");
|
|
68983
|
-
const projectPluginsDir = path13.join(workdir, "nax", "plugins");
|
|
69184
|
+
const projectPluginsDir = path13.join(workdir, ".nax", "plugins");
|
|
68984
69185
|
const configPlugins = config2.plugins || [];
|
|
68985
69186
|
const registry2 = await loadPlugins(globalPluginsDir, projectPluginsDir, configPlugins, workdir, config2.disabledPlugins);
|
|
68986
69187
|
const plugins = registry2.plugins;
|
|
@@ -68989,8 +69190,8 @@ async function pluginsListCommand(config2, workdir, overrideGlobalPluginsDir) {
|
|
|
68989
69190
|
console.log(`
|
|
68990
69191
|
To install plugins:`);
|
|
68991
69192
|
console.log(" \u2022 Add to global directory: ~/.nax/plugins/");
|
|
68992
|
-
console.log(" \u2022 Add to project directory:
|
|
68993
|
-
console.log(" \u2022 Configure in nax/config.json");
|
|
69193
|
+
console.log(" \u2022 Add to project directory: ./.nax/plugins/");
|
|
69194
|
+
console.log(" \u2022 Configure in .nax/config.json");
|
|
68994
69195
|
console.log(`
|
|
68995
69196
|
See https://github.com/nax/nax#plugins for more details.`);
|
|
68996
69197
|
return;
|
|
@@ -69241,7 +69442,7 @@ function isProcessAlive2(pid) {
|
|
|
69241
69442
|
}
|
|
69242
69443
|
}
|
|
69243
69444
|
async function loadStatusFile2(workdir) {
|
|
69244
|
-
const statusPath = join34(workdir, "nax", "status.json");
|
|
69445
|
+
const statusPath = join34(workdir, ".nax", "status.json");
|
|
69245
69446
|
if (!existsSync21(statusPath))
|
|
69246
69447
|
return null;
|
|
69247
69448
|
try {
|
|
@@ -69288,7 +69489,7 @@ async function diagnoseCommand(options = {}) {
|
|
|
69288
69489
|
const workdir = options.workdir ?? process.cwd();
|
|
69289
69490
|
const naxSubdir = findProjectDir(workdir);
|
|
69290
69491
|
let projectDir = naxSubdir ? join34(naxSubdir, "..") : null;
|
|
69291
|
-
if (!projectDir && existsSync21(join34(workdir, "nax"))) {
|
|
69492
|
+
if (!projectDir && existsSync21(join34(workdir, ".nax"))) {
|
|
69292
69493
|
projectDir = workdir;
|
|
69293
69494
|
}
|
|
69294
69495
|
if (!projectDir)
|
|
@@ -69299,7 +69500,7 @@ async function diagnoseCommand(options = {}) {
|
|
|
69299
69500
|
if (status2) {
|
|
69300
69501
|
feature = status2.run.feature;
|
|
69301
69502
|
} else {
|
|
69302
|
-
const featuresDir = join34(projectDir, "nax", "features");
|
|
69503
|
+
const featuresDir = join34(projectDir, ".nax", "features");
|
|
69303
69504
|
if (!existsSync21(featuresDir))
|
|
69304
69505
|
throw new Error("No features found in project");
|
|
69305
69506
|
const features = readdirSync5(featuresDir, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => e.name);
|
|
@@ -69309,7 +69510,7 @@ async function diagnoseCommand(options = {}) {
|
|
|
69309
69510
|
logger.info("diagnose", "No feature specified, using first found", { feature });
|
|
69310
69511
|
}
|
|
69311
69512
|
}
|
|
69312
|
-
const featureDir = join34(projectDir, "nax", "features", feature);
|
|
69513
|
+
const featureDir = join34(projectDir, ".nax", "features", feature);
|
|
69313
69514
|
const prdPath = join34(featureDir, "prd.json");
|
|
69314
69515
|
if (!existsSync21(prdPath))
|
|
69315
69516
|
throw new Error(`Feature not found: ${feature}`);
|
|
@@ -69368,10 +69569,10 @@ async function generateCommand(options) {
|
|
|
69368
69569
|
if (dryRun) {
|
|
69369
69570
|
console.log(source_default.yellow("\u26A0 Dry run \u2014 no files will be written"));
|
|
69370
69571
|
}
|
|
69371
|
-
console.log(source_default.blue("\u2192 Discovering packages with nax/context.md..."));
|
|
69572
|
+
console.log(source_default.blue("\u2192 Discovering packages with .nax/packages/*/context.md..."));
|
|
69372
69573
|
const packages = await discoverPackages(workdir);
|
|
69373
69574
|
if (packages.length === 0) {
|
|
69374
|
-
console.log(source_default.yellow(" No packages found (no
|
|
69575
|
+
console.log(source_default.yellow(" No packages found (no .nax/packages/*/context.md or .nax/packages/*/*/context.md)"));
|
|
69375
69576
|
return;
|
|
69376
69577
|
}
|
|
69377
69578
|
console.log(source_default.blue(`\u2192 Generating agent files for ${packages.length} package(s)...`));
|
|
@@ -69416,12 +69617,12 @@ async function generateCommand(options) {
|
|
|
69416
69617
|
process.exit(1);
|
|
69417
69618
|
return;
|
|
69418
69619
|
}
|
|
69419
|
-
const contextPath = options.context ? join35(workdir, options.context) : join35(workdir, "nax/context.md");
|
|
69620
|
+
const contextPath = options.context ? join35(workdir, options.context) : join35(workdir, ".nax/context.md");
|
|
69420
69621
|
const outputDir = options.output ? join35(workdir, options.output) : workdir;
|
|
69421
69622
|
const autoInject = !options.noAutoInject;
|
|
69422
69623
|
if (!existsSync22(contextPath)) {
|
|
69423
69624
|
console.error(source_default.red(`\u2717 Context file not found: ${contextPath}`));
|
|
69424
|
-
console.error(source_default.yellow(" Create nax/context.md first, or run `nax init` to scaffold it."));
|
|
69625
|
+
console.error(source_default.yellow(" Create .nax/context.md first, or run `nax init` to scaffold it."));
|
|
69425
69626
|
process.exit(1);
|
|
69426
69627
|
}
|
|
69427
69628
|
if (options.agent && !VALID_AGENTS.includes(options.agent)) {
|
|
@@ -69487,7 +69688,7 @@ async function generateCommand(options) {
|
|
|
69487
69688
|
const packages = await discoverPackages(workdir);
|
|
69488
69689
|
if (packages.length > 0) {
|
|
69489
69690
|
console.log(source_default.blue(`
|
|
69490
|
-
\u2192 Discovered ${packages.length} package(s) with
|
|
69691
|
+
\u2192 Discovered ${packages.length} package(s) with context.md \u2014 generating agent files...`));
|
|
69491
69692
|
let pkgErrorCount = 0;
|
|
69492
69693
|
for (const pkgDir of packages) {
|
|
69493
69694
|
const pkgResults = await generateForPackage(pkgDir, config2, dryRun);
|
|
@@ -69630,6 +69831,7 @@ var FIELD_DESCRIPTIONS = {
|
|
|
69630
69831
|
"acceptance.maxRetries": "Max retry loops for fix stories",
|
|
69631
69832
|
"acceptance.generateTests": "Generate acceptance tests during analyze",
|
|
69632
69833
|
"acceptance.testPath": "Path to acceptance test file (relative to feature dir)",
|
|
69834
|
+
"acceptance.timeoutMs": "Timeout for acceptance test generation in milliseconds (default: 1800000 = 30 min)",
|
|
69633
69835
|
context: "Context injection configuration",
|
|
69634
69836
|
"context.fileInjection": "Mode: 'disabled' (default, MCP-aware agents pull context on-demand) | 'keyword' (legacy git-grep injection for non-MCP agents). Set context.fileInjection in config.",
|
|
69635
69837
|
"context.testCoverage": "Test coverage context settings",
|
|
@@ -70262,7 +70464,7 @@ async function logsCommand(options) {
|
|
|
70262
70464
|
return;
|
|
70263
70465
|
}
|
|
70264
70466
|
const resolved = resolveProject({ dir: options.dir });
|
|
70265
|
-
const naxDir = join40(resolved.projectDir, "nax");
|
|
70467
|
+
const naxDir = join40(resolved.projectDir, ".nax");
|
|
70266
70468
|
const configPath = resolved.configPath;
|
|
70267
70469
|
const configFile = Bun.file(configPath);
|
|
70268
70470
|
const config2 = await configFile.json();
|
|
@@ -70312,7 +70514,7 @@ async function precheckCommand(options) {
|
|
|
70312
70514
|
process.exit(1);
|
|
70313
70515
|
}
|
|
70314
70516
|
}
|
|
70315
|
-
const naxDir = join41(resolved.projectDir, "nax");
|
|
70517
|
+
const naxDir = join41(resolved.projectDir, ".nax");
|
|
70316
70518
|
const featureDir = join41(naxDir, "features", featureName);
|
|
70317
70519
|
const prdPath = join41(featureDir, "prd.json");
|
|
70318
70520
|
if (!existsSync31(featureDir)) {
|
|
@@ -70628,7 +70830,14 @@ function precomputeBatchPlan(stories, maxBatchSize = DEFAULT_MAX_BATCH_SIZE) {
|
|
|
70628
70830
|
let currentBatch = [];
|
|
70629
70831
|
for (const story of stories) {
|
|
70630
70832
|
const isSimple = story.routing?.complexity === "simple" && story.routing?.testStrategy === "test-after";
|
|
70631
|
-
|
|
70833
|
+
const isNoTest = story.routing?.testStrategy === "no-test";
|
|
70834
|
+
const isBatchable = isSimple || isNoTest;
|
|
70835
|
+
if (isBatchable && currentBatch.length < maxBatchSize) {
|
|
70836
|
+
const batchIsNoTest = currentBatch.length > 0 && currentBatch[0]?.routing?.testStrategy === "no-test";
|
|
70837
|
+
if (currentBatch.length > 0 && batchIsNoTest !== isNoTest) {
|
|
70838
|
+
batches.push({ stories: [...currentBatch], isBatch: currentBatch.length > 1 });
|
|
70839
|
+
currentBatch = [];
|
|
70840
|
+
}
|
|
70632
70841
|
currentBatch.push(story);
|
|
70633
70842
|
} else {
|
|
70634
70843
|
if (currentBatch.length > 0) {
|
|
@@ -70638,11 +70847,8 @@ function precomputeBatchPlan(stories, maxBatchSize = DEFAULT_MAX_BATCH_SIZE) {
|
|
|
70638
70847
|
});
|
|
70639
70848
|
currentBatch = [];
|
|
70640
70849
|
}
|
|
70641
|
-
if (!
|
|
70642
|
-
batches.push({
|
|
70643
|
-
stories: [story],
|
|
70644
|
-
isBatch: false
|
|
70645
|
-
});
|
|
70850
|
+
if (!isBatchable) {
|
|
70851
|
+
batches.push({ stories: [story], isBatch: false });
|
|
70646
70852
|
} else {
|
|
70647
70853
|
currentBatch.push(story);
|
|
70648
70854
|
}
|
|
@@ -78321,15 +78527,15 @@ Next: nax generate --package ${options.package}`));
|
|
|
78321
78527
|
}
|
|
78322
78528
|
return;
|
|
78323
78529
|
}
|
|
78324
|
-
const naxDir =
|
|
78530
|
+
const naxDir = join53(workdir, "nax");
|
|
78325
78531
|
if (existsSync34(naxDir) && !options.force) {
|
|
78326
78532
|
console.log(source_default.yellow("nax already initialized. Use --force to overwrite."));
|
|
78327
78533
|
return;
|
|
78328
78534
|
}
|
|
78329
|
-
mkdirSync6(
|
|
78330
|
-
mkdirSync6(
|
|
78331
|
-
await Bun.write(
|
|
78332
|
-
await Bun.write(
|
|
78535
|
+
mkdirSync6(join53(naxDir, "features"), { recursive: true });
|
|
78536
|
+
mkdirSync6(join53(naxDir, "hooks"), { recursive: true });
|
|
78537
|
+
await Bun.write(join53(naxDir, "config.json"), JSON.stringify(DEFAULT_CONFIG, null, 2));
|
|
78538
|
+
await Bun.write(join53(naxDir, "hooks.json"), JSON.stringify({
|
|
78333
78539
|
hooks: {
|
|
78334
78540
|
"on-start": { command: 'echo "nax started: $NAX_FEATURE"', enabled: false },
|
|
78335
78541
|
"on-complete": { command: 'echo "nax complete: $NAX_FEATURE"', enabled: false },
|
|
@@ -78337,12 +78543,12 @@ Next: nax generate --package ${options.package}`));
|
|
|
78337
78543
|
"on-error": { command: 'echo "nax error: $NAX_REASON"', enabled: false }
|
|
78338
78544
|
}
|
|
78339
78545
|
}, null, 2));
|
|
78340
|
-
await Bun.write(
|
|
78546
|
+
await Bun.write(join53(naxDir, ".gitignore"), `# nax temp files
|
|
78341
78547
|
*.tmp
|
|
78342
78548
|
.paused.json
|
|
78343
78549
|
.nax-verifier-verdict.json
|
|
78344
78550
|
`);
|
|
78345
|
-
await Bun.write(
|
|
78551
|
+
await Bun.write(join53(naxDir, "context.md"), `# Project Context
|
|
78346
78552
|
|
|
78347
78553
|
This document defines coding standards, architectural decisions, and forbidden patterns for this project.
|
|
78348
78554
|
Run \`nax generate\` to regenerate agent config files (CLAUDE.md, AGENTS.md, .cursorrules, etc.) from this file.
|
|
@@ -78468,8 +78674,8 @@ program2.command("run").description("Run the orchestration loop for a feature").
|
|
|
78468
78674
|
console.error(source_default.red("nax not initialized. Run: nax init"));
|
|
78469
78675
|
process.exit(1);
|
|
78470
78676
|
}
|
|
78471
|
-
const featureDir =
|
|
78472
|
-
const prdPath =
|
|
78677
|
+
const featureDir = join53(naxDir, "features", options.feature);
|
|
78678
|
+
const prdPath = join53(featureDir, "prd.json");
|
|
78473
78679
|
if (options.plan && options.from) {
|
|
78474
78680
|
if (existsSync34(prdPath) && !options.force) {
|
|
78475
78681
|
console.error(source_default.red(`Error: prd.json already exists for feature "${options.feature}".`));
|
|
@@ -78491,10 +78697,10 @@ program2.command("run").description("Run the orchestration loop for a feature").
|
|
|
78491
78697
|
}
|
|
78492
78698
|
}
|
|
78493
78699
|
try {
|
|
78494
|
-
const planLogDir =
|
|
78700
|
+
const planLogDir = join53(featureDir, "plan");
|
|
78495
78701
|
mkdirSync6(planLogDir, { recursive: true });
|
|
78496
78702
|
const planLogId = new Date().toISOString().replace(/:/g, "-").replace(/\..+/, "");
|
|
78497
|
-
const planLogPath =
|
|
78703
|
+
const planLogPath = join53(planLogDir, `${planLogId}.jsonl`);
|
|
78498
78704
|
initLogger({ level: "info", filePath: planLogPath, useChalk: false, headless: true });
|
|
78499
78705
|
console.log(source_default.dim(` [Plan log: ${planLogPath}]`));
|
|
78500
78706
|
console.log(source_default.dim(" [Planning phase: generating PRD from spec]"));
|
|
@@ -78532,10 +78738,10 @@ program2.command("run").description("Run the orchestration loop for a feature").
|
|
|
78532
78738
|
process.exit(1);
|
|
78533
78739
|
}
|
|
78534
78740
|
resetLogger();
|
|
78535
|
-
const runsDir =
|
|
78741
|
+
const runsDir = join53(featureDir, "runs");
|
|
78536
78742
|
mkdirSync6(runsDir, { recursive: true });
|
|
78537
78743
|
const runId = new Date().toISOString().replace(/:/g, "-").replace(/\..+/, "");
|
|
78538
|
-
const logFilePath =
|
|
78744
|
+
const logFilePath = join53(runsDir, `${runId}.jsonl`);
|
|
78539
78745
|
const isTTY = process.stdout.isTTY ?? false;
|
|
78540
78746
|
const headlessFlag = options.headless ?? false;
|
|
78541
78747
|
const headlessEnv = process.env.NAX_HEADLESS === "1";
|
|
@@ -78551,7 +78757,7 @@ program2.command("run").description("Run the orchestration loop for a feature").
|
|
|
78551
78757
|
config2.autoMode.defaultAgent = options.agent;
|
|
78552
78758
|
}
|
|
78553
78759
|
config2.execution.maxIterations = Number.parseInt(options.maxIterations, 10);
|
|
78554
|
-
const globalNaxDir =
|
|
78760
|
+
const globalNaxDir = join53(homedir10(), ".nax");
|
|
78555
78761
|
const hooks = await loadHooksConfig(naxDir, globalNaxDir);
|
|
78556
78762
|
const eventEmitter = new PipelineEventEmitter;
|
|
78557
78763
|
let tuiInstance;
|
|
@@ -78574,7 +78780,7 @@ program2.command("run").description("Run the orchestration loop for a feature").
|
|
|
78574
78780
|
} else {
|
|
78575
78781
|
console.log(source_default.dim(" [Headless mode \u2014 pipe output]"));
|
|
78576
78782
|
}
|
|
78577
|
-
const statusFilePath =
|
|
78783
|
+
const statusFilePath = join53(workdir, "nax", "status.json");
|
|
78578
78784
|
let parallel;
|
|
78579
78785
|
if (options.parallel !== undefined) {
|
|
78580
78786
|
parallel = Number.parseInt(options.parallel, 10);
|
|
@@ -78600,7 +78806,7 @@ program2.command("run").description("Run the orchestration loop for a feature").
|
|
|
78600
78806
|
headless: useHeadless,
|
|
78601
78807
|
skipPrecheck: options.skipPrecheck ?? false
|
|
78602
78808
|
});
|
|
78603
|
-
const latestSymlink =
|
|
78809
|
+
const latestSymlink = join53(runsDir, "latest.jsonl");
|
|
78604
78810
|
try {
|
|
78605
78811
|
if (existsSync34(latestSymlink)) {
|
|
78606
78812
|
Bun.spawnSync(["rm", latestSymlink]);
|
|
@@ -78638,9 +78844,9 @@ features.command("create <name>").description("Create a new feature").option("-d
|
|
|
78638
78844
|
console.error(source_default.red("nax not initialized. Run: nax init"));
|
|
78639
78845
|
process.exit(1);
|
|
78640
78846
|
}
|
|
78641
|
-
const featureDir =
|
|
78847
|
+
const featureDir = join53(naxDir, "features", name);
|
|
78642
78848
|
mkdirSync6(featureDir, { recursive: true });
|
|
78643
|
-
await Bun.write(
|
|
78849
|
+
await Bun.write(join53(featureDir, "spec.md"), `# Feature: ${name}
|
|
78644
78850
|
|
|
78645
78851
|
## Overview
|
|
78646
78852
|
|
|
@@ -78648,7 +78854,7 @@ features.command("create <name>").description("Create a new feature").option("-d
|
|
|
78648
78854
|
|
|
78649
78855
|
## Acceptance Criteria
|
|
78650
78856
|
`);
|
|
78651
|
-
await Bun.write(
|
|
78857
|
+
await Bun.write(join53(featureDir, "plan.md"), `# Plan: ${name}
|
|
78652
78858
|
|
|
78653
78859
|
## Architecture
|
|
78654
78860
|
|
|
@@ -78656,7 +78862,7 @@ features.command("create <name>").description("Create a new feature").option("-d
|
|
|
78656
78862
|
|
|
78657
78863
|
## Dependencies
|
|
78658
78864
|
`);
|
|
78659
|
-
await Bun.write(
|
|
78865
|
+
await Bun.write(join53(featureDir, "tasks.md"), `# Tasks: ${name}
|
|
78660
78866
|
|
|
78661
78867
|
## US-001: [Title]
|
|
78662
78868
|
|
|
@@ -78665,7 +78871,7 @@ features.command("create <name>").description("Create a new feature").option("-d
|
|
|
78665
78871
|
### Acceptance Criteria
|
|
78666
78872
|
- [ ] Criterion 1
|
|
78667
78873
|
`);
|
|
78668
|
-
await Bun.write(
|
|
78874
|
+
await Bun.write(join53(featureDir, "progress.txt"), `# Progress: ${name}
|
|
78669
78875
|
|
|
78670
78876
|
Created: ${new Date().toISOString()}
|
|
78671
78877
|
|
|
@@ -78693,7 +78899,7 @@ features.command("list").description("List all features").option("-d, --dir <pat
|
|
|
78693
78899
|
console.error(source_default.red("nax not initialized."));
|
|
78694
78900
|
process.exit(1);
|
|
78695
78901
|
}
|
|
78696
|
-
const featuresDir =
|
|
78902
|
+
const featuresDir = join53(naxDir, "features");
|
|
78697
78903
|
if (!existsSync34(featuresDir)) {
|
|
78698
78904
|
console.log(source_default.dim("No features yet."));
|
|
78699
78905
|
return;
|
|
@@ -78708,7 +78914,7 @@ features.command("list").description("List all features").option("-d, --dir <pat
|
|
|
78708
78914
|
Features:
|
|
78709
78915
|
`));
|
|
78710
78916
|
for (const name of entries) {
|
|
78711
|
-
const prdPath =
|
|
78917
|
+
const prdPath = join53(featuresDir, name, "prd.json");
|
|
78712
78918
|
if (existsSync34(prdPath)) {
|
|
78713
78919
|
const prd = await loadPRD(prdPath);
|
|
78714
78920
|
const c = countStories(prd);
|
|
@@ -78739,10 +78945,10 @@ Use: nax plan -f <feature> --from <spec>`));
|
|
|
78739
78945
|
process.exit(1);
|
|
78740
78946
|
}
|
|
78741
78947
|
const config2 = await loadConfig(workdir);
|
|
78742
|
-
const featureLogDir =
|
|
78948
|
+
const featureLogDir = join53(naxDir, "features", options.feature, "plan");
|
|
78743
78949
|
mkdirSync6(featureLogDir, { recursive: true });
|
|
78744
78950
|
const planLogId = new Date().toISOString().replace(/:/g, "-").replace(/\..+/, "");
|
|
78745
|
-
const planLogPath =
|
|
78951
|
+
const planLogPath = join53(featureLogDir, `${planLogId}.jsonl`);
|
|
78746
78952
|
initLogger({ level: "info", filePath: planLogPath, useChalk: false, headless: true });
|
|
78747
78953
|
console.log(source_default.dim(` [Plan log: ${planLogPath}]`));
|
|
78748
78954
|
try {
|
|
@@ -78779,7 +78985,7 @@ program2.command("analyze").description("(deprecated) Parse spec.md into prd.jso
|
|
|
78779
78985
|
console.error(source_default.red("nax not initialized. Run: nax init"));
|
|
78780
78986
|
process.exit(1);
|
|
78781
78987
|
}
|
|
78782
|
-
const featureDir =
|
|
78988
|
+
const featureDir = join53(naxDir, "features", options.feature);
|
|
78783
78989
|
if (!existsSync34(featureDir)) {
|
|
78784
78990
|
console.error(source_default.red(`Feature "${options.feature}" not found.`));
|
|
78785
78991
|
process.exit(1);
|
|
@@ -78795,7 +79001,7 @@ program2.command("analyze").description("(deprecated) Parse spec.md into prd.jso
|
|
|
78795
79001
|
specPath: options.from,
|
|
78796
79002
|
reclassify: options.reclassify
|
|
78797
79003
|
});
|
|
78798
|
-
const prdPath =
|
|
79004
|
+
const prdPath = join53(featureDir, "prd.json");
|
|
78799
79005
|
await Bun.write(prdPath, JSON.stringify(prd, null, 2));
|
|
78800
79006
|
const c = countStories(prd);
|
|
78801
79007
|
console.log(source_default.green(`
|