@nathapp/nax 0.50.3 → 0.51.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +30 -0
- package/README.md +177 -104
- package/dist/nax.js +417 -213
- 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 -415
- 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 -138
- 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 -219
- 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 -218
- 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 -522
- package/src/config/schema-types.ts +0 -53
- package/src/config/schema.ts +0 -60
- package/src/config/schemas.ts +0 -426
- 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 -309
- 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 -144
- 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",
|
|
@@ -3312,7 +3321,8 @@ Decompose this spec into user stories. For each story, provide:
|
|
|
3312
3321
|
9. reasoning: Why this complexity level
|
|
3313
3322
|
10. estimatedLOC: Estimated lines of code to change
|
|
3314
3323
|
11. risks: Array of implementation risks
|
|
3315
|
-
12. testStrategy: "test-after" | "tdd-simple" | "three-session-tdd" | "three-session-tdd-lite"
|
|
3324
|
+
12. testStrategy: "no-test" | "test-after" | "tdd-simple" | "three-session-tdd" | "three-session-tdd-lite"
|
|
3325
|
+
13. noTestJustification: string (REQUIRED when testStrategy is "no-test" \u2014 explain why tests are unnecessary)
|
|
3316
3326
|
|
|
3317
3327
|
${COMPLEXITY_GUIDE}
|
|
3318
3328
|
|
|
@@ -17967,8 +17977,8 @@ var init_schemas3 = __esm(() => {
|
|
|
17967
17977
|
storySizeGate: StorySizeGateConfigSchema
|
|
17968
17978
|
});
|
|
17969
17979
|
PromptsConfigSchema = exports_external.object({
|
|
17970
|
-
overrides: exports_external.record(exports_external.string().refine((key) => ["test-writer", "implementer", "verifier", "single-session", "tdd-simple"].includes(key), {
|
|
17971
|
-
message: "Role must be one of: test-writer, implementer, verifier, single-session, tdd-simple"
|
|
17980
|
+
overrides: exports_external.record(exports_external.string().refine((key) => ["no-test", "test-writer", "implementer", "verifier", "single-session", "tdd-simple"].includes(key), {
|
|
17981
|
+
message: "Role must be one of: no-test, test-writer, implementer, verifier, single-session, tdd-simple"
|
|
17972
17982
|
}), exports_external.string().min(1, "Override path must be non-empty")).optional()
|
|
17973
17983
|
});
|
|
17974
17984
|
DecomposeConfigSchema = exports_external.object({
|
|
@@ -18761,7 +18771,8 @@ Rules:
|
|
|
18761
18771
|
- **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
|
|
18762
18772
|
- **NEVER use placeholder assertions** \u2014 no always-passing or always-failing stubs, no TODO comments as the only content, no empty test bodies
|
|
18763
18773
|
- Every test MUST have real assertions that PASS when the feature is correctly implemented and FAIL when it is broken
|
|
18764
|
-
- Output raw code only \u2014 no markdown fences, start directly with the language's import or package declaration
|
|
18774
|
+
- Output raw code only \u2014 no markdown fences, start directly with the language's import or package declaration
|
|
18775
|
+
- **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')\`).`;
|
|
18765
18776
|
const prompt = basePrompt;
|
|
18766
18777
|
logger.info("acceptance", "Generating tests from PRD refined criteria", { count: refinedCriteria.length });
|
|
18767
18778
|
const rawOutput = await (options.adapter ?? _generatorPRDDeps.adapter).complete(prompt, {
|
|
@@ -18846,7 +18857,8 @@ Rules:
|
|
|
18846
18857
|
- **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
|
|
18847
18858
|
- **NEVER use placeholder assertions** \u2014 no always-passing or always-failing stubs, no TODO comments as the only content, no empty test bodies
|
|
18848
18859
|
- Every test MUST have real assertions that PASS when the feature is correctly implemented and FAIL when it is broken
|
|
18849
|
-
- Output raw code only \u2014 no markdown fences, start directly with the language's import or package declaration
|
|
18860
|
+
- Output raw code only \u2014 no markdown fences, start directly with the language's import or package declaration
|
|
18861
|
+
- **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')\`).`;
|
|
18850
18862
|
}
|
|
18851
18863
|
async function generateAcceptanceTests(adapter, options) {
|
|
18852
18864
|
const logger = getLogger();
|
|
@@ -18961,7 +18973,34 @@ function findRelatedStories(failedAC, prd) {
|
|
|
18961
18973
|
const passedStories = prd.userStories.filter((s) => s.status === "passed").map((s) => s.id);
|
|
18962
18974
|
return passedStories.slice(0, 5);
|
|
18963
18975
|
}
|
|
18964
|
-
function
|
|
18976
|
+
function groupACsByRelatedStories(failedACs, prd) {
|
|
18977
|
+
const groups = new Map;
|
|
18978
|
+
for (const ac of failedACs) {
|
|
18979
|
+
const related = findRelatedStories(ac, prd);
|
|
18980
|
+
const key = [...related].sort().join(",");
|
|
18981
|
+
if (!groups.has(key)) {
|
|
18982
|
+
groups.set(key, { acs: [], relatedStories: related });
|
|
18983
|
+
}
|
|
18984
|
+
groups.get(key)?.acs.push(ac);
|
|
18985
|
+
}
|
|
18986
|
+
const result = Array.from(groups.values());
|
|
18987
|
+
while (result.length > MAX_FIX_STORIES) {
|
|
18988
|
+
result.sort((a, b) => a.acs.length - b.acs.length);
|
|
18989
|
+
const smallest = result.shift();
|
|
18990
|
+
if (!smallest)
|
|
18991
|
+
break;
|
|
18992
|
+
result[0].acs.push(...smallest.acs);
|
|
18993
|
+
for (const s of smallest.relatedStories) {
|
|
18994
|
+
if (!result[0].relatedStories.includes(s)) {
|
|
18995
|
+
result[0].relatedStories.push(s);
|
|
18996
|
+
}
|
|
18997
|
+
}
|
|
18998
|
+
}
|
|
18999
|
+
return result;
|
|
19000
|
+
}
|
|
19001
|
+
function buildFixPrompt(batchedACs, acTextMap, testOutput, relatedStories, prd, testFilePath) {
|
|
19002
|
+
const acList = batchedACs.map((ac) => `${ac}: ${acTextMap[ac] || "No description available"}`).join(`
|
|
19003
|
+
`);
|
|
18965
19004
|
const relatedStoriesText = relatedStories.map((id) => {
|
|
18966
19005
|
const story = prd.userStories.find((s) => s.id === id);
|
|
18967
19006
|
if (!story)
|
|
@@ -18971,43 +19010,47 @@ function buildFixPrompt(failedAC, acText, testOutput, relatedStories, prd) {
|
|
|
18971
19010
|
}).filter(Boolean).join(`
|
|
18972
19011
|
|
|
18973
19012
|
`);
|
|
18974
|
-
|
|
18975
|
-
|
|
18976
|
-
|
|
18977
|
-
|
|
19013
|
+
const testFileSection = testFilePath ? `
|
|
19014
|
+
ACCEPTANCE TEST FILE: ${testFilePath}
|
|
19015
|
+
(Read this file first to understand what each test expects)
|
|
19016
|
+
` : "";
|
|
19017
|
+
return `You are a debugging expert. Feature acceptance tests have failed.${testFileSection}
|
|
19018
|
+
FAILED ACCEPTANCE CRITERIA (${batchedACs.length} total):
|
|
19019
|
+
${acList}
|
|
18978
19020
|
|
|
18979
19021
|
TEST FAILURE OUTPUT:
|
|
18980
|
-
${testOutput}
|
|
19022
|
+
${testOutput.slice(0, 2000)}
|
|
18981
19023
|
|
|
18982
19024
|
RELATED STORIES (implemented this functionality):
|
|
18983
19025
|
${relatedStoriesText}
|
|
18984
19026
|
|
|
18985
|
-
Your task: Generate a fix
|
|
19027
|
+
Your task: Generate a fix description that will make these acceptance tests pass.
|
|
18986
19028
|
|
|
18987
19029
|
Requirements:
|
|
18988
|
-
1.
|
|
18989
|
-
2. Identify
|
|
18990
|
-
3.
|
|
18991
|
-
4.
|
|
19030
|
+
1. Read the acceptance test file first to understand what each failing test expects
|
|
19031
|
+
2. Identify the root cause based on the test failure output
|
|
19032
|
+
3. Find and fix the relevant implementation code (do NOT modify the test file)
|
|
19033
|
+
4. Write a clear, actionable fix description (2-4 sentences)
|
|
18992
19034
|
5. Reference the relevant story IDs if needed
|
|
18993
19035
|
|
|
18994
19036
|
Respond with ONLY the fix description (no JSON, no markdown, just the description text).`;
|
|
18995
19037
|
}
|
|
18996
19038
|
async function generateFixStories(adapter, options) {
|
|
18997
|
-
const { failedACs, testOutput, prd, specContent, modelDef } = options;
|
|
18998
|
-
const fixStories = [];
|
|
18999
|
-
const acTextMap = parseACTextFromSpec(specContent);
|
|
19039
|
+
const { failedACs, testOutput, prd, specContent, modelDef, testFilePath } = options;
|
|
19000
19040
|
const logger = getLogger();
|
|
19001
|
-
|
|
19002
|
-
|
|
19003
|
-
|
|
19004
|
-
|
|
19005
|
-
const relatedStories =
|
|
19041
|
+
const acTextMap = parseACTextFromSpec(specContent);
|
|
19042
|
+
const groups = groupACsByRelatedStories(failedACs, prd);
|
|
19043
|
+
const fixStories = [];
|
|
19044
|
+
for (let i = 0;i < groups.length; i++) {
|
|
19045
|
+
const { acs: batchedACs, relatedStories } = groups[i];
|
|
19006
19046
|
if (relatedStories.length === 0) {
|
|
19007
|
-
logger.warn("acceptance", "
|
|
19047
|
+
logger.warn("acceptance", "[WARN] No related stories found for AC group \u2014 skipping", { batchedACs });
|
|
19008
19048
|
continue;
|
|
19009
19049
|
}
|
|
19010
|
-
|
|
19050
|
+
logger.info("acceptance", "Generating fix for AC group", { batchedACs });
|
|
19051
|
+
const prompt = buildFixPrompt(batchedACs, acTextMap, testOutput, relatedStories, prd, testFilePath);
|
|
19052
|
+
const relatedStory = prd.userStories.find((s) => relatedStories.includes(s.id) && s.workdir);
|
|
19053
|
+
const workdir = relatedStory?.workdir;
|
|
19011
19054
|
try {
|
|
19012
19055
|
const fixDescription = await adapter.complete(prompt, {
|
|
19013
19056
|
model: modelDef.model,
|
|
@@ -19015,25 +19058,31 @@ async function generateFixStories(adapter, options) {
|
|
|
19015
19058
|
});
|
|
19016
19059
|
fixStories.push({
|
|
19017
19060
|
id: `US-FIX-${String(i + 1).padStart(3, "0")}`,
|
|
19018
|
-
title: `Fix: ${
|
|
19019
|
-
failedAC,
|
|
19061
|
+
title: `Fix: ${batchedACs.join(", ")} \u2014 ${(acTextMap[batchedACs[0]] || "").slice(0, 40)}`,
|
|
19062
|
+
failedAC: batchedACs[0],
|
|
19063
|
+
batchedACs,
|
|
19020
19064
|
testOutput,
|
|
19021
19065
|
relatedStories,
|
|
19022
|
-
description: fixDescription
|
|
19066
|
+
description: fixDescription,
|
|
19067
|
+
testFilePath,
|
|
19068
|
+
workdir
|
|
19023
19069
|
});
|
|
19024
|
-
logger.info("acceptance", "
|
|
19070
|
+
logger.info("acceptance", "[OK] Generated fix story", { storyId: fixStories[fixStories.length - 1].id });
|
|
19025
19071
|
} catch (error48) {
|
|
19026
|
-
logger.warn("acceptance", "
|
|
19027
|
-
|
|
19072
|
+
logger.warn("acceptance", "[WARN] Error generating fix", {
|
|
19073
|
+
batchedACs,
|
|
19028
19074
|
error: error48.message
|
|
19029
19075
|
});
|
|
19030
19076
|
fixStories.push({
|
|
19031
19077
|
id: `US-FIX-${String(i + 1).padStart(3, "0")}`,
|
|
19032
|
-
title: `Fix: ${
|
|
19033
|
-
failedAC,
|
|
19078
|
+
title: `Fix: ${batchedACs.join(", ")}`,
|
|
19079
|
+
failedAC: batchedACs[0],
|
|
19080
|
+
batchedACs,
|
|
19034
19081
|
testOutput,
|
|
19035
19082
|
relatedStories,
|
|
19036
|
-
description: `Fix the implementation to make ${
|
|
19083
|
+
description: `Fix the implementation to make ${batchedACs.join(", ")} pass. Related stories: ${relatedStories.join(", ")}.`,
|
|
19084
|
+
testFilePath,
|
|
19085
|
+
workdir
|
|
19037
19086
|
});
|
|
19038
19087
|
}
|
|
19039
19088
|
}
|
|
@@ -19054,20 +19103,40 @@ function parseACTextFromSpec(specContent) {
|
|
|
19054
19103
|
return map2;
|
|
19055
19104
|
}
|
|
19056
19105
|
function convertFixStoryToUserStory(fixStory) {
|
|
19106
|
+
const batchedACs = fixStory.batchedACs ?? [fixStory.failedAC];
|
|
19107
|
+
const acList = batchedACs.join(", ");
|
|
19108
|
+
const truncatedOutput = fixStory.testOutput.slice(0, 1000);
|
|
19109
|
+
const testFilePath = fixStory.testFilePath ?? "acceptance.test.ts";
|
|
19110
|
+
const enrichedDescription = [
|
|
19111
|
+
fixStory.description,
|
|
19112
|
+
"",
|
|
19113
|
+
`ACCEPTANCE TEST FILE: ${testFilePath}`,
|
|
19114
|
+
`FAILED ACCEPTANCE CRITERIA: ${acList}`,
|
|
19115
|
+
"",
|
|
19116
|
+
"TEST FAILURE OUTPUT:",
|
|
19117
|
+
truncatedOutput,
|
|
19118
|
+
"",
|
|
19119
|
+
"Instructions: Read the acceptance test file first to understand what each failing test expects.",
|
|
19120
|
+
"Then find the relevant source code and fix the implementation.",
|
|
19121
|
+
"Do NOT modify the test file."
|
|
19122
|
+
].join(`
|
|
19123
|
+
`);
|
|
19057
19124
|
return {
|
|
19058
19125
|
id: fixStory.id,
|
|
19059
19126
|
title: fixStory.title,
|
|
19060
|
-
description:
|
|
19061
|
-
acceptanceCriteria:
|
|
19127
|
+
description: enrichedDescription,
|
|
19128
|
+
acceptanceCriteria: batchedACs.map((ac) => `Fix ${ac}`),
|
|
19062
19129
|
tags: ["fix", "acceptance-failure"],
|
|
19063
19130
|
dependencies: fixStory.relatedStories,
|
|
19064
19131
|
status: "pending",
|
|
19065
19132
|
passes: false,
|
|
19066
19133
|
escalations: [],
|
|
19067
19134
|
attempts: 0,
|
|
19068
|
-
contextFiles: []
|
|
19135
|
+
contextFiles: [],
|
|
19136
|
+
workdir: fixStory.workdir
|
|
19069
19137
|
};
|
|
19070
19138
|
}
|
|
19139
|
+
var MAX_FIX_STORIES = 8;
|
|
19071
19140
|
var init_fix_generator = __esm(() => {
|
|
19072
19141
|
init_logger2();
|
|
19073
19142
|
});
|
|
@@ -19460,7 +19529,7 @@ async function closeAcpSession(session) {
|
|
|
19460
19529
|
}
|
|
19461
19530
|
}
|
|
19462
19531
|
function acpSessionsPath(workdir, featureName) {
|
|
19463
|
-
return join3(workdir, "nax", "features", featureName, "acp-sessions.json");
|
|
19532
|
+
return join3(workdir, ".nax", "features", featureName, "acp-sessions.json");
|
|
19464
19533
|
}
|
|
19465
19534
|
function sidecarSessionName(entry) {
|
|
19466
19535
|
return typeof entry === "string" ? entry : entry.sessionName;
|
|
@@ -20971,6 +21040,7 @@ import { join as join6, resolve as resolve4 } from "path";
|
|
|
20971
21040
|
function globalConfigDir() {
|
|
20972
21041
|
return join6(homedir3(), ".nax");
|
|
20973
21042
|
}
|
|
21043
|
+
var PROJECT_NAX_DIR = ".nax";
|
|
20974
21044
|
var init_paths = () => {};
|
|
20975
21045
|
|
|
20976
21046
|
// src/config/loader.ts
|
|
@@ -20983,7 +21053,7 @@ function findProjectDir(startDir = process.cwd()) {
|
|
|
20983
21053
|
let dir = resolve5(startDir);
|
|
20984
21054
|
let depth = 0;
|
|
20985
21055
|
while (depth < MAX_DIRECTORY_DEPTH) {
|
|
20986
|
-
const candidate = join7(dir,
|
|
21056
|
+
const candidate = join7(dir, PROJECT_NAX_DIR);
|
|
20987
21057
|
if (existsSync5(join7(candidate, "config.json"))) {
|
|
20988
21058
|
return candidate;
|
|
20989
21059
|
}
|
|
@@ -21053,7 +21123,7 @@ async function loadConfigForWorkdir(rootConfigPath, packageDir) {
|
|
|
21053
21123
|
return rootConfig;
|
|
21054
21124
|
}
|
|
21055
21125
|
const repoRoot = dirname2(rootNaxDir);
|
|
21056
|
-
const packageConfigPath = join7(repoRoot,
|
|
21126
|
+
const packageConfigPath = join7(repoRoot, PROJECT_NAX_DIR, "mono", packageDir, "config.json");
|
|
21057
21127
|
const packageOverride = await loadJsonFile(packageConfigPath, "config");
|
|
21058
21128
|
if (!packageOverride) {
|
|
21059
21129
|
return rootConfig;
|
|
@@ -22329,7 +22399,7 @@ var package_default;
|
|
|
22329
22399
|
var init_package = __esm(() => {
|
|
22330
22400
|
package_default = {
|
|
22331
22401
|
name: "@nathapp/nax",
|
|
22332
|
-
version: "0.
|
|
22402
|
+
version: "0.51.2",
|
|
22333
22403
|
description: "AI Coding Agent Orchestrator \u2014 loops until done",
|
|
22334
22404
|
type: "module",
|
|
22335
22405
|
bin: {
|
|
@@ -22380,8 +22450,6 @@ var init_package = __esm(() => {
|
|
|
22380
22450
|
],
|
|
22381
22451
|
files: [
|
|
22382
22452
|
"dist/",
|
|
22383
|
-
"src/",
|
|
22384
|
-
"bin/",
|
|
22385
22453
|
"README.md",
|
|
22386
22454
|
"CHANGELOG.md"
|
|
22387
22455
|
],
|
|
@@ -22403,8 +22471,8 @@ var init_version = __esm(() => {
|
|
|
22403
22471
|
NAX_VERSION = package_default.version;
|
|
22404
22472
|
NAX_COMMIT = (() => {
|
|
22405
22473
|
try {
|
|
22406
|
-
if (/^[0-9a-f]{6,10}$/.test("
|
|
22407
|
-
return "
|
|
22474
|
+
if (/^[0-9a-f]{6,10}$/.test("7f71f43"))
|
|
22475
|
+
return "7f71f43";
|
|
22408
22476
|
} catch {}
|
|
22409
22477
|
try {
|
|
22410
22478
|
const result = Bun.spawnSync(["git", "rev-parse", "--short", "HEAD"], {
|
|
@@ -22553,14 +22621,14 @@ function collectBatchMetrics(ctx, storyStartTime) {
|
|
|
22553
22621
|
});
|
|
22554
22622
|
}
|
|
22555
22623
|
async function saveRunMetrics(workdir, runMetrics) {
|
|
22556
|
-
const metricsPath = path2.join(workdir, "nax", "metrics.json");
|
|
22624
|
+
const metricsPath = path2.join(workdir, ".nax", "metrics.json");
|
|
22557
22625
|
const existing = await loadJsonFile(metricsPath, "metrics");
|
|
22558
22626
|
const allMetrics = Array.isArray(existing) ? existing : [];
|
|
22559
22627
|
allMetrics.push(runMetrics);
|
|
22560
22628
|
await saveJsonFile(metricsPath, allMetrics, "metrics");
|
|
22561
22629
|
}
|
|
22562
22630
|
async function loadRunMetrics(workdir) {
|
|
22563
|
-
const metricsPath = path2.join(workdir, "nax", "metrics.json");
|
|
22631
|
+
const metricsPath = path2.join(workdir, ".nax", "metrics.json");
|
|
22564
22632
|
const content = await loadJsonFile(metricsPath, "metrics");
|
|
22565
22633
|
return Array.isArray(content) ? content : [];
|
|
22566
22634
|
}
|
|
@@ -24254,10 +24322,18 @@ var init_agents = __esm(() => {
|
|
|
24254
24322
|
// src/pipeline/stages/acceptance-setup.ts
|
|
24255
24323
|
var exports_acceptance_setup = {};
|
|
24256
24324
|
__export(exports_acceptance_setup, {
|
|
24325
|
+
computeACFingerprint: () => computeACFingerprint,
|
|
24257
24326
|
acceptanceSetupStage: () => acceptanceSetupStage,
|
|
24258
24327
|
_acceptanceSetupDeps: () => _acceptanceSetupDeps
|
|
24259
24328
|
});
|
|
24260
24329
|
import path5 from "path";
|
|
24330
|
+
function computeACFingerprint(criteria) {
|
|
24331
|
+
const sorted = [...criteria].sort().join(`
|
|
24332
|
+
`);
|
|
24333
|
+
const hasher = new Bun.CryptoHasher("sha256");
|
|
24334
|
+
hasher.update(sorted);
|
|
24335
|
+
return `sha256:${hasher.digest("hex")}`;
|
|
24336
|
+
}
|
|
24261
24337
|
var _acceptanceSetupDeps, acceptanceSetupStage;
|
|
24262
24338
|
var init_acceptance_setup = __esm(() => {
|
|
24263
24339
|
init_config();
|
|
@@ -24269,6 +24345,27 @@ var init_acceptance_setup = __esm(() => {
|
|
|
24269
24345
|
writeFile: async (filePath, content) => {
|
|
24270
24346
|
await Bun.write(filePath, content);
|
|
24271
24347
|
},
|
|
24348
|
+
copyFile: async (src, dest) => {
|
|
24349
|
+
const content = await Bun.file(src).text();
|
|
24350
|
+
await Bun.write(dest, content);
|
|
24351
|
+
},
|
|
24352
|
+
deleteFile: async (filePath) => {
|
|
24353
|
+
const { unlink } = await import("fs/promises");
|
|
24354
|
+
await unlink(filePath);
|
|
24355
|
+
},
|
|
24356
|
+
readMeta: async (metaPath) => {
|
|
24357
|
+
const f = Bun.file(metaPath);
|
|
24358
|
+
if (!await f.exists())
|
|
24359
|
+
return null;
|
|
24360
|
+
try {
|
|
24361
|
+
return JSON.parse(await f.text());
|
|
24362
|
+
} catch {
|
|
24363
|
+
return null;
|
|
24364
|
+
}
|
|
24365
|
+
},
|
|
24366
|
+
writeMeta: async (metaPath, meta3) => {
|
|
24367
|
+
await Bun.write(metaPath, JSON.stringify(meta3, null, 2));
|
|
24368
|
+
},
|
|
24272
24369
|
runTest: async (_testPath, _workdir) => {
|
|
24273
24370
|
const proc = Bun.spawn(["bun", "test", _testPath], {
|
|
24274
24371
|
cwd: _workdir,
|
|
@@ -24302,11 +24399,22 @@ ${stderr}` };
|
|
|
24302
24399
|
return { action: "fail", reason: "[acceptance-setup] featureDir is not set" };
|
|
24303
24400
|
}
|
|
24304
24401
|
const testPath = path5.join(ctx.featureDir, "acceptance.test.ts");
|
|
24305
|
-
const
|
|
24402
|
+
const metaPath = path5.join(ctx.featureDir, "acceptance-meta.json");
|
|
24403
|
+
const allCriteria = ctx.prd.userStories.flatMap((s) => s.acceptanceCriteria);
|
|
24306
24404
|
let totalCriteria = 0;
|
|
24307
24405
|
let testableCount = 0;
|
|
24308
|
-
|
|
24309
|
-
|
|
24406
|
+
const fileExists = await _acceptanceSetupDeps.fileExists(testPath);
|
|
24407
|
+
let shouldGenerate = !fileExists;
|
|
24408
|
+
if (fileExists) {
|
|
24409
|
+
const fingerprint = computeACFingerprint(allCriteria);
|
|
24410
|
+
const meta3 = await _acceptanceSetupDeps.readMeta(metaPath);
|
|
24411
|
+
if (!meta3 || meta3.acFingerprint !== fingerprint) {
|
|
24412
|
+
await _acceptanceSetupDeps.copyFile(testPath, `${testPath}.bak`);
|
|
24413
|
+
await _acceptanceSetupDeps.deleteFile(testPath);
|
|
24414
|
+
shouldGenerate = true;
|
|
24415
|
+
}
|
|
24416
|
+
}
|
|
24417
|
+
if (shouldGenerate) {
|
|
24310
24418
|
totalCriteria = allCriteria.length;
|
|
24311
24419
|
const { getAgent: getAgent2 } = await Promise.resolve().then(() => (init_agents(), exports_agents));
|
|
24312
24420
|
const agent = (ctx.agentGetFn ?? getAgent2)(ctx.config.autoMode.defaultAgent);
|
|
@@ -24341,6 +24449,14 @@ ${stderr}` };
|
|
|
24341
24449
|
adapter: agent ?? undefined
|
|
24342
24450
|
});
|
|
24343
24451
|
await _acceptanceSetupDeps.writeFile(testPath, result.testCode);
|
|
24452
|
+
const fingerprint = computeACFingerprint(allCriteria);
|
|
24453
|
+
await _acceptanceSetupDeps.writeMeta(metaPath, {
|
|
24454
|
+
generatedAt: new Date().toISOString(),
|
|
24455
|
+
acFingerprint: fingerprint,
|
|
24456
|
+
storyCount: ctx.prd.userStories.length,
|
|
24457
|
+
acCount: totalCriteria,
|
|
24458
|
+
generator: "nax"
|
|
24459
|
+
});
|
|
24344
24460
|
}
|
|
24345
24461
|
if (ctx.config.acceptance.redGate === false) {
|
|
24346
24462
|
ctx.acceptanceSetup = { totalCriteria, testableCount, redFailCount: 0 };
|
|
@@ -24444,7 +24560,7 @@ function hasScript(packageJson, scriptName) {
|
|
|
24444
24560
|
return false;
|
|
24445
24561
|
return scriptName in scripts;
|
|
24446
24562
|
}
|
|
24447
|
-
async function resolveCommand(check2, config2, executionConfig, workdir) {
|
|
24563
|
+
async function resolveCommand(check2, config2, executionConfig, workdir, qualityCommands) {
|
|
24448
24564
|
if (executionConfig) {
|
|
24449
24565
|
if (check2 === "lint" && executionConfig.lintCommand !== undefined) {
|
|
24450
24566
|
return executionConfig.lintCommand;
|
|
@@ -24456,6 +24572,10 @@ async function resolveCommand(check2, config2, executionConfig, workdir) {
|
|
|
24456
24572
|
if (config2.commands[check2]) {
|
|
24457
24573
|
return config2.commands[check2] ?? null;
|
|
24458
24574
|
}
|
|
24575
|
+
const qualityCmd = qualityCommands?.[check2];
|
|
24576
|
+
if (qualityCmd) {
|
|
24577
|
+
return qualityCmd;
|
|
24578
|
+
}
|
|
24459
24579
|
const packageJson = await loadPackageJson(workdir);
|
|
24460
24580
|
if (hasScript(packageJson, check2)) {
|
|
24461
24581
|
return `bun run ${check2}`;
|
|
@@ -24553,7 +24673,7 @@ async function getUncommittedFilesImpl(workdir) {
|
|
|
24553
24673
|
return [];
|
|
24554
24674
|
}
|
|
24555
24675
|
}
|
|
24556
|
-
async function runReview(config2, workdir, executionConfig) {
|
|
24676
|
+
async function runReview(config2, workdir, executionConfig, qualityCommands) {
|
|
24557
24677
|
const startTime = Date.now();
|
|
24558
24678
|
const logger = getSafeLogger();
|
|
24559
24679
|
const checks3 = [];
|
|
@@ -24591,7 +24711,7 @@ Stage and commit these files before running review.`
|
|
|
24591
24711
|
};
|
|
24592
24712
|
}
|
|
24593
24713
|
for (const checkName of config2.checks) {
|
|
24594
|
-
const command = await resolveCommand(checkName, config2, executionConfig, workdir);
|
|
24714
|
+
const command = await resolveCommand(checkName, config2, executionConfig, workdir, qualityCommands);
|
|
24595
24715
|
if (command === null) {
|
|
24596
24716
|
getSafeLogger()?.warn("review", `Skipping ${checkName} check (command not configured or disabled)`);
|
|
24597
24717
|
continue;
|
|
@@ -24653,9 +24773,9 @@ async function getChangedFiles(workdir, baseRef) {
|
|
|
24653
24773
|
}
|
|
24654
24774
|
|
|
24655
24775
|
class ReviewOrchestrator {
|
|
24656
|
-
async review(reviewConfig, workdir, executionConfig, plugins, storyGitRef, scopePrefix) {
|
|
24776
|
+
async review(reviewConfig, workdir, executionConfig, plugins, storyGitRef, scopePrefix, qualityCommands) {
|
|
24657
24777
|
const logger = getSafeLogger();
|
|
24658
|
-
const builtIn = await runReview(reviewConfig, workdir, executionConfig);
|
|
24778
|
+
const builtIn = await runReview(reviewConfig, workdir, executionConfig, qualityCommands);
|
|
24659
24779
|
if (!builtIn.success) {
|
|
24660
24780
|
return { builtIn, success: false, failureReason: builtIn.failureReason, pluginFailed: false };
|
|
24661
24781
|
}
|
|
@@ -24745,7 +24865,7 @@ var init_review = __esm(() => {
|
|
|
24745
24865
|
const effectiveConfig = ctx.effectiveConfig ?? ctx.config;
|
|
24746
24866
|
logger.info("review", "Running review phase", { storyId: ctx.story.id });
|
|
24747
24867
|
const effectiveWorkdir = ctx.story.workdir ? join17(ctx.workdir, ctx.story.workdir) : ctx.workdir;
|
|
24748
|
-
const result = await reviewOrchestrator.review(effectiveConfig.review, effectiveWorkdir, effectiveConfig.execution, ctx.plugins, ctx.storyGitRef, ctx.story.workdir);
|
|
24868
|
+
const result = await reviewOrchestrator.review(effectiveConfig.review, effectiveWorkdir, effectiveConfig.execution, ctx.plugins, ctx.storyGitRef, ctx.story.workdir, effectiveConfig.quality?.commands);
|
|
24749
24869
|
ctx.reviewResult = result.builtIn;
|
|
24750
24870
|
if (!result.success) {
|
|
24751
24871
|
const allFindings = result.builtIn.pluginReviewers?.flatMap((pr) => pr.findings ?? []) ?? [];
|
|
@@ -25962,7 +26082,7 @@ function hookCtx(feature, opts) {
|
|
|
25962
26082
|
};
|
|
25963
26083
|
}
|
|
25964
26084
|
async function loadPackageContextMd(packageWorkdir) {
|
|
25965
|
-
const contextPath = `${packageWorkdir}
|
|
26085
|
+
const contextPath = `${packageWorkdir}/.nax/context.md`;
|
|
25966
26086
|
const file2 = Bun.file(contextPath);
|
|
25967
26087
|
if (!await file2.exists())
|
|
25968
26088
|
return null;
|
|
@@ -27144,6 +27264,11 @@ function buildIsolationSection(roleOrMode, mode, testCommand) {
|
|
|
27144
27264
|
const footer = `
|
|
27145
27265
|
|
|
27146
27266
|
${buildTestFilterRule(testCmd)}`;
|
|
27267
|
+
if (role === "no-test") {
|
|
27268
|
+
return `${header}
|
|
27269
|
+
|
|
27270
|
+
isolation scope: Implement changes in src/ and other non-test directories. Do NOT create or modify any files in the test/ directory.${footer}`;
|
|
27271
|
+
}
|
|
27147
27272
|
if (role === "test-writer") {
|
|
27148
27273
|
const m = mode ?? "strict";
|
|
27149
27274
|
if (m === "strict") {
|
|
@@ -27191,13 +27316,26 @@ function buildTestFrameworkHint(testCommand) {
|
|
|
27191
27316
|
return "Use Jest (describe/test/expect)";
|
|
27192
27317
|
return "Use your project's test framework";
|
|
27193
27318
|
}
|
|
27194
|
-
function buildRoleTaskSection(roleOrVariant, variant, testCommand, isolation) {
|
|
27319
|
+
function buildRoleTaskSection(roleOrVariant, variant, testCommand, isolation, noTestJustification) {
|
|
27195
27320
|
if ((roleOrVariant === "standard" || roleOrVariant === "lite") && variant === undefined) {
|
|
27196
27321
|
return buildRoleTaskSection("implementer", roleOrVariant, testCommand, isolation);
|
|
27197
27322
|
}
|
|
27198
27323
|
const role = roleOrVariant;
|
|
27199
27324
|
const testCmd = testCommand ?? DEFAULT_TEST_CMD2;
|
|
27200
27325
|
const frameworkHint = buildTestFrameworkHint(testCmd);
|
|
27326
|
+
if (role === "no-test") {
|
|
27327
|
+
const justification = noTestJustification ?? "No behavioral changes \u2014 tests not required";
|
|
27328
|
+
return `# Role: Implementer (No Tests)
|
|
27329
|
+
|
|
27330
|
+
Your task: implement the change as described. This story has no behavioral changes and does not require test modifications.
|
|
27331
|
+
|
|
27332
|
+
Instructions:
|
|
27333
|
+
- Implement the change as described in the story
|
|
27334
|
+
- Do NOT create or modify test files
|
|
27335
|
+
- Justification for no tests: ${justification}
|
|
27336
|
+
- When done, stage and commit ALL changed files with: git commit -m 'feat: <description>'
|
|
27337
|
+
- Goal: change implemented, no test files created or modified, all changes committed`;
|
|
27338
|
+
}
|
|
27201
27339
|
if (role === "implementer") {
|
|
27202
27340
|
const v = variant ?? "standard";
|
|
27203
27341
|
if (v === "standard") {
|
|
@@ -27472,6 +27610,7 @@ class PromptBuilder {
|
|
|
27472
27610
|
_loaderConfig;
|
|
27473
27611
|
_testCommand;
|
|
27474
27612
|
_hermeticConfig;
|
|
27613
|
+
_noTestJustification;
|
|
27475
27614
|
constructor(role, options = {}) {
|
|
27476
27615
|
this._role = role;
|
|
27477
27616
|
this._options = options;
|
|
@@ -27515,6 +27654,10 @@ class PromptBuilder {
|
|
|
27515
27654
|
this._hermeticConfig = config2;
|
|
27516
27655
|
return this;
|
|
27517
27656
|
}
|
|
27657
|
+
noTestJustification(justification) {
|
|
27658
|
+
this._noTestJustification = justification;
|
|
27659
|
+
return this;
|
|
27660
|
+
}
|
|
27518
27661
|
async build() {
|
|
27519
27662
|
const sections = [];
|
|
27520
27663
|
if (this._constitution) {
|
|
@@ -27574,7 +27717,7 @@ ${this._contextMd}
|
|
|
27574
27717
|
}
|
|
27575
27718
|
const variant = this._options.variant;
|
|
27576
27719
|
const isolation = this._options.isolation;
|
|
27577
|
-
return buildRoleTaskSection(this._role, variant, this._testCommand, isolation);
|
|
27720
|
+
return buildRoleTaskSection(this._role, variant, this._testCommand, isolation, this._noTestJustification);
|
|
27578
27721
|
}
|
|
27579
27722
|
}
|
|
27580
27723
|
var SECTION_SEP2 = `
|
|
@@ -28777,8 +28920,8 @@ var init_prompt = __esm(() => {
|
|
|
28777
28920
|
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);
|
|
28778
28921
|
prompt = await builder.build();
|
|
28779
28922
|
} else {
|
|
28780
|
-
const role = "tdd-simple";
|
|
28781
|
-
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);
|
|
28923
|
+
const role = ctx.routing.testStrategy === "no-test" ? "no-test" : "tdd-simple";
|
|
28924
|
+
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);
|
|
28782
28925
|
prompt = await builder.build();
|
|
28783
28926
|
}
|
|
28784
28927
|
ctx.prompt = prompt;
|
|
@@ -30411,8 +30554,7 @@ function generatePackageContextTemplate(packagePath) {
|
|
|
30411
30554
|
}
|
|
30412
30555
|
async function initPackage(repoRoot, packagePath, force = false) {
|
|
30413
30556
|
const logger = getLogger();
|
|
30414
|
-
const
|
|
30415
|
-
const naxDir = join31(packageDir, "nax");
|
|
30557
|
+
const naxDir = join31(repoRoot, ".nax", "mono", packagePath);
|
|
30416
30558
|
const contextPath = join31(naxDir, "context.md");
|
|
30417
30559
|
if (existsSync20(contextPath) && !force) {
|
|
30418
30560
|
logger.info("init", "Package context.md already exists (use --force to overwrite)", { path: contextPath });
|
|
@@ -30427,7 +30569,7 @@ async function initPackage(repoRoot, packagePath, force = false) {
|
|
|
30427
30569
|
}
|
|
30428
30570
|
async function initContext(projectRoot, options = {}) {
|
|
30429
30571
|
const logger = getLogger();
|
|
30430
|
-
const naxDir = join31(projectRoot, "nax");
|
|
30572
|
+
const naxDir = join31(projectRoot, ".nax");
|
|
30431
30573
|
const contextPath = join31(naxDir, "context.md");
|
|
30432
30574
|
if (existsSync20(contextPath) && !options.force) {
|
|
30433
30575
|
logger.info("init", "context.md already exists, skipping (use --force to overwrite)", { path: contextPath });
|
|
@@ -30444,7 +30586,7 @@ async function initContext(projectRoot, options = {}) {
|
|
|
30444
30586
|
content = generateContextTemplate(scan);
|
|
30445
30587
|
}
|
|
30446
30588
|
await Bun.write(contextPath, content);
|
|
30447
|
-
logger.info("init", "Generated nax/context.md template from project scan", { path: contextPath });
|
|
30589
|
+
logger.info("init", "Generated .nax/context.md template from project scan", { path: contextPath });
|
|
30448
30590
|
}
|
|
30449
30591
|
var _deps6;
|
|
30450
30592
|
var init_init_context = __esm(() => {
|
|
@@ -31015,18 +31157,18 @@ var NAX_RUNTIME_PATTERNS;
|
|
|
31015
31157
|
var init_checks_git = __esm(() => {
|
|
31016
31158
|
NAX_RUNTIME_PATTERNS = [
|
|
31017
31159
|
/^.{2} nax\.lock$/,
|
|
31018
|
-
/^.{2} nax\/$/,
|
|
31019
|
-
/^.{2} nax\/metrics\.json$/,
|
|
31020
|
-
/^.{2} nax\/features\/$/,
|
|
31021
|
-
/^.{2} nax\/features\/[^/]+\/$/,
|
|
31022
|
-
/^.{2} nax\/features\/[^/]+\/status\.json$/,
|
|
31023
|
-
/^.{2} nax\/features\/[^/]+\/prd\.json$/,
|
|
31024
|
-
/^.{2} nax\/features\/[^/]+\/runs\//,
|
|
31025
|
-
/^.{2} nax\/features\/[^/]+\/plan\//,
|
|
31026
|
-
/^.{2} nax\/features\/[^/]+\/acp-sessions\.json$/,
|
|
31027
|
-
/^.{2} nax\/features\/[^/]+\/interactions\//,
|
|
31028
|
-
/^.{2} nax\/features\/[^/]+\/progress\.txt$/,
|
|
31029
|
-
/^.{2} nax\/features\/[^/]+\/acceptance-refined\.json$/,
|
|
31160
|
+
/^.{2} \.nax\/$/,
|
|
31161
|
+
/^.{2} \.nax\/metrics\.json$/,
|
|
31162
|
+
/^.{2} \.nax\/features\/$/,
|
|
31163
|
+
/^.{2} \.nax\/features\/[^/]+\/$/,
|
|
31164
|
+
/^.{2} \.nax\/features\/[^/]+\/status\.json$/,
|
|
31165
|
+
/^.{2} \.nax\/features\/[^/]+\/prd\.json$/,
|
|
31166
|
+
/^.{2} \.nax\/features\/[^/]+\/runs\//,
|
|
31167
|
+
/^.{2} \.nax\/features\/[^/]+\/plan\//,
|
|
31168
|
+
/^.{2} \.nax\/features\/[^/]+\/acp-sessions\.json$/,
|
|
31169
|
+
/^.{2} \.nax\/features\/[^/]+\/interactions\//,
|
|
31170
|
+
/^.{2} \.nax\/features\/[^/]+\/progress\.txt$/,
|
|
31171
|
+
/^.{2} \.nax\/features\/[^/]+\/acceptance-refined\.json$/,
|
|
31030
31172
|
/^.{2} \.nax-verifier-verdict\.json$/,
|
|
31031
31173
|
/^.{2} \.nax-pids$/,
|
|
31032
31174
|
/^.{2} \.nax-wt\//
|
|
@@ -31337,9 +31479,9 @@ async function checkGitignoreCoversNax(workdir) {
|
|
|
31337
31479
|
const content = await file2.text();
|
|
31338
31480
|
const patterns = [
|
|
31339
31481
|
"nax.lock",
|
|
31340
|
-
"nax/**/runs/",
|
|
31341
|
-
"nax/metrics.json",
|
|
31342
|
-
"nax/features/*/status.json",
|
|
31482
|
+
".nax/**/runs/",
|
|
31483
|
+
".nax/metrics.json",
|
|
31484
|
+
".nax/features/*/status.json",
|
|
31343
31485
|
".nax-pids",
|
|
31344
31486
|
".nax-wt/"
|
|
31345
31487
|
];
|
|
@@ -32188,12 +32330,20 @@ var init_crash_recovery = __esm(() => {
|
|
|
32188
32330
|
var exports_acceptance_loop = {};
|
|
32189
32331
|
__export(exports_acceptance_loop, {
|
|
32190
32332
|
runAcceptanceLoop: () => runAcceptanceLoop,
|
|
32333
|
+
isTestLevelFailure: () => isTestLevelFailure,
|
|
32191
32334
|
isStubTestFile: () => isStubTestFile
|
|
32192
32335
|
});
|
|
32193
|
-
import path14 from "path";
|
|
32336
|
+
import path14, { join as join45 } from "path";
|
|
32194
32337
|
function isStubTestFile(content) {
|
|
32195
32338
|
return /expect\s*\(\s*true\s*\)\s*\.\s*toBe\s*\(\s*(?:false|true)\s*\)/.test(content);
|
|
32196
32339
|
}
|
|
32340
|
+
function isTestLevelFailure(failedACs, totalACs) {
|
|
32341
|
+
if (failedACs.includes("AC-ERROR"))
|
|
32342
|
+
return true;
|
|
32343
|
+
if (totalACs === 0)
|
|
32344
|
+
return false;
|
|
32345
|
+
return failedACs.length / totalACs > 0.8;
|
|
32346
|
+
}
|
|
32197
32347
|
async function loadSpecContent(featureDir) {
|
|
32198
32348
|
if (!featureDir)
|
|
32199
32349
|
return "";
|
|
@@ -32213,6 +32363,7 @@ async function generateAndAddFixStories(ctx, failures, prd) {
|
|
|
32213
32363
|
return null;
|
|
32214
32364
|
}
|
|
32215
32365
|
const modelDef = resolveModel(ctx.config.models[ctx.config.analyze.model]);
|
|
32366
|
+
const testFilePath = ctx.featureDir ? path14.join(ctx.featureDir, "acceptance.test.ts") : undefined;
|
|
32216
32367
|
const fixStories = await generateFixStories(agent, {
|
|
32217
32368
|
failedACs: failures.failedACs,
|
|
32218
32369
|
testOutput: failures.testOutput,
|
|
@@ -32220,7 +32371,8 @@ async function generateAndAddFixStories(ctx, failures, prd) {
|
|
|
32220
32371
|
specContent: await loadSpecContent(ctx.featureDir),
|
|
32221
32372
|
workdir: ctx.workdir,
|
|
32222
32373
|
modelDef,
|
|
32223
|
-
config: ctx.config
|
|
32374
|
+
config: ctx.config,
|
|
32375
|
+
testFilePath
|
|
32224
32376
|
});
|
|
32225
32377
|
if (fixStories.length === 0) {
|
|
32226
32378
|
logger?.error("acceptance", "Failed to generate fix stories");
|
|
@@ -32244,9 +32396,10 @@ async function executeFixStory(ctx, story, prd, iterations) {
|
|
|
32244
32396
|
agent: ctx.config.autoMode.defaultAgent,
|
|
32245
32397
|
iteration: iterations
|
|
32246
32398
|
}), ctx.workdir);
|
|
32399
|
+
const fixEffectiveConfig = story.workdir ? await loadConfigForWorkdir(join45(ctx.workdir, ".nax", "config.json"), story.workdir) : ctx.config;
|
|
32247
32400
|
const fixContext = {
|
|
32248
32401
|
config: ctx.config,
|
|
32249
|
-
effectiveConfig:
|
|
32402
|
+
effectiveConfig: fixEffectiveConfig,
|
|
32250
32403
|
prd,
|
|
32251
32404
|
story,
|
|
32252
32405
|
stories: [story],
|
|
@@ -32266,6 +32419,23 @@ async function executeFixStory(ctx, story, prd, iterations) {
|
|
|
32266
32419
|
metrics: result.context.storyMetrics
|
|
32267
32420
|
};
|
|
32268
32421
|
}
|
|
32422
|
+
async function regenerateAcceptanceTest(testPath, acceptanceContext) {
|
|
32423
|
+
const logger = getSafeLogger();
|
|
32424
|
+
const bakPath = `${testPath}.bak`;
|
|
32425
|
+
const content = await Bun.file(testPath).text();
|
|
32426
|
+
await Bun.write(bakPath, content);
|
|
32427
|
+
logger?.info("acceptance", `Backed up acceptance test -> ${bakPath}`);
|
|
32428
|
+
const { unlink: unlink3 } = await import("fs/promises");
|
|
32429
|
+
await unlink3(testPath);
|
|
32430
|
+
const { acceptanceSetupStage: acceptanceSetupStage2 } = await Promise.resolve().then(() => (init_acceptance_setup(), exports_acceptance_setup));
|
|
32431
|
+
await acceptanceSetupStage2.execute(acceptanceContext);
|
|
32432
|
+
if (!await Bun.file(testPath).exists()) {
|
|
32433
|
+
logger?.error("acceptance", "Acceptance test regeneration failed \u2014 manual intervention required");
|
|
32434
|
+
return false;
|
|
32435
|
+
}
|
|
32436
|
+
logger?.info("acceptance", "Acceptance test regenerated successfully");
|
|
32437
|
+
return true;
|
|
32438
|
+
}
|
|
32269
32439
|
async function runAcceptanceLoop(ctx) {
|
|
32270
32440
|
const logger = getSafeLogger();
|
|
32271
32441
|
const maxRetries = ctx.config.acceptance.maxRetries;
|
|
@@ -32343,9 +32513,23 @@ async function runAcceptanceLoop(ctx) {
|
|
|
32343
32513
|
logger?.error("acceptance", "Acceptance test generation failed after retry \u2014 manual implementation required");
|
|
32344
32514
|
return buildResult(false, prd, totalCost, iterations, storiesCompleted, prdDirty);
|
|
32345
32515
|
}
|
|
32516
|
+
continue;
|
|
32346
32517
|
}
|
|
32347
32518
|
}
|
|
32348
32519
|
}
|
|
32520
|
+
const totalACs = prd.userStories.filter((s) => !s.id.startsWith("US-FIX-")).flatMap((s) => s.acceptanceCriteria).length;
|
|
32521
|
+
if (ctx.featureDir && isTestLevelFailure(failures.failedACs, totalACs)) {
|
|
32522
|
+
logger?.warn("acceptance", `Test-level failure detected (${failures.failedACs.length}/${totalACs} ACs failed) \u2014 regenerating acceptance test`);
|
|
32523
|
+
const testPath = path14.join(ctx.featureDir, "acceptance.test.ts");
|
|
32524
|
+
const testFile = Bun.file(testPath);
|
|
32525
|
+
if (await testFile.exists()) {
|
|
32526
|
+
const regenerated = await regenerateAcceptanceTest(testPath, acceptanceContext);
|
|
32527
|
+
if (!regenerated) {
|
|
32528
|
+
return buildResult(false, prd, totalCost, iterations, storiesCompleted, prdDirty);
|
|
32529
|
+
}
|
|
32530
|
+
continue;
|
|
32531
|
+
}
|
|
32532
|
+
}
|
|
32349
32533
|
logger?.info("acceptance", "Generating fix stories...");
|
|
32350
32534
|
const fixStories = await generateAndAddFixStories(ctx, failures, prd);
|
|
32351
32535
|
if (!fixStories) {
|
|
@@ -32376,6 +32560,7 @@ async function runAcceptanceLoop(ctx) {
|
|
|
32376
32560
|
}
|
|
32377
32561
|
var init_acceptance_loop = __esm(() => {
|
|
32378
32562
|
init_acceptance();
|
|
32563
|
+
init_loader2();
|
|
32379
32564
|
init_schema();
|
|
32380
32565
|
init_hooks();
|
|
32381
32566
|
init_logger2();
|
|
@@ -32810,12 +32995,12 @@ __export(exports_manager, {
|
|
|
32810
32995
|
WorktreeManager: () => WorktreeManager
|
|
32811
32996
|
});
|
|
32812
32997
|
import { existsSync as existsSync32, symlinkSync } from "fs";
|
|
32813
|
-
import { join as
|
|
32998
|
+
import { join as join46 } from "path";
|
|
32814
32999
|
|
|
32815
33000
|
class WorktreeManager {
|
|
32816
33001
|
async create(projectRoot, storyId) {
|
|
32817
33002
|
validateStoryId(storyId);
|
|
32818
|
-
const worktreePath =
|
|
33003
|
+
const worktreePath = join46(projectRoot, ".nax-wt", storyId);
|
|
32819
33004
|
const branchName = `nax/${storyId}`;
|
|
32820
33005
|
try {
|
|
32821
33006
|
const proc = Bun.spawn(["git", "worktree", "add", worktreePath, "-b", branchName], {
|
|
@@ -32840,9 +33025,9 @@ class WorktreeManager {
|
|
|
32840
33025
|
}
|
|
32841
33026
|
throw new Error(`Failed to create worktree: ${String(error48)}`);
|
|
32842
33027
|
}
|
|
32843
|
-
const nodeModulesSource =
|
|
33028
|
+
const nodeModulesSource = join46(projectRoot, "node_modules");
|
|
32844
33029
|
if (existsSync32(nodeModulesSource)) {
|
|
32845
|
-
const nodeModulesTarget =
|
|
33030
|
+
const nodeModulesTarget = join46(worktreePath, "node_modules");
|
|
32846
33031
|
try {
|
|
32847
33032
|
symlinkSync(nodeModulesSource, nodeModulesTarget, "dir");
|
|
32848
33033
|
} catch (error48) {
|
|
@@ -32850,9 +33035,9 @@ class WorktreeManager {
|
|
|
32850
33035
|
throw new Error(`Failed to symlink node_modules: ${errorMessage(error48)}`);
|
|
32851
33036
|
}
|
|
32852
33037
|
}
|
|
32853
|
-
const envSource =
|
|
33038
|
+
const envSource = join46(projectRoot, ".env");
|
|
32854
33039
|
if (existsSync32(envSource)) {
|
|
32855
|
-
const envTarget =
|
|
33040
|
+
const envTarget = join46(worktreePath, ".env");
|
|
32856
33041
|
try {
|
|
32857
33042
|
symlinkSync(envSource, envTarget, "file");
|
|
32858
33043
|
} catch (error48) {
|
|
@@ -32863,7 +33048,7 @@ class WorktreeManager {
|
|
|
32863
33048
|
}
|
|
32864
33049
|
async remove(projectRoot, storyId) {
|
|
32865
33050
|
validateStoryId(storyId);
|
|
32866
|
-
const worktreePath =
|
|
33051
|
+
const worktreePath = join46(projectRoot, ".nax-wt", storyId);
|
|
32867
33052
|
const branchName = `nax/${storyId}`;
|
|
32868
33053
|
try {
|
|
32869
33054
|
const proc = Bun.spawn(["git", "worktree", "remove", worktreePath, "--force"], {
|
|
@@ -33254,7 +33439,7 @@ var init_parallel_worker = __esm(() => {
|
|
|
33254
33439
|
|
|
33255
33440
|
// src/execution/parallel-coordinator.ts
|
|
33256
33441
|
import os3 from "os";
|
|
33257
|
-
import { join as
|
|
33442
|
+
import { join as join47 } from "path";
|
|
33258
33443
|
function groupStoriesByDependencies(stories) {
|
|
33259
33444
|
const batches = [];
|
|
33260
33445
|
const processed = new Set;
|
|
@@ -33333,7 +33518,7 @@ async function executeParallel(stories, prdPath, projectRoot, config2, hooks, pl
|
|
|
33333
33518
|
};
|
|
33334
33519
|
const worktreePaths = new Map;
|
|
33335
33520
|
for (const story of batch) {
|
|
33336
|
-
const worktreePath =
|
|
33521
|
+
const worktreePath = join47(projectRoot, ".nax-wt", story.id);
|
|
33337
33522
|
try {
|
|
33338
33523
|
await worktreeManager.create(projectRoot, story.id);
|
|
33339
33524
|
worktreePaths.set(story.id, worktreePath);
|
|
@@ -33382,7 +33567,7 @@ async function executeParallel(stories, prdPath, projectRoot, config2, hooks, pl
|
|
|
33382
33567
|
});
|
|
33383
33568
|
logger?.warn("parallel", "Worktree preserved for manual conflict resolution", {
|
|
33384
33569
|
storyId: mergeResult.storyId,
|
|
33385
|
-
worktreePath:
|
|
33570
|
+
worktreePath: join47(projectRoot, ".nax-wt", mergeResult.storyId)
|
|
33386
33571
|
});
|
|
33387
33572
|
}
|
|
33388
33573
|
}
|
|
@@ -33842,12 +34027,12 @@ var init_parallel_executor = __esm(() => {
|
|
|
33842
34027
|
// src/pipeline/subscribers/events-writer.ts
|
|
33843
34028
|
import { appendFile as appendFile2, mkdir as mkdir2 } from "fs/promises";
|
|
33844
34029
|
import { homedir as homedir7 } from "os";
|
|
33845
|
-
import { basename as basename5, join as
|
|
34030
|
+
import { basename as basename5, join as join48 } from "path";
|
|
33846
34031
|
function wireEventsWriter(bus, feature, runId, workdir) {
|
|
33847
34032
|
const logger = getSafeLogger();
|
|
33848
34033
|
const project = basename5(workdir);
|
|
33849
|
-
const eventsDir =
|
|
33850
|
-
const eventsFile =
|
|
34034
|
+
const eventsDir = join48(homedir7(), ".nax", "events", project);
|
|
34035
|
+
const eventsFile = join48(eventsDir, "events.jsonl");
|
|
33851
34036
|
let dirReady = false;
|
|
33852
34037
|
const write = (line) => {
|
|
33853
34038
|
(async () => {
|
|
@@ -34021,12 +34206,12 @@ var init_interaction2 = __esm(() => {
|
|
|
34021
34206
|
// src/pipeline/subscribers/registry.ts
|
|
34022
34207
|
import { mkdir as mkdir3, writeFile } from "fs/promises";
|
|
34023
34208
|
import { homedir as homedir8 } from "os";
|
|
34024
|
-
import { basename as basename6, join as
|
|
34209
|
+
import { basename as basename6, join as join49 } from "path";
|
|
34025
34210
|
function wireRegistry(bus, feature, runId, workdir) {
|
|
34026
34211
|
const logger = getSafeLogger();
|
|
34027
34212
|
const project = basename6(workdir);
|
|
34028
|
-
const runDir =
|
|
34029
|
-
const metaFile =
|
|
34213
|
+
const runDir = join49(homedir8(), ".nax", "runs", `${project}-${feature}-${runId}`);
|
|
34214
|
+
const metaFile = join49(runDir, "meta.json");
|
|
34030
34215
|
const unsub = bus.on("run:started", (_ev) => {
|
|
34031
34216
|
(async () => {
|
|
34032
34217
|
try {
|
|
@@ -34036,8 +34221,8 @@ function wireRegistry(bus, feature, runId, workdir) {
|
|
|
34036
34221
|
project,
|
|
34037
34222
|
feature,
|
|
34038
34223
|
workdir,
|
|
34039
|
-
statusPath:
|
|
34040
|
-
eventsDir:
|
|
34224
|
+
statusPath: join49(workdir, ".nax", "features", feature, "status.json"),
|
|
34225
|
+
eventsDir: join49(workdir, ".nax", "features", feature, "runs"),
|
|
34041
34226
|
registeredAt: new Date().toISOString()
|
|
34042
34227
|
};
|
|
34043
34228
|
await writeFile(metaFile, JSON.stringify(meta3, null, 2));
|
|
@@ -34464,7 +34649,7 @@ async function handleTierEscalation(ctx) {
|
|
|
34464
34649
|
}
|
|
34465
34650
|
for (const s of storiesToEscalate) {
|
|
34466
34651
|
const currentTestStrategy = s.routing?.testStrategy ?? ctx.routing.testStrategy;
|
|
34467
|
-
const shouldSwitchToTestAfter = escalateRetryAsTestAfter && currentTestStrategy !== "test-after";
|
|
34652
|
+
const shouldSwitchToTestAfter = escalateRetryAsTestAfter && currentTestStrategy !== "test-after" && currentTestStrategy !== "no-test";
|
|
34468
34653
|
if (shouldSwitchToTestAfter) {
|
|
34469
34654
|
logger?.warn("escalation", "Switching strategy to test-after (greenfield-no-tests fallback)", {
|
|
34470
34655
|
storyId: s.id,
|
|
@@ -34488,7 +34673,7 @@ async function handleTierEscalation(ctx) {
|
|
|
34488
34673
|
if (!shouldEscalate)
|
|
34489
34674
|
return s;
|
|
34490
34675
|
const currentTestStrategy = s.routing?.testStrategy ?? ctx.routing.testStrategy;
|
|
34491
|
-
const shouldSwitchToTestAfter = escalateRetryAsTestAfter && currentTestStrategy !== "test-after";
|
|
34676
|
+
const shouldSwitchToTestAfter = escalateRetryAsTestAfter && currentTestStrategy !== "test-after" && currentTestStrategy !== "no-test";
|
|
34492
34677
|
const baseRouting = s.routing ?? { ...ctx.routing };
|
|
34493
34678
|
const updatedRouting = {
|
|
34494
34679
|
...baseRouting,
|
|
@@ -34690,7 +34875,7 @@ var init_pipeline_result_handler = __esm(() => {
|
|
|
34690
34875
|
});
|
|
34691
34876
|
|
|
34692
34877
|
// src/execution/iteration-runner.ts
|
|
34693
|
-
import { join as
|
|
34878
|
+
import { join as join50 } from "path";
|
|
34694
34879
|
async function runIteration(ctx, prd, selection, iterations, totalCost, allStoryMetrics) {
|
|
34695
34880
|
const logger = getSafeLogger();
|
|
34696
34881
|
const { story, storiesToExecute, routing, isBatchExecution } = selection;
|
|
@@ -34716,7 +34901,7 @@ async function runIteration(ctx, prd, selection, iterations, totalCost, allStory
|
|
|
34716
34901
|
const storyStartTime = Date.now();
|
|
34717
34902
|
const storyGitRef = await captureGitRef(ctx.workdir);
|
|
34718
34903
|
const accumulatedAttemptCost = (story.priorFailures || []).reduce((sum, f) => sum + (f.cost || 0), 0);
|
|
34719
|
-
const effectiveConfig = story.workdir ? await _iterationRunnerDeps.loadConfigForWorkdir(
|
|
34904
|
+
const effectiveConfig = story.workdir ? await _iterationRunnerDeps.loadConfigForWorkdir(join50(ctx.workdir, ".nax", "config.json"), story.workdir) : ctx.config;
|
|
34720
34905
|
const pipelineContext = {
|
|
34721
34906
|
config: ctx.config,
|
|
34722
34907
|
effectiveConfig,
|
|
@@ -35082,7 +35267,7 @@ async function writeStatusFile(filePath, status) {
|
|
|
35082
35267
|
var init_status_file = () => {};
|
|
35083
35268
|
|
|
35084
35269
|
// src/execution/status-writer.ts
|
|
35085
|
-
import { join as
|
|
35270
|
+
import { join as join51 } from "path";
|
|
35086
35271
|
|
|
35087
35272
|
class StatusWriter {
|
|
35088
35273
|
statusFile;
|
|
@@ -35150,7 +35335,7 @@ class StatusWriter {
|
|
|
35150
35335
|
if (!this._prd)
|
|
35151
35336
|
return;
|
|
35152
35337
|
const safeLogger = getSafeLogger();
|
|
35153
|
-
const featureStatusPath =
|
|
35338
|
+
const featureStatusPath = join51(featureDir, "status.json");
|
|
35154
35339
|
try {
|
|
35155
35340
|
const base = this.getSnapshot(totalCost, iterations);
|
|
35156
35341
|
if (!base) {
|
|
@@ -35358,7 +35543,7 @@ __export(exports_run_initialization, {
|
|
|
35358
35543
|
initializeRun: () => initializeRun,
|
|
35359
35544
|
_reconcileDeps: () => _reconcileDeps
|
|
35360
35545
|
});
|
|
35361
|
-
import { join as
|
|
35546
|
+
import { join as join52 } from "path";
|
|
35362
35547
|
async function reconcileState(prd, prdPath, workdir, config2) {
|
|
35363
35548
|
const logger = getSafeLogger();
|
|
35364
35549
|
let reconciledCount = 0;
|
|
@@ -35370,7 +35555,7 @@ async function reconcileState(prd, prdPath, workdir, config2) {
|
|
|
35370
35555
|
if (!hasCommits)
|
|
35371
35556
|
continue;
|
|
35372
35557
|
if (story.failureStage === "review" || story.failureStage === "autofix") {
|
|
35373
|
-
const effectiveWorkdir = story.workdir ?
|
|
35558
|
+
const effectiveWorkdir = story.workdir ? join52(workdir, story.workdir) : workdir;
|
|
35374
35559
|
try {
|
|
35375
35560
|
const reviewResult = await _reconcileDeps.runReview(config2.review, effectiveWorkdir, config2.execution);
|
|
35376
35561
|
if (!reviewResult.success) {
|
|
@@ -35551,7 +35736,7 @@ async function setupRun(options) {
|
|
|
35551
35736
|
}
|
|
35552
35737
|
try {
|
|
35553
35738
|
const globalPluginsDir = path18.join(os5.homedir(), ".nax", "plugins");
|
|
35554
|
-
const projectPluginsDir = path18.join(workdir, "nax", "plugins");
|
|
35739
|
+
const projectPluginsDir = path18.join(workdir, ".nax", "plugins");
|
|
35555
35740
|
const configPlugins = config2.plugins || [];
|
|
35556
35741
|
const pluginRegistry = await loadPlugins(globalPluginsDir, projectPluginsDir, configPlugins, workdir, config2.disabledPlugins);
|
|
35557
35742
|
logger?.info("plugins", `Loaded ${pluginRegistry.plugins.length} plugins`, {
|
|
@@ -66504,7 +66689,7 @@ var require_jsx_dev_runtime = __commonJS((exports, module) => {
|
|
|
66504
66689
|
init_source();
|
|
66505
66690
|
import { existsSync as existsSync34, mkdirSync as mkdirSync6 } from "fs";
|
|
66506
66691
|
import { homedir as homedir10 } from "os";
|
|
66507
|
-
import { join as
|
|
66692
|
+
import { join as join53 } from "path";
|
|
66508
66693
|
|
|
66509
66694
|
// node_modules/commander/esm.mjs
|
|
66510
66695
|
var import__ = __toESM(require_commander(), 1);
|
|
@@ -67200,7 +67385,7 @@ function formatMetadataSection(metadata) {
|
|
|
67200
67385
|
// src/context/generators/aider.ts
|
|
67201
67386
|
function generateAiderConfig(context) {
|
|
67202
67387
|
const header = `# Aider Configuration
|
|
67203
|
-
# Auto-generated from nax/context.md \u2014 run \`nax generate\` to regenerate.
|
|
67388
|
+
# Auto-generated from .nax/context.md \u2014 run \`nax generate\` to regenerate.
|
|
67204
67389
|
# DO NOT EDIT MANUALLY
|
|
67205
67390
|
|
|
67206
67391
|
# Project instructions
|
|
@@ -67224,7 +67409,7 @@ var aiderGenerator = {
|
|
|
67224
67409
|
function generateClaudeConfig(context) {
|
|
67225
67410
|
const header = `# Project Context
|
|
67226
67411
|
|
|
67227
|
-
This file is auto-generated from
|
|
67412
|
+
This file is auto-generated from \`.nax/context.md\`.
|
|
67228
67413
|
DO NOT EDIT MANUALLY \u2014 run \`nax generate\` to regenerate.
|
|
67229
67414
|
|
|
67230
67415
|
---
|
|
@@ -67243,7 +67428,7 @@ var claudeGenerator = {
|
|
|
67243
67428
|
function generateCodexConfig(context) {
|
|
67244
67429
|
const header = `# Codex Instructions
|
|
67245
67430
|
|
|
67246
|
-
This file is auto-generated from
|
|
67431
|
+
This file is auto-generated from \`.nax/context.md\`.
|
|
67247
67432
|
DO NOT EDIT MANUALLY \u2014 run \`nax generate\` to regenerate.
|
|
67248
67433
|
|
|
67249
67434
|
---
|
|
@@ -67262,7 +67447,7 @@ var codexGenerator = {
|
|
|
67262
67447
|
function generateCursorRules(context) {
|
|
67263
67448
|
const header = `# Project Rules
|
|
67264
67449
|
|
|
67265
|
-
Auto-generated from nax/context.md \u2014 run \`nax generate\` to regenerate.
|
|
67450
|
+
Auto-generated from .nax/context.md \u2014 run \`nax generate\` to regenerate.
|
|
67266
67451
|
DO NOT EDIT MANUALLY
|
|
67267
67452
|
|
|
67268
67453
|
---
|
|
@@ -67281,7 +67466,7 @@ var cursorGenerator = {
|
|
|
67281
67466
|
function generateGeminiConfig(context) {
|
|
67282
67467
|
const header = `# Gemini CLI Context
|
|
67283
67468
|
|
|
67284
|
-
This file is auto-generated from
|
|
67469
|
+
This file is auto-generated from \`.nax/context.md\`.
|
|
67285
67470
|
DO NOT EDIT MANUALLY \u2014 run \`nax generate\` to regenerate.
|
|
67286
67471
|
|
|
67287
67472
|
---
|
|
@@ -67300,7 +67485,7 @@ var geminiGenerator = {
|
|
|
67300
67485
|
function generateOpencodeConfig(context) {
|
|
67301
67486
|
const header = `# Agent Instructions
|
|
67302
67487
|
|
|
67303
|
-
This file is auto-generated from
|
|
67488
|
+
This file is auto-generated from \`.nax/context.md\`.
|
|
67304
67489
|
DO NOT EDIT MANUALLY \u2014 run \`nax generate\` to regenerate.
|
|
67305
67490
|
|
|
67306
67491
|
These instructions apply to all AI coding agents in this project.
|
|
@@ -67321,7 +67506,7 @@ var opencodeGenerator = {
|
|
|
67321
67506
|
function generateWindsurfRules(context) {
|
|
67322
67507
|
const header = `# Windsurf Project Rules
|
|
67323
67508
|
|
|
67324
|
-
Auto-generated from nax/context.md \u2014 run \`nax generate\` to regenerate.
|
|
67509
|
+
Auto-generated from .nax/context.md \u2014 run \`nax generate\` to regenerate.
|
|
67325
67510
|
DO NOT EDIT MANUALLY
|
|
67326
67511
|
|
|
67327
67512
|
---
|
|
@@ -67398,10 +67583,10 @@ async function generateAll(options, config2, agentFilter) {
|
|
|
67398
67583
|
async function discoverPackages(repoRoot) {
|
|
67399
67584
|
const packages = [];
|
|
67400
67585
|
const seen = new Set;
|
|
67401
|
-
for (const pattern of ["
|
|
67586
|
+
for (const pattern of [".nax/mono/*/context.md", ".nax/mono/*/*/context.md"]) {
|
|
67402
67587
|
const glob = new Bun.Glob(pattern);
|
|
67403
|
-
for await (const match of glob.scan(repoRoot)) {
|
|
67404
|
-
const pkgRelative = match.replace(
|
|
67588
|
+
for await (const match of glob.scan({ cwd: repoRoot, dot: true })) {
|
|
67589
|
+
const pkgRelative = match.replace(/^\.nax\/mono\//, "").replace(/\/context\.md$/, "");
|
|
67405
67590
|
const pkgAbsolute = join11(repoRoot, pkgRelative);
|
|
67406
67591
|
if (!seen.has(pkgAbsolute)) {
|
|
67407
67592
|
seen.add(pkgAbsolute);
|
|
@@ -67479,7 +67664,7 @@ async function discoverWorkspacePackages(repoRoot) {
|
|
|
67479
67664
|
return results.sort();
|
|
67480
67665
|
}
|
|
67481
67666
|
async function generateForPackage(packageDir, config2, dryRun = false) {
|
|
67482
|
-
const contextPath = join11(packageDir, "nax", "context.md");
|
|
67667
|
+
const contextPath = join11(packageDir, ".nax", "context.md");
|
|
67483
67668
|
if (!existsSync10(contextPath)) {
|
|
67484
67669
|
return [
|
|
67485
67670
|
{
|
|
@@ -67596,6 +67781,13 @@ function validateStory(raw, index, allIds) {
|
|
|
67596
67781
|
}
|
|
67597
67782
|
const rawTestStrategy = routing.testStrategy ?? s.testStrategy;
|
|
67598
67783
|
const testStrategy = resolveTestStrategy(typeof rawTestStrategy === "string" ? rawTestStrategy : undefined);
|
|
67784
|
+
const rawJustification = routing.noTestJustification ?? s.noTestJustification;
|
|
67785
|
+
if (testStrategy === "no-test") {
|
|
67786
|
+
if (!rawJustification || typeof rawJustification !== "string" || rawJustification.trim() === "") {
|
|
67787
|
+
throw new Error(`[schema] story[${index}].routing.noTestJustification is required when testStrategy is "no-test"`);
|
|
67788
|
+
}
|
|
67789
|
+
}
|
|
67790
|
+
const noTestJustification = typeof rawJustification === "string" && rawJustification.trim() !== "" ? rawJustification.trim() : undefined;
|
|
67599
67791
|
const rawDeps = s.dependencies;
|
|
67600
67792
|
const dependencies = Array.isArray(rawDeps) ? rawDeps : [];
|
|
67601
67793
|
for (const dep of dependencies) {
|
|
@@ -67635,7 +67827,8 @@ function validateStory(raw, index, allIds) {
|
|
|
67635
67827
|
routing: {
|
|
67636
67828
|
complexity,
|
|
67637
67829
|
testStrategy,
|
|
67638
|
-
reasoning: "validated from LLM output"
|
|
67830
|
+
reasoning: "validated from LLM output",
|
|
67831
|
+
...noTestJustification !== undefined ? { noTestJustification } : {}
|
|
67639
67832
|
},
|
|
67640
67833
|
...workdir !== undefined ? { workdir } : {},
|
|
67641
67834
|
...contextFiles.length > 0 ? { contextFiles } : {}
|
|
@@ -67702,9 +67895,9 @@ var _deps2 = {
|
|
|
67702
67895
|
createInteractionBridge: () => createCliInteractionBridge()
|
|
67703
67896
|
};
|
|
67704
67897
|
async function planCommand(workdir, config2, options) {
|
|
67705
|
-
const naxDir = join12(workdir, "nax");
|
|
67898
|
+
const naxDir = join12(workdir, ".nax");
|
|
67706
67899
|
if (!existsSync11(naxDir)) {
|
|
67707
|
-
throw new Error(
|
|
67900
|
+
throw new Error(`.nax directory not found. Run 'nax init' first in ${workdir}`);
|
|
67708
67901
|
}
|
|
67709
67902
|
const logger = getLogger();
|
|
67710
67903
|
logger?.info("plan", "Reading spec", { from: options.from });
|
|
@@ -68000,7 +68193,8 @@ Generate a JSON object with this exact structure (no markdown, no explanation \u
|
|
|
68000
68193
|
"passes": false,
|
|
68001
68194
|
"routing": {
|
|
68002
68195
|
"complexity": "simple | medium | complex | expert",
|
|
68003
|
-
"testStrategy": "tdd-simple | three-session-tdd-lite | three-session-tdd | test-after",
|
|
68196
|
+
"testStrategy": "no-test | tdd-simple | three-session-tdd-lite | three-session-tdd | test-after",
|
|
68197
|
+
"noTestJustification": "string \u2014 REQUIRED when testStrategy is no-test, explains why tests are unnecessary",
|
|
68004
68198
|
"reasoning": "string \u2014 brief classification rationale"
|
|
68005
68199
|
},
|
|
68006
68200
|
"escalations": [],
|
|
@@ -68038,7 +68232,7 @@ async function acceptCommand(options) {
|
|
|
68038
68232
|
logger.error("cli", "Invalid project directory", { error: err.message });
|
|
68039
68233
|
throw new NaxError("Invalid project directory", "INVALID_DIRECTORY", { error: err.message });
|
|
68040
68234
|
}
|
|
68041
|
-
const featureDir = path.join(projectDir, "nax", "features", feature);
|
|
68235
|
+
const featureDir = path.join(projectDir, ".nax", "features", feature);
|
|
68042
68236
|
const prdPath = path.join(featureDir, "prd.json");
|
|
68043
68237
|
const prdFile = Bun.file(prdPath);
|
|
68044
68238
|
if (!await prdFile.exists()) {
|
|
@@ -68174,29 +68368,29 @@ function resolveProject(options = {}) {
|
|
|
68174
68368
|
let configPath;
|
|
68175
68369
|
if (dir) {
|
|
68176
68370
|
projectRoot = realpathSync3(resolve6(dir));
|
|
68177
|
-
naxDir = join13(projectRoot, "nax");
|
|
68371
|
+
naxDir = join13(projectRoot, ".nax");
|
|
68178
68372
|
if (!existsSync12(naxDir)) {
|
|
68179
68373
|
throw new NaxError(`Directory does not contain a nax project: ${projectRoot}
|
|
68180
68374
|
Expected to find: ${naxDir}`, "NAX_DIR_NOT_FOUND", { projectRoot, naxDir });
|
|
68181
68375
|
}
|
|
68182
68376
|
configPath = join13(naxDir, "config.json");
|
|
68183
68377
|
if (!existsSync12(configPath)) {
|
|
68184
|
-
throw new NaxError(
|
|
68378
|
+
throw new NaxError(`.nax directory found but config.json is missing: ${naxDir}
|
|
68185
68379
|
Expected to find: ${configPath}`, "CONFIG_NOT_FOUND", { naxDir, configPath });
|
|
68186
68380
|
}
|
|
68187
68381
|
} else {
|
|
68188
68382
|
const found = findProjectRoot(process.cwd());
|
|
68189
68383
|
if (!found) {
|
|
68190
|
-
const cwdNaxDir = join13(process.cwd(), "nax");
|
|
68384
|
+
const cwdNaxDir = join13(process.cwd(), ".nax");
|
|
68191
68385
|
if (existsSync12(cwdNaxDir)) {
|
|
68192
68386
|
const cwdConfigPath = join13(cwdNaxDir, "config.json");
|
|
68193
|
-
throw new NaxError(
|
|
68387
|
+
throw new NaxError(`.nax directory found but config.json is missing: ${cwdNaxDir}
|
|
68194
68388
|
Expected to find: ${cwdConfigPath}`, "CONFIG_NOT_FOUND", { naxDir: cwdNaxDir, configPath: cwdConfigPath });
|
|
68195
68389
|
}
|
|
68196
68390
|
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() });
|
|
68197
68391
|
}
|
|
68198
68392
|
projectRoot = found;
|
|
68199
|
-
naxDir = join13(projectRoot, "nax");
|
|
68393
|
+
naxDir = join13(projectRoot, ".nax");
|
|
68200
68394
|
configPath = join13(naxDir, "config.json");
|
|
68201
68395
|
}
|
|
68202
68396
|
let featureDir;
|
|
@@ -68229,7 +68423,7 @@ function findProjectRoot(startDir) {
|
|
|
68229
68423
|
let current = resolve6(startDir);
|
|
68230
68424
|
let depth = 0;
|
|
68231
68425
|
while (depth < MAX_DIRECTORY_DEPTH) {
|
|
68232
|
-
const naxDir = join13(current, "nax");
|
|
68426
|
+
const naxDir = join13(current, ".nax");
|
|
68233
68427
|
const configPath = join13(naxDir, "config.json");
|
|
68234
68428
|
if (existsSync12(configPath)) {
|
|
68235
68429
|
return realpathSync3(current);
|
|
@@ -68268,7 +68462,7 @@ async function loadStatusFile(featureDir) {
|
|
|
68268
68462
|
}
|
|
68269
68463
|
}
|
|
68270
68464
|
async function loadProjectStatusFile(projectDir) {
|
|
68271
|
-
const statusPath = join15(projectDir, "nax", "status.json");
|
|
68465
|
+
const statusPath = join15(projectDir, ".nax", "status.json");
|
|
68272
68466
|
if (!existsSync13(statusPath)) {
|
|
68273
68467
|
return null;
|
|
68274
68468
|
}
|
|
@@ -68334,7 +68528,7 @@ async function getFeatureSummary(featureName, featureDir) {
|
|
|
68334
68528
|
return summary;
|
|
68335
68529
|
}
|
|
68336
68530
|
async function displayAllFeatures(projectDir) {
|
|
68337
|
-
const featuresDir = join15(projectDir, "nax", "features");
|
|
68531
|
+
const featuresDir = join15(projectDir, ".nax", "features");
|
|
68338
68532
|
if (!existsSync13(featuresDir)) {
|
|
68339
68533
|
console.log(source_default.dim("No features found."));
|
|
68340
68534
|
return;
|
|
@@ -68539,7 +68733,7 @@ async function parseRunLog(logPath) {
|
|
|
68539
68733
|
async function runsListCommand(options) {
|
|
68540
68734
|
const logger = getLogger();
|
|
68541
68735
|
const { feature, workdir } = options;
|
|
68542
|
-
const runsDir = join16(workdir, "nax", "features", feature, "runs");
|
|
68736
|
+
const runsDir = join16(workdir, ".nax", "features", feature, "runs");
|
|
68543
68737
|
if (!existsSync14(runsDir)) {
|
|
68544
68738
|
logger.info("cli", "No runs found for feature", { feature, hint: `Directory not found: ${runsDir}` });
|
|
68545
68739
|
return;
|
|
@@ -68577,7 +68771,7 @@ async function runsListCommand(options) {
|
|
|
68577
68771
|
async function runsShowCommand(options) {
|
|
68578
68772
|
const logger = getLogger();
|
|
68579
68773
|
const { runId, feature, workdir } = options;
|
|
68580
|
-
const logPath = join16(workdir, "nax", "features", feature, "runs", `${runId}.jsonl`);
|
|
68774
|
+
const logPath = join16(workdir, ".nax", "features", feature, "runs", `${runId}.jsonl`);
|
|
68581
68775
|
if (!existsSync14(logPath)) {
|
|
68582
68776
|
logger.error("cli", "Run not found", { runId, feature, logPath });
|
|
68583
68777
|
throw new NaxError("Run not found", "RUN_NOT_FOUND", { runId, feature, logPath });
|
|
@@ -68740,9 +68934,9 @@ ${ctx.contextMarkdown}`;
|
|
|
68740
68934
|
async function promptsCommand(options) {
|
|
68741
68935
|
const logger = getLogger();
|
|
68742
68936
|
const { feature, workdir, config: config2, storyId, outputDir } = options;
|
|
68743
|
-
const naxDir = join29(workdir, "nax");
|
|
68937
|
+
const naxDir = join29(workdir, ".nax");
|
|
68744
68938
|
if (!existsSync18(naxDir)) {
|
|
68745
|
-
throw new Error(
|
|
68939
|
+
throw new Error(`.nax directory not found. Run 'nax init' first in ${workdir}`);
|
|
68746
68940
|
}
|
|
68747
68941
|
const featureDir = join29(naxDir, "features", feature);
|
|
68748
68942
|
const prdPath = join29(featureDir, "prd.json");
|
|
@@ -68854,13 +69048,13 @@ var TEMPLATE_HEADER = `<!--
|
|
|
68854
69048
|
- Conventions (project coding standards)
|
|
68855
69049
|
|
|
68856
69050
|
To activate overrides, add to your nax/config.json:
|
|
68857
|
-
{ "prompts": { "overrides": { "<role>": "nax/templates/<role>.md" } } }
|
|
69051
|
+
{ "prompts": { "overrides": { "<role>": ".nax/templates/<role>.md" } } }
|
|
68858
69052
|
-->
|
|
68859
69053
|
|
|
68860
69054
|
`;
|
|
68861
69055
|
async function promptsInitCommand(options) {
|
|
68862
69056
|
const { workdir, force = false, autoWireConfig = true } = options;
|
|
68863
|
-
const templatesDir = join30(workdir, "nax", "templates");
|
|
69057
|
+
const templatesDir = join30(workdir, ".nax", "templates");
|
|
68864
69058
|
mkdirSync4(templatesDir, { recursive: true });
|
|
68865
69059
|
const existingFiles = TEMPLATE_ROLES.map((t) => t.file).filter((f) => existsSync19(join30(templatesDir, f)));
|
|
68866
69060
|
if (existingFiles.length > 0 && !force) {
|
|
@@ -68891,11 +69085,11 @@ async function autoWirePromptsConfig(workdir) {
|
|
|
68891
69085
|
const exampleConfig = JSON.stringify({
|
|
68892
69086
|
prompts: {
|
|
68893
69087
|
overrides: {
|
|
68894
|
-
"test-writer": "nax/templates/test-writer.md",
|
|
68895
|
-
implementer: "nax/templates/implementer.md",
|
|
68896
|
-
verifier: "nax/templates/verifier.md",
|
|
68897
|
-
"single-session": "nax/templates/single-session.md",
|
|
68898
|
-
"tdd-simple": "nax/templates/tdd-simple.md"
|
|
69088
|
+
"test-writer": ".nax/templates/test-writer.md",
|
|
69089
|
+
implementer: ".nax/templates/implementer.md",
|
|
69090
|
+
verifier: ".nax/templates/verifier.md",
|
|
69091
|
+
"single-session": ".nax/templates/single-session.md",
|
|
69092
|
+
"tdd-simple": ".nax/templates/tdd-simple.md"
|
|
68899
69093
|
}
|
|
68900
69094
|
}
|
|
68901
69095
|
}, null, 2);
|
|
@@ -68913,11 +69107,11 @@ ${exampleConfig}`);
|
|
|
68913
69107
|
return;
|
|
68914
69108
|
}
|
|
68915
69109
|
const overrides = {
|
|
68916
|
-
"test-writer": "nax/templates/test-writer.md",
|
|
68917
|
-
implementer: "nax/templates/implementer.md",
|
|
68918
|
-
verifier: "nax/templates/verifier.md",
|
|
68919
|
-
"single-session": "nax/templates/single-session.md",
|
|
68920
|
-
"tdd-simple": "nax/templates/tdd-simple.md"
|
|
69110
|
+
"test-writer": ".nax/templates/test-writer.md",
|
|
69111
|
+
implementer: ".nax/templates/implementer.md",
|
|
69112
|
+
verifier: ".nax/templates/verifier.md",
|
|
69113
|
+
"single-session": ".nax/templates/single-session.md",
|
|
69114
|
+
"tdd-simple": ".nax/templates/tdd-simple.md"
|
|
68921
69115
|
};
|
|
68922
69116
|
if (!config2.prompts) {
|
|
68923
69117
|
config2.prompts = {};
|
|
@@ -68989,7 +69183,7 @@ import * as os2 from "os";
|
|
|
68989
69183
|
import * as path13 from "path";
|
|
68990
69184
|
async function pluginsListCommand(config2, workdir, overrideGlobalPluginsDir) {
|
|
68991
69185
|
const globalPluginsDir = overrideGlobalPluginsDir ?? path13.join(os2.homedir(), ".nax", "plugins");
|
|
68992
|
-
const projectPluginsDir = path13.join(workdir, "nax", "plugins");
|
|
69186
|
+
const projectPluginsDir = path13.join(workdir, ".nax", "plugins");
|
|
68993
69187
|
const configPlugins = config2.plugins || [];
|
|
68994
69188
|
const registry2 = await loadPlugins(globalPluginsDir, projectPluginsDir, configPlugins, workdir, config2.disabledPlugins);
|
|
68995
69189
|
const plugins = registry2.plugins;
|
|
@@ -68998,8 +69192,8 @@ async function pluginsListCommand(config2, workdir, overrideGlobalPluginsDir) {
|
|
|
68998
69192
|
console.log(`
|
|
68999
69193
|
To install plugins:`);
|
|
69000
69194
|
console.log(" \u2022 Add to global directory: ~/.nax/plugins/");
|
|
69001
|
-
console.log(" \u2022 Add to project directory:
|
|
69002
|
-
console.log(" \u2022 Configure in nax/config.json");
|
|
69195
|
+
console.log(" \u2022 Add to project directory: ./.nax/plugins/");
|
|
69196
|
+
console.log(" \u2022 Configure in .nax/config.json");
|
|
69003
69197
|
console.log(`
|
|
69004
69198
|
See https://github.com/nax/nax#plugins for more details.`);
|
|
69005
69199
|
return;
|
|
@@ -69250,7 +69444,7 @@ function isProcessAlive2(pid) {
|
|
|
69250
69444
|
}
|
|
69251
69445
|
}
|
|
69252
69446
|
async function loadStatusFile2(workdir) {
|
|
69253
|
-
const statusPath = join34(workdir, "nax", "status.json");
|
|
69447
|
+
const statusPath = join34(workdir, ".nax", "status.json");
|
|
69254
69448
|
if (!existsSync21(statusPath))
|
|
69255
69449
|
return null;
|
|
69256
69450
|
try {
|
|
@@ -69297,7 +69491,7 @@ async function diagnoseCommand(options = {}) {
|
|
|
69297
69491
|
const workdir = options.workdir ?? process.cwd();
|
|
69298
69492
|
const naxSubdir = findProjectDir(workdir);
|
|
69299
69493
|
let projectDir = naxSubdir ? join34(naxSubdir, "..") : null;
|
|
69300
|
-
if (!projectDir && existsSync21(join34(workdir, "nax"))) {
|
|
69494
|
+
if (!projectDir && existsSync21(join34(workdir, ".nax"))) {
|
|
69301
69495
|
projectDir = workdir;
|
|
69302
69496
|
}
|
|
69303
69497
|
if (!projectDir)
|
|
@@ -69308,7 +69502,7 @@ async function diagnoseCommand(options = {}) {
|
|
|
69308
69502
|
if (status2) {
|
|
69309
69503
|
feature = status2.run.feature;
|
|
69310
69504
|
} else {
|
|
69311
|
-
const featuresDir = join34(projectDir, "nax", "features");
|
|
69505
|
+
const featuresDir = join34(projectDir, ".nax", "features");
|
|
69312
69506
|
if (!existsSync21(featuresDir))
|
|
69313
69507
|
throw new Error("No features found in project");
|
|
69314
69508
|
const features = readdirSync5(featuresDir, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => e.name);
|
|
@@ -69318,7 +69512,7 @@ async function diagnoseCommand(options = {}) {
|
|
|
69318
69512
|
logger.info("diagnose", "No feature specified, using first found", { feature });
|
|
69319
69513
|
}
|
|
69320
69514
|
}
|
|
69321
|
-
const featureDir = join34(projectDir, "nax", "features", feature);
|
|
69515
|
+
const featureDir = join34(projectDir, ".nax", "features", feature);
|
|
69322
69516
|
const prdPath = join34(featureDir, "prd.json");
|
|
69323
69517
|
if (!existsSync21(prdPath))
|
|
69324
69518
|
throw new Error(`Feature not found: ${feature}`);
|
|
@@ -69377,10 +69571,10 @@ async function generateCommand(options) {
|
|
|
69377
69571
|
if (dryRun) {
|
|
69378
69572
|
console.log(source_default.yellow("\u26A0 Dry run \u2014 no files will be written"));
|
|
69379
69573
|
}
|
|
69380
|
-
console.log(source_default.blue("\u2192 Discovering packages with nax/context.md..."));
|
|
69574
|
+
console.log(source_default.blue("\u2192 Discovering packages with .nax/mono/*/context.md..."));
|
|
69381
69575
|
const packages = await discoverPackages(workdir);
|
|
69382
69576
|
if (packages.length === 0) {
|
|
69383
|
-
console.log(source_default.yellow(" No packages found (no
|
|
69577
|
+
console.log(source_default.yellow(" No packages found (no .nax/mono/*/context.md or .nax/mono/*/*/context.md)"));
|
|
69384
69578
|
return;
|
|
69385
69579
|
}
|
|
69386
69580
|
console.log(source_default.blue(`\u2192 Generating agent files for ${packages.length} package(s)...`));
|
|
@@ -69425,12 +69619,12 @@ async function generateCommand(options) {
|
|
|
69425
69619
|
process.exit(1);
|
|
69426
69620
|
return;
|
|
69427
69621
|
}
|
|
69428
|
-
const contextPath = options.context ? join35(workdir, options.context) : join35(workdir, "nax/context.md");
|
|
69622
|
+
const contextPath = options.context ? join35(workdir, options.context) : join35(workdir, ".nax/context.md");
|
|
69429
69623
|
const outputDir = options.output ? join35(workdir, options.output) : workdir;
|
|
69430
69624
|
const autoInject = !options.noAutoInject;
|
|
69431
69625
|
if (!existsSync22(contextPath)) {
|
|
69432
69626
|
console.error(source_default.red(`\u2717 Context file not found: ${contextPath}`));
|
|
69433
|
-
console.error(source_default.yellow(" Create nax/context.md first, or run `nax init` to scaffold it."));
|
|
69627
|
+
console.error(source_default.yellow(" Create .nax/context.md first, or run `nax init` to scaffold it."));
|
|
69434
69628
|
process.exit(1);
|
|
69435
69629
|
}
|
|
69436
69630
|
if (options.agent && !VALID_AGENTS.includes(options.agent)) {
|
|
@@ -69496,7 +69690,7 @@ async function generateCommand(options) {
|
|
|
69496
69690
|
const packages = await discoverPackages(workdir);
|
|
69497
69691
|
if (packages.length > 0) {
|
|
69498
69692
|
console.log(source_default.blue(`
|
|
69499
|
-
\u2192 Discovered ${packages.length} package(s) with
|
|
69693
|
+
\u2192 Discovered ${packages.length} package(s) with context.md \u2014 generating agent files...`));
|
|
69500
69694
|
let pkgErrorCount = 0;
|
|
69501
69695
|
for (const pkgDir of packages) {
|
|
69502
69696
|
const pkgResults = await generateForPackage(pkgDir, config2, dryRun);
|
|
@@ -70272,7 +70466,7 @@ async function logsCommand(options) {
|
|
|
70272
70466
|
return;
|
|
70273
70467
|
}
|
|
70274
70468
|
const resolved = resolveProject({ dir: options.dir });
|
|
70275
|
-
const naxDir = join40(resolved.projectDir, "nax");
|
|
70469
|
+
const naxDir = join40(resolved.projectDir, ".nax");
|
|
70276
70470
|
const configPath = resolved.configPath;
|
|
70277
70471
|
const configFile = Bun.file(configPath);
|
|
70278
70472
|
const config2 = await configFile.json();
|
|
@@ -70322,7 +70516,7 @@ async function precheckCommand(options) {
|
|
|
70322
70516
|
process.exit(1);
|
|
70323
70517
|
}
|
|
70324
70518
|
}
|
|
70325
|
-
const naxDir = join41(resolved.projectDir, "nax");
|
|
70519
|
+
const naxDir = join41(resolved.projectDir, ".nax");
|
|
70326
70520
|
const featureDir = join41(naxDir, "features", featureName);
|
|
70327
70521
|
const prdPath = join41(featureDir, "prd.json");
|
|
70328
70522
|
if (!existsSync31(featureDir)) {
|
|
@@ -70638,7 +70832,14 @@ function precomputeBatchPlan(stories, maxBatchSize = DEFAULT_MAX_BATCH_SIZE) {
|
|
|
70638
70832
|
let currentBatch = [];
|
|
70639
70833
|
for (const story of stories) {
|
|
70640
70834
|
const isSimple = story.routing?.complexity === "simple" && story.routing?.testStrategy === "test-after";
|
|
70641
|
-
|
|
70835
|
+
const isNoTest = story.routing?.testStrategy === "no-test";
|
|
70836
|
+
const isBatchable = isSimple || isNoTest;
|
|
70837
|
+
if (isBatchable && currentBatch.length < maxBatchSize) {
|
|
70838
|
+
const batchIsNoTest = currentBatch.length > 0 && currentBatch[0]?.routing?.testStrategy === "no-test";
|
|
70839
|
+
if (currentBatch.length > 0 && batchIsNoTest !== isNoTest) {
|
|
70840
|
+
batches.push({ stories: [...currentBatch], isBatch: currentBatch.length > 1 });
|
|
70841
|
+
currentBatch = [];
|
|
70842
|
+
}
|
|
70642
70843
|
currentBatch.push(story);
|
|
70643
70844
|
} else {
|
|
70644
70845
|
if (currentBatch.length > 0) {
|
|
@@ -70648,11 +70849,8 @@ function precomputeBatchPlan(stories, maxBatchSize = DEFAULT_MAX_BATCH_SIZE) {
|
|
|
70648
70849
|
});
|
|
70649
70850
|
currentBatch = [];
|
|
70650
70851
|
}
|
|
70651
|
-
if (!
|
|
70652
|
-
batches.push({
|
|
70653
|
-
stories: [story],
|
|
70654
|
-
isBatch: false
|
|
70655
|
-
});
|
|
70852
|
+
if (!isBatchable) {
|
|
70853
|
+
batches.push({ stories: [story], isBatch: false });
|
|
70656
70854
|
} else {
|
|
70657
70855
|
currentBatch.push(story);
|
|
70658
70856
|
}
|
|
@@ -78331,15 +78529,15 @@ Next: nax generate --package ${options.package}`));
|
|
|
78331
78529
|
}
|
|
78332
78530
|
return;
|
|
78333
78531
|
}
|
|
78334
|
-
const naxDir =
|
|
78532
|
+
const naxDir = join53(workdir, "nax");
|
|
78335
78533
|
if (existsSync34(naxDir) && !options.force) {
|
|
78336
78534
|
console.log(source_default.yellow("nax already initialized. Use --force to overwrite."));
|
|
78337
78535
|
return;
|
|
78338
78536
|
}
|
|
78339
|
-
mkdirSync6(
|
|
78340
|
-
mkdirSync6(
|
|
78341
|
-
await Bun.write(
|
|
78342
|
-
await Bun.write(
|
|
78537
|
+
mkdirSync6(join53(naxDir, "features"), { recursive: true });
|
|
78538
|
+
mkdirSync6(join53(naxDir, "hooks"), { recursive: true });
|
|
78539
|
+
await Bun.write(join53(naxDir, "config.json"), JSON.stringify(DEFAULT_CONFIG, null, 2));
|
|
78540
|
+
await Bun.write(join53(naxDir, "hooks.json"), JSON.stringify({
|
|
78343
78541
|
hooks: {
|
|
78344
78542
|
"on-start": { command: 'echo "nax started: $NAX_FEATURE"', enabled: false },
|
|
78345
78543
|
"on-complete": { command: 'echo "nax complete: $NAX_FEATURE"', enabled: false },
|
|
@@ -78347,12 +78545,12 @@ Next: nax generate --package ${options.package}`));
|
|
|
78347
78545
|
"on-error": { command: 'echo "nax error: $NAX_REASON"', enabled: false }
|
|
78348
78546
|
}
|
|
78349
78547
|
}, null, 2));
|
|
78350
|
-
await Bun.write(
|
|
78548
|
+
await Bun.write(join53(naxDir, ".gitignore"), `# nax temp files
|
|
78351
78549
|
*.tmp
|
|
78352
78550
|
.paused.json
|
|
78353
78551
|
.nax-verifier-verdict.json
|
|
78354
78552
|
`);
|
|
78355
|
-
await Bun.write(
|
|
78553
|
+
await Bun.write(join53(naxDir, "context.md"), `# Project Context
|
|
78356
78554
|
|
|
78357
78555
|
This document defines coding standards, architectural decisions, and forbidden patterns for this project.
|
|
78358
78556
|
Run \`nax generate\` to regenerate agent config files (CLAUDE.md, AGENTS.md, .cursorrules, etc.) from this file.
|
|
@@ -78478,8 +78676,8 @@ program2.command("run").description("Run the orchestration loop for a feature").
|
|
|
78478
78676
|
console.error(source_default.red("nax not initialized. Run: nax init"));
|
|
78479
78677
|
process.exit(1);
|
|
78480
78678
|
}
|
|
78481
|
-
const featureDir =
|
|
78482
|
-
const prdPath =
|
|
78679
|
+
const featureDir = join53(naxDir, "features", options.feature);
|
|
78680
|
+
const prdPath = join53(featureDir, "prd.json");
|
|
78483
78681
|
if (options.plan && options.from) {
|
|
78484
78682
|
if (existsSync34(prdPath) && !options.force) {
|
|
78485
78683
|
console.error(source_default.red(`Error: prd.json already exists for feature "${options.feature}".`));
|
|
@@ -78501,10 +78699,10 @@ program2.command("run").description("Run the orchestration loop for a feature").
|
|
|
78501
78699
|
}
|
|
78502
78700
|
}
|
|
78503
78701
|
try {
|
|
78504
|
-
const planLogDir =
|
|
78702
|
+
const planLogDir = join53(featureDir, "plan");
|
|
78505
78703
|
mkdirSync6(planLogDir, { recursive: true });
|
|
78506
78704
|
const planLogId = new Date().toISOString().replace(/:/g, "-").replace(/\..+/, "");
|
|
78507
|
-
const planLogPath =
|
|
78705
|
+
const planLogPath = join53(planLogDir, `${planLogId}.jsonl`);
|
|
78508
78706
|
initLogger({ level: "info", filePath: planLogPath, useChalk: false, headless: true });
|
|
78509
78707
|
console.log(source_default.dim(` [Plan log: ${planLogPath}]`));
|
|
78510
78708
|
console.log(source_default.dim(" [Planning phase: generating PRD from spec]"));
|
|
@@ -78542,10 +78740,10 @@ program2.command("run").description("Run the orchestration loop for a feature").
|
|
|
78542
78740
|
process.exit(1);
|
|
78543
78741
|
}
|
|
78544
78742
|
resetLogger();
|
|
78545
|
-
const runsDir =
|
|
78743
|
+
const runsDir = join53(featureDir, "runs");
|
|
78546
78744
|
mkdirSync6(runsDir, { recursive: true });
|
|
78547
78745
|
const runId = new Date().toISOString().replace(/:/g, "-").replace(/\..+/, "");
|
|
78548
|
-
const logFilePath =
|
|
78746
|
+
const logFilePath = join53(runsDir, `${runId}.jsonl`);
|
|
78549
78747
|
const isTTY = process.stdout.isTTY ?? false;
|
|
78550
78748
|
const headlessFlag = options.headless ?? false;
|
|
78551
78749
|
const headlessEnv = process.env.NAX_HEADLESS === "1";
|
|
@@ -78561,7 +78759,7 @@ program2.command("run").description("Run the orchestration loop for a feature").
|
|
|
78561
78759
|
config2.autoMode.defaultAgent = options.agent;
|
|
78562
78760
|
}
|
|
78563
78761
|
config2.execution.maxIterations = Number.parseInt(options.maxIterations, 10);
|
|
78564
|
-
const globalNaxDir =
|
|
78762
|
+
const globalNaxDir = join53(homedir10(), ".nax");
|
|
78565
78763
|
const hooks = await loadHooksConfig(naxDir, globalNaxDir);
|
|
78566
78764
|
const eventEmitter = new PipelineEventEmitter;
|
|
78567
78765
|
let tuiInstance;
|
|
@@ -78584,7 +78782,7 @@ program2.command("run").description("Run the orchestration loop for a feature").
|
|
|
78584
78782
|
} else {
|
|
78585
78783
|
console.log(source_default.dim(" [Headless mode \u2014 pipe output]"));
|
|
78586
78784
|
}
|
|
78587
|
-
const statusFilePath =
|
|
78785
|
+
const statusFilePath = join53(workdir, "nax", "status.json");
|
|
78588
78786
|
let parallel;
|
|
78589
78787
|
if (options.parallel !== undefined) {
|
|
78590
78788
|
parallel = Number.parseInt(options.parallel, 10);
|
|
@@ -78610,7 +78808,7 @@ program2.command("run").description("Run the orchestration loop for a feature").
|
|
|
78610
78808
|
headless: useHeadless,
|
|
78611
78809
|
skipPrecheck: options.skipPrecheck ?? false
|
|
78612
78810
|
});
|
|
78613
|
-
const latestSymlink =
|
|
78811
|
+
const latestSymlink = join53(runsDir, "latest.jsonl");
|
|
78614
78812
|
try {
|
|
78615
78813
|
if (existsSync34(latestSymlink)) {
|
|
78616
78814
|
Bun.spawnSync(["rm", latestSymlink]);
|
|
@@ -78648,34 +78846,42 @@ features.command("create <name>").description("Create a new feature").option("-d
|
|
|
78648
78846
|
console.error(source_default.red("nax not initialized. Run: nax init"));
|
|
78649
78847
|
process.exit(1);
|
|
78650
78848
|
}
|
|
78651
|
-
const featureDir =
|
|
78849
|
+
const featureDir = join53(naxDir, "features", name);
|
|
78652
78850
|
mkdirSync6(featureDir, { recursive: true });
|
|
78653
|
-
await Bun.write(
|
|
78851
|
+
await Bun.write(join53(featureDir, "spec.md"), `# Feature: ${name}
|
|
78654
78852
|
|
|
78655
78853
|
## Overview
|
|
78656
78854
|
|
|
78657
|
-
|
|
78855
|
+
<!-- One paragraph describing what this feature does and why it's needed. -->
|
|
78658
78856
|
|
|
78659
|
-
##
|
|
78660
|
-
`);
|
|
78661
|
-
await Bun.write(join52(featureDir, "plan.md"), `# Plan: ${name}
|
|
78857
|
+
## Background / Context
|
|
78662
78858
|
|
|
78663
|
-
|
|
78859
|
+
<!-- Optional: relevant background, existing behaviour, or constraints. -->
|
|
78664
78860
|
|
|
78665
|
-
##
|
|
78861
|
+
## User Stories
|
|
78666
78862
|
|
|
78667
|
-
|
|
78668
|
-
|
|
78669
|
-
await Bun.write(join52(featureDir, "tasks.md"), `# Tasks: ${name}
|
|
78863
|
+
<!-- Describe what users need. Each story becomes a unit of work for nax.
|
|
78864
|
+
Be specific \u2014 the more detail here, the better the generated plan. -->
|
|
78670
78865
|
|
|
78671
|
-
|
|
78866
|
+
- As a [user], I want to [goal] so that [benefit].
|
|
78672
78867
|
|
|
78673
|
-
|
|
78868
|
+
## Technical Requirements
|
|
78674
78869
|
|
|
78675
|
-
|
|
78676
|
-
|
|
78870
|
+
<!-- Optional: specific technical constraints, patterns to follow, APIs to use, etc. -->
|
|
78871
|
+
|
|
78872
|
+
## Acceptance Criteria
|
|
78873
|
+
|
|
78874
|
+
<!-- These are parsed by nax to generate acceptance tests.
|
|
78875
|
+
Use clear, testable statements. Each criterion = one AC test. -->
|
|
78876
|
+
|
|
78877
|
+
- [ ] [Describe observable outcome 1]
|
|
78878
|
+
- [ ] [Describe observable outcome 2]
|
|
78879
|
+
|
|
78880
|
+
## Out of Scope
|
|
78881
|
+
|
|
78882
|
+
<!-- What this feature explicitly does NOT cover. -->
|
|
78677
78883
|
`);
|
|
78678
|
-
await Bun.write(
|
|
78884
|
+
await Bun.write(join53(featureDir, "progress.txt"), `# Progress: ${name}
|
|
78679
78885
|
|
|
78680
78886
|
Created: ${new Date().toISOString()}
|
|
78681
78887
|
|
|
@@ -78684,11 +78890,9 @@ Created: ${new Date().toISOString()}
|
|
|
78684
78890
|
console.log(source_default.green(`\u2705 Created feature: ${name}`));
|
|
78685
78891
|
console.log(source_default.dim(` ${featureDir}/`));
|
|
78686
78892
|
console.log(source_default.dim(" \u251C\u2500\u2500 spec.md"));
|
|
78687
|
-
console.log(source_default.dim(" \u251C\u2500\u2500 plan.md"));
|
|
78688
|
-
console.log(source_default.dim(" \u251C\u2500\u2500 tasks.md"));
|
|
78689
78893
|
console.log(source_default.dim(" \u2514\u2500\u2500 progress.txt"));
|
|
78690
78894
|
console.log(source_default.dim(`
|
|
78691
|
-
Next: Edit spec.md
|
|
78895
|
+
Next: Edit spec.md, then: nax plan -f ${name} --from spec.md --auto`));
|
|
78692
78896
|
});
|
|
78693
78897
|
features.command("list").description("List all features").option("-d, --dir <path>", "Project directory", process.cwd()).action(async (options) => {
|
|
78694
78898
|
let workdir;
|
|
@@ -78703,7 +78907,7 @@ features.command("list").description("List all features").option("-d, --dir <pat
|
|
|
78703
78907
|
console.error(source_default.red("nax not initialized."));
|
|
78704
78908
|
process.exit(1);
|
|
78705
78909
|
}
|
|
78706
|
-
const featuresDir =
|
|
78910
|
+
const featuresDir = join53(naxDir, "features");
|
|
78707
78911
|
if (!existsSync34(featuresDir)) {
|
|
78708
78912
|
console.log(source_default.dim("No features yet."));
|
|
78709
78913
|
return;
|
|
@@ -78718,7 +78922,7 @@ features.command("list").description("List all features").option("-d, --dir <pat
|
|
|
78718
78922
|
Features:
|
|
78719
78923
|
`));
|
|
78720
78924
|
for (const name of entries) {
|
|
78721
|
-
const prdPath =
|
|
78925
|
+
const prdPath = join53(featuresDir, name, "prd.json");
|
|
78722
78926
|
if (existsSync34(prdPath)) {
|
|
78723
78927
|
const prd = await loadPRD(prdPath);
|
|
78724
78928
|
const c = countStories(prd);
|
|
@@ -78749,10 +78953,10 @@ Use: nax plan -f <feature> --from <spec>`));
|
|
|
78749
78953
|
process.exit(1);
|
|
78750
78954
|
}
|
|
78751
78955
|
const config2 = await loadConfig(workdir);
|
|
78752
|
-
const featureLogDir =
|
|
78956
|
+
const featureLogDir = join53(naxDir, "features", options.feature, "plan");
|
|
78753
78957
|
mkdirSync6(featureLogDir, { recursive: true });
|
|
78754
78958
|
const planLogId = new Date().toISOString().replace(/:/g, "-").replace(/\..+/, "");
|
|
78755
|
-
const planLogPath =
|
|
78959
|
+
const planLogPath = join53(featureLogDir, `${planLogId}.jsonl`);
|
|
78756
78960
|
initLogger({ level: "info", filePath: planLogPath, useChalk: false, headless: true });
|
|
78757
78961
|
console.log(source_default.dim(` [Plan log: ${planLogPath}]`));
|
|
78758
78962
|
try {
|
|
@@ -78789,7 +78993,7 @@ program2.command("analyze").description("(deprecated) Parse spec.md into prd.jso
|
|
|
78789
78993
|
console.error(source_default.red("nax not initialized. Run: nax init"));
|
|
78790
78994
|
process.exit(1);
|
|
78791
78995
|
}
|
|
78792
|
-
const featureDir =
|
|
78996
|
+
const featureDir = join53(naxDir, "features", options.feature);
|
|
78793
78997
|
if (!existsSync34(featureDir)) {
|
|
78794
78998
|
console.error(source_default.red(`Feature "${options.feature}" not found.`));
|
|
78795
78999
|
process.exit(1);
|
|
@@ -78805,7 +79009,7 @@ program2.command("analyze").description("(deprecated) Parse spec.md into prd.jso
|
|
|
78805
79009
|
specPath: options.from,
|
|
78806
79010
|
reclassify: options.reclassify
|
|
78807
79011
|
});
|
|
78808
|
-
const prdPath =
|
|
79012
|
+
const prdPath = join53(featureDir, "prd.json");
|
|
78809
79013
|
await Bun.write(prdPath, JSON.stringify(prd, null, 2));
|
|
78810
79014
|
const c = countStories(prd);
|
|
78811
79015
|
console.log(source_default.green(`
|