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