@nathapp/nax 0.18.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.gitlab-ci.yml +96 -0
- package/BRIEF.md +140 -0
- package/CHANGELOG.md +60 -0
- package/CLAUDE.md +159 -0
- package/README.md +373 -0
- package/US-007-IMPLEMENTATION.md +139 -0
- package/bin/nax.ts +930 -0
- package/biome.json +14 -0
- package/bun.lock +168 -0
- package/bunfig.toml +11 -0
- package/docs/20260216-fix-plan-context-review.md +56 -0
- package/docs/20260216-relentless-vs-ngent-comparison.md +208 -0
- package/docs/20260216-v02-plan.md +136 -0
- package/docs/20260216-v02-review.md +685 -0
- package/docs/20260217-dogfood-findings.md +56 -0
- package/docs/20260217-p2-plus-plan.md +117 -0
- package/docs/20260217-partial-fixes-plan.md +62 -0
- package/docs/20260217-plan-analyze-spec.md +117 -0
- package/docs/20260217-post-impl-review.md +1137 -0
- package/docs/20260217-quick-wins-plan.md +66 -0
- package/docs/20260217-split-runner-plan.md +75 -0
- package/docs/20260217-v03-impl-plan.md +80 -0
- package/docs/20260217-v03-post-impl-review.md +589 -0
- package/docs/20260217-v04-impl-plan.md +86 -0
- package/docs/20260217-v05-post-impl-review.md +850 -0
- package/docs/20260217-v06-post-impl-review.md +817 -0
- package/docs/20260218-adr003-port-plan.md +151 -0
- package/docs/20260218-review-adr003-verification.md +175 -0
- package/docs/20260219-fix-plan-bug16-19.md +79 -0
- package/docs/20260219-fix-plan-bug20-22.md +114 -0
- package/docs/20260219-plan-llm-routing.md +116 -0
- package/docs/20260219-review-bug20-22-fixes.md +135 -0
- package/docs/20260219-routing-baseline-keyword.md +63 -0
- package/docs/20260220-plan-structured-logging-p1.md +80 -0
- package/docs/20260220-plan-structured-logging-p2.md +37 -0
- package/docs/20260220-review-llm-routing.md +180 -0
- package/docs/20260220-review-post-fix-llm-routing.md +70 -0
- package/docs/20260221-fix-plan-relevantfiles-split.md +101 -0
- package/docs/20260221-fix-plan-routing-mode.md +125 -0
- package/docs/20260221-review-v0.9-implementation.md +379 -0
- package/docs/20260222-fix-plan-v091-routing-isolation.md +197 -0
- package/docs/20260223-fix-plan-prompt-audit.md +62 -0
- package/docs/20260224-nax-roadmap-phases.md +189 -0
- package/docs/20260225-phase2-llm-service-layer.md +401 -0
- package/docs/20260225-review-v0.10.1.md +187 -0
- package/docs/20260303-v010-implementation-plan.md +165 -0
- package/docs/CLAUDE.md.bak +191 -0
- package/docs/ROADMAP.md +165 -0
- package/docs/SPEC-rectification.md +0 -0
- package/docs/SPEC.md +324 -0
- package/docs/US-001-plugin-loading-verification.md +152 -0
- package/docs/architecture-analysis.md +1076 -0
- package/docs/bugs/BUG-21-escalation-null-attempts.md +48 -0
- package/docs/bugs-from-dogfood-run-c.md +243 -0
- package/docs/code-review-20260228.md +612 -0
- package/docs/code-review-v0.15.0.md +629 -0
- package/docs/hook-lifecycle-test-plan.md +149 -0
- package/docs/releases/v0.11.0-and-earlier.md +20 -0
- package/docs/releases/v0.12.0.md +15 -0
- package/docs/releases/v0.13.0.md +14 -0
- package/docs/releases/v0.14.0.md +20 -0
- package/docs/releases/v0.14.1.md +36 -0
- package/docs/releases/v0.14.2.md +51 -0
- package/docs/releases/v0.14.3.md +174 -0
- package/docs/releases/v0.14.4.md +94 -0
- package/docs/releases/v0.15.0.md +502 -0
- package/docs/releases/v0.15.1.md +170 -0
- package/docs/releases/v0.15.3.md +193 -0
- package/docs/specs/status-file-v0.10.1.md +812 -0
- package/docs/v0.10-global-config.md +206 -0
- package/docs/v0.10-plugin-system.md +415 -0
- package/docs/v0.10-prompt-optimizer.md +234 -0
- package/docs/v0.3-spec.md +244 -0
- package/docs/v0.4-spec.md +140 -0
- package/docs/v0.5-spec.md +237 -0
- package/docs/v0.6-spec.md +371 -0
- package/docs/v0.7-spec.md +177 -0
- package/docs/v0.8-llm-routing.md +206 -0
- package/docs/v0.8-structured-logging.md +132 -0
- package/docs/v0.9.3-prompt-audit.md +112 -0
- package/examples/plugins/console-reporter/index.test.ts +207 -0
- package/examples/plugins/console-reporter/index.ts +110 -0
- package/nax/config.json +147 -0
- package/nax/features/bugfix-v0171/prd.json +52 -0
- package/nax/features/config-management/prd.json +108 -0
- package/nax/features/config-management/progress.txt +5 -0
- package/nax/features/diagnose/acceptance.test.ts +412 -0
- package/nax/features/diagnose/prd.json +41 -0
- package/nax/features/orchestration-fixes/prd.json +89 -0
- package/nax/features/orchestration-fixes/progress.txt +1 -0
- package/nax/features/plugin-integration/US-007-VERIFICATION.md +259 -0
- package/nax/features/plugin-integration/prd.json +208 -0
- package/nax/features/plugin-integration/progress.txt +5 -0
- package/nax/features/precheck/prd.json +205 -0
- package/nax/features/precheck/progress.txt +15 -0
- package/nax/features/structured-logging/prd.json +199 -0
- package/nax/features/unlock/prd.json +36 -0
- package/package.json +47 -0
- package/src/acceptance/fix-generator.ts +348 -0
- package/src/acceptance/generator.ts +282 -0
- package/src/acceptance/index.ts +30 -0
- package/src/acceptance/types.ts +79 -0
- package/src/agents/claude-decompose.ts +169 -0
- package/src/agents/claude-plan.ts +139 -0
- package/src/agents/claude.ts +324 -0
- package/src/agents/cost.ts +268 -0
- package/src/agents/index.ts +13 -0
- package/src/agents/registry.ts +48 -0
- package/src/agents/types-extended.ts +133 -0
- package/src/agents/types.ts +113 -0
- package/src/agents/validation.ts +69 -0
- package/src/analyze/classifier.ts +305 -0
- package/src/analyze/index.ts +16 -0
- package/src/analyze/scanner.ts +175 -0
- package/src/analyze/types.ts +51 -0
- package/src/cli/accept.ts +108 -0
- package/src/cli/analyze-parser.ts +284 -0
- package/src/cli/analyze.ts +207 -0
- package/src/cli/config.ts +561 -0
- package/src/cli/constitution.ts +109 -0
- package/src/cli/diagnose-analysis.ts +159 -0
- package/src/cli/diagnose-formatter.ts +87 -0
- package/src/cli/diagnose.ts +203 -0
- package/src/cli/generate.ts +127 -0
- package/src/cli/index.ts +37 -0
- package/src/cli/init.ts +188 -0
- package/src/cli/interact.ts +295 -0
- package/src/cli/plan.ts +198 -0
- package/src/cli/plugins.ts +111 -0
- package/src/cli/prompts.ts +295 -0
- package/src/cli/runs.ts +174 -0
- package/src/cli/status-cost.ts +151 -0
- package/src/cli/status-features.ts +338 -0
- package/src/cli/status.ts +13 -0
- package/src/commands/common.ts +171 -0
- package/src/commands/diagnose.ts +17 -0
- package/src/commands/index.ts +8 -0
- package/src/commands/logs.ts +384 -0
- package/src/commands/precheck.ts +86 -0
- package/src/commands/unlock.ts +96 -0
- package/src/config/defaults.ts +160 -0
- package/src/config/index.ts +22 -0
- package/src/config/loader.ts +121 -0
- package/src/config/merger.ts +147 -0
- package/src/config/path-security.ts +121 -0
- package/src/config/paths.ts +27 -0
- package/src/config/schema.ts +56 -0
- package/src/config/schemas.ts +286 -0
- package/src/config/types.ts +423 -0
- package/src/config/validate.ts +103 -0
- package/src/constitution/generator.ts +191 -0
- package/src/constitution/generators/aider.ts +41 -0
- package/src/constitution/generators/claude.ts +35 -0
- package/src/constitution/generators/cursor.ts +36 -0
- package/src/constitution/generators/opencode.ts +38 -0
- package/src/constitution/generators/types.ts +33 -0
- package/src/constitution/generators/windsurf.ts +36 -0
- package/src/constitution/index.ts +10 -0
- package/src/constitution/loader.ts +133 -0
- package/src/constitution/types.ts +31 -0
- package/src/context/auto-detect.ts +227 -0
- package/src/context/builder.ts +246 -0
- package/src/context/elements.ts +83 -0
- package/src/context/formatter.ts +107 -0
- package/src/context/generator.ts +129 -0
- package/src/context/generators/aider.ts +34 -0
- package/src/context/generators/claude.ts +28 -0
- package/src/context/generators/cursor.ts +28 -0
- package/src/context/generators/opencode.ts +30 -0
- package/src/context/generators/windsurf.ts +28 -0
- package/src/context/greenfield.ts +114 -0
- package/src/context/index.ts +33 -0
- package/src/context/injector.ts +279 -0
- package/src/context/test-scanner.ts +370 -0
- package/src/context/types.ts +98 -0
- package/src/errors.ts +67 -0
- package/src/execution/batching.ts +157 -0
- package/src/execution/crash-recovery.ts +373 -0
- package/src/execution/escalation/escalation.ts +44 -0
- package/src/execution/escalation/index.ts +13 -0
- package/src/execution/escalation/tier-escalation.ts +295 -0
- package/src/execution/escalation/tier-outcome.ts +158 -0
- package/src/execution/helpers.ts +38 -0
- package/src/execution/index.ts +45 -0
- package/src/execution/lifecycle/acceptance-loop.ts +272 -0
- package/src/execution/lifecycle/headless-formatter.ts +85 -0
- package/src/execution/lifecycle/index.ts +12 -0
- package/src/execution/lifecycle/parallel-lifecycle.ts +101 -0
- package/src/execution/lifecycle/precheck-runner.ts +140 -0
- package/src/execution/lifecycle/run-cleanup.ts +81 -0
- package/src/execution/lifecycle/run-completion.ts +129 -0
- package/src/execution/lifecycle/run-initialization.ts +141 -0
- package/src/execution/lifecycle/run-lifecycle.ts +312 -0
- package/src/execution/lifecycle/run-setup.ts +204 -0
- package/src/execution/lifecycle/story-hooks.ts +38 -0
- package/src/execution/lifecycle/story-size-prompts.ts +123 -0
- package/src/execution/lock.ts +115 -0
- package/src/execution/parallel-executor.ts +216 -0
- package/src/execution/parallel.ts +400 -0
- package/src/execution/pid-registry.ts +280 -0
- package/src/execution/pipeline-result-handler.ts +388 -0
- package/src/execution/post-verify-rectification.ts +188 -0
- package/src/execution/post-verify.ts +274 -0
- package/src/execution/progress.ts +25 -0
- package/src/execution/prompts.ts +127 -0
- package/src/execution/queue-handler.ts +109 -0
- package/src/execution/rectification.ts +13 -0
- package/src/execution/runner.ts +377 -0
- package/src/execution/sequential-executor.ts +388 -0
- package/src/execution/status-file.ts +264 -0
- package/src/execution/status-writer.ts +139 -0
- package/src/execution/story-context.ts +229 -0
- package/src/execution/test-output-parser.ts +14 -0
- package/src/execution/verification.ts +72 -0
- package/src/hooks/index.ts +2 -0
- package/src/hooks/runner.ts +286 -0
- package/src/hooks/types.ts +67 -0
- package/src/interaction/chain.ts +154 -0
- package/src/interaction/index.ts +60 -0
- package/src/interaction/init.ts +83 -0
- package/src/interaction/plugins/auto.ts +217 -0
- package/src/interaction/plugins/cli.ts +300 -0
- package/src/interaction/plugins/telegram.ts +384 -0
- package/src/interaction/plugins/webhook.ts +258 -0
- package/src/interaction/state.ts +171 -0
- package/src/interaction/triggers.ts +229 -0
- package/src/interaction/types.ts +163 -0
- package/src/logger/formatters.ts +84 -0
- package/src/logger/index.ts +16 -0
- package/src/logger/logger.ts +298 -0
- package/src/logger/types.ts +48 -0
- package/src/logging/formatter.ts +355 -0
- package/src/logging/index.ts +22 -0
- package/src/logging/types.ts +93 -0
- package/src/metrics/aggregator.ts +190 -0
- package/src/metrics/index.ts +14 -0
- package/src/metrics/tracker.ts +200 -0
- package/src/metrics/types.ts +109 -0
- package/src/optimizer/index.ts +62 -0
- package/src/optimizer/noop.optimizer.ts +24 -0
- package/src/optimizer/rule-based.optimizer.ts +248 -0
- package/src/optimizer/types.ts +53 -0
- package/src/pipeline/events.ts +130 -0
- package/src/pipeline/index.ts +19 -0
- package/src/pipeline/runner.ts +161 -0
- package/src/pipeline/stages/acceptance.ts +197 -0
- package/src/pipeline/stages/completion.ts +99 -0
- package/src/pipeline/stages/constitution.ts +63 -0
- package/src/pipeline/stages/context.ts +117 -0
- package/src/pipeline/stages/execution.ts +194 -0
- package/src/pipeline/stages/index.ts +62 -0
- package/src/pipeline/stages/optimizer.ts +74 -0
- package/src/pipeline/stages/prompt.ts +57 -0
- package/src/pipeline/stages/queue-check.ts +103 -0
- package/src/pipeline/stages/review.ts +181 -0
- package/src/pipeline/stages/routing.ts +81 -0
- package/src/pipeline/stages/verify.ts +100 -0
- package/src/pipeline/types.ts +167 -0
- package/src/plugins/index.ts +31 -0
- package/src/plugins/loader.ts +287 -0
- package/src/plugins/registry.ts +168 -0
- package/src/plugins/types.ts +327 -0
- package/src/plugins/validator.ts +352 -0
- package/src/prd/index.ts +172 -0
- package/src/prd/types.ts +202 -0
- package/src/precheck/checks-blockers.ts +391 -0
- package/src/precheck/checks-warnings.ts +142 -0
- package/src/precheck/checks.ts +30 -0
- package/src/precheck/index.ts +247 -0
- package/src/precheck/story-size-gate.ts +144 -0
- package/src/precheck/types.ts +31 -0
- package/src/queue/index.ts +2 -0
- package/src/queue/manager.ts +254 -0
- package/src/queue/types.ts +54 -0
- package/src/review/index.ts +8 -0
- package/src/review/runner.ts +172 -0
- package/src/review/types.ts +66 -0
- package/src/routing/builder.ts +81 -0
- package/src/routing/chain.ts +74 -0
- package/src/routing/index.ts +16 -0
- package/src/routing/loader.ts +58 -0
- package/src/routing/router.ts +303 -0
- package/src/routing/strategies/adaptive.ts +215 -0
- package/src/routing/strategies/index.ts +8 -0
- package/src/routing/strategies/keyword.ts +163 -0
- package/src/routing/strategies/llm-prompts.ts +209 -0
- package/src/routing/strategies/llm.ts +235 -0
- package/src/routing/strategies/manual.ts +50 -0
- package/src/routing/strategy.ts +99 -0
- package/src/tdd/cleanup.ts +111 -0
- package/src/tdd/index.ts +23 -0
- package/src/tdd/isolation.ts +123 -0
- package/src/tdd/orchestrator.ts +383 -0
- package/src/tdd/prompts.ts +270 -0
- package/src/tdd/rectification-gate.ts +183 -0
- package/src/tdd/session-runner.ts +179 -0
- package/src/tdd/types.ts +81 -0
- package/src/tdd/verdict.ts +271 -0
- package/src/tui/App.tsx +265 -0
- package/src/tui/components/AgentPanel.tsx +75 -0
- package/src/tui/components/CostOverlay.tsx +118 -0
- package/src/tui/components/HelpOverlay.tsx +107 -0
- package/src/tui/components/StatusBar.tsx +63 -0
- package/src/tui/components/StoriesPanel.tsx +177 -0
- package/src/tui/hooks/useKeyboard.ts +142 -0
- package/src/tui/hooks/useLayout.ts +137 -0
- package/src/tui/hooks/usePipelineEvents.ts +183 -0
- package/src/tui/hooks/usePty.ts +194 -0
- package/src/tui/index.tsx +38 -0
- package/src/tui/types.ts +76 -0
- package/src/utils/git.ts +83 -0
- package/src/utils/queue-writer.ts +54 -0
- package/src/verification/executor.ts +235 -0
- package/src/verification/gate.ts +207 -0
- package/src/verification/index.ts +12 -0
- package/src/verification/parser.ts +230 -0
- package/src/verification/rectification.ts +108 -0
- package/src/verification/types.ts +113 -0
- package/src/worktree/dispatcher.ts +65 -0
- package/src/worktree/index.ts +2 -0
- package/src/worktree/manager.ts +187 -0
- package/src/worktree/merge.ts +301 -0
- package/src/worktree/types.ts +4 -0
- package/test/TEST_COVERAGE_US001.md +217 -0
- package/test/TEST_COVERAGE_US003.md +84 -0
- package/test/TEST_COVERAGE_US005.md +86 -0
- package/test/US-002-orchestrator.test.ts +246 -0
- package/test/acceptance/cm-003-default-view.test.ts +194 -0
- package/test/execution/pid-registry.test.ts +240 -0
- package/test/execution/post-verify.test.ts +224 -0
- package/test/helpers/timeout.ts +42 -0
- package/test/integration/US-002-TEST-SUMMARY.md +107 -0
- package/test/integration/US-003-TEST-SUMMARY.md +149 -0
- package/test/integration/US-004-TEST-SUMMARY.md +106 -0
- package/test/integration/US-005-TEST-SUMMARY.md +138 -0
- package/test/integration/US-007-TEST-SUMMARY.md +100 -0
- package/test/integration/agent-validation.test.ts +439 -0
- package/test/integration/analyze-integration.test.ts +261 -0
- package/test/integration/analyze-scanner.test.ts +131 -0
- package/test/integration/cli-config-default-edge-cases.test.ts +222 -0
- package/test/integration/cli-config-default-view.test.ts +229 -0
- package/test/integration/cli-config-diff.test.ts +460 -0
- package/test/integration/cli-config.test.ts +736 -0
- package/test/integration/cli-diagnose.test.ts +592 -0
- package/test/integration/cli-logs.test.ts +314 -0
- package/test/integration/cli-plugins.test.ts +678 -0
- package/test/integration/cli-precheck.test.ts +371 -0
- package/test/integration/cli-run-headless.test.ts +173 -0
- package/test/integration/cli.test.ts +75 -0
- package/test/integration/config/merger.test.ts +465 -0
- package/test/integration/config/paths.test.ts +51 -0
- package/test/integration/config-loader.test.ts +265 -0
- package/test/integration/config.test.ts +444 -0
- package/test/integration/context-integration.test.ts +702 -0
- package/test/integration/context-provider-injection.test.ts +506 -0
- package/test/integration/context-verification-integration.test.ts +295 -0
- package/test/integration/e2e.test.ts +896 -0
- package/test/integration/execution.test.ts +625 -0
- package/test/integration/helpers.test.ts +295 -0
- package/test/integration/hooks.test.ts +361 -0
- package/test/integration/interaction-chain-pipeline.test.ts +464 -0
- package/test/integration/isolation.test.ts +143 -0
- package/test/integration/logger.test.ts +461 -0
- package/test/integration/parallel.test.ts +250 -0
- package/test/integration/path-security.test.ts +173 -0
- package/test/integration/pipeline-acceptance.test.ts +302 -0
- package/test/integration/pipeline-events.test.ts +475 -0
- package/test/integration/pipeline.test.ts +658 -0
- package/test/integration/plan.test.ts +157 -0
- package/test/integration/plugin-routing.test.ts +921 -0
- package/test/integration/plugins/config-integration.test.ts +172 -0
- package/test/integration/plugins/config-resolution.test.ts +522 -0
- package/test/integration/plugins/loader.test.ts +641 -0
- package/test/integration/plugins/registry.test.ts +746 -0
- package/test/integration/plugins/validator.test.ts +563 -0
- package/test/integration/prd-pause.test.ts +205 -0
- package/test/integration/prd-resolvers.test.ts +185 -0
- package/test/integration/precheck-integration.test.ts +468 -0
- package/test/integration/precheck.test.ts +805 -0
- package/test/integration/progress.test.ts +34 -0
- package/test/integration/rectification-flow.test.ts +512 -0
- package/test/integration/reporter-lifecycle.test.ts +860 -0
- package/test/integration/review-config-commands.test.ts +319 -0
- package/test/integration/review-config-schema.test.ts +116 -0
- package/test/integration/review-plugin-integration.test.ts +722 -0
- package/test/integration/review.test.ts +149 -0
- package/test/integration/routing-stage-bug-021.test.ts +274 -0
- package/test/integration/routing-stage-greenfield.test.ts +286 -0
- package/test/integration/runner-config-plugins.test.ts +461 -0
- package/test/integration/runner-fixes.test.ts +399 -0
- package/test/integration/runner-plugin-integration.test.ts +543 -0
- package/test/integration/runner.test.ts +1679 -0
- package/test/integration/s5-greenfield-fallback.test.ts +297 -0
- package/test/integration/status-file-integration.test.ts +325 -0
- package/test/integration/status-file.test.ts +379 -0
- package/test/integration/status-writer.test.ts +345 -0
- package/test/integration/story-id-in-events.test.ts +273 -0
- package/test/integration/tdd-cleanup.test.ts +246 -0
- package/test/integration/tdd-orchestrator.test.ts +1762 -0
- package/test/integration/test-scanner.test.ts +403 -0
- package/test/integration/verification-asset-check.test.ts +142 -0
- package/test/integration/verify-stage.test.ts +275 -0
- package/test/integration/worktree/manager.test.ts +218 -0
- package/test/integration/worktree/merge.test.ts +341 -0
- package/test/manual/logging-formatter-demo.ts +158 -0
- package/test/ui/tui-agent-panel.test.tsx +99 -0
- package/test/ui/tui-controls.test.ts +334 -0
- package/test/ui/tui-cost-and-pty.test.ts +189 -0
- package/test/ui/tui-layout.test.ts +378 -0
- package/test/ui/tui-pty-integration.test.tsx +159 -0
- package/test/ui/tui-stories.test.ts +332 -0
- package/test/unit/acceptance.test.ts +186 -0
- package/test/unit/agent-stderr-capture.test.ts +146 -0
- package/test/unit/analyze-classifier.test.ts +215 -0
- package/test/unit/analyze.test.ts +224 -0
- package/test/unit/auto-detect.test.ts +249 -0
- package/test/unit/cli-status.test.ts +417 -0
- package/test/unit/commands/common.test.ts +320 -0
- package/test/unit/commands/logs.test.ts +416 -0
- package/test/unit/commands/unlock.test.ts +319 -0
- package/test/unit/constitution-generators.test.ts +160 -0
- package/test/unit/constitution.test.ts +209 -0
- package/test/unit/context.test.ts +1722 -0
- package/test/unit/cost.test.ts +231 -0
- package/test/unit/crash-recovery.test.ts +308 -0
- package/test/unit/escalation.test.ts +126 -0
- package/test/unit/execution-logging-stderr.test.ts +156 -0
- package/test/unit/execution-stage.test.ts +122 -0
- package/test/unit/fix-generator.test.ts +275 -0
- package/test/unit/formatters.test.ts +469 -0
- package/test/unit/greenfield.test.ts +179 -0
- package/test/unit/helpers.test.ts +317 -0
- package/test/unit/interaction/human-review-trigger.test.ts +164 -0
- package/test/unit/interaction-network-failures.test.ts +389 -0
- package/test/unit/interaction-plugins.test.ts +164 -0
- package/test/unit/isolation.test.ts +134 -0
- package/test/unit/logging/formatter.test.ts +455 -0
- package/test/unit/merge.test.ts +268 -0
- package/test/unit/metrics.test.ts +276 -0
- package/test/unit/optimizer/noop.optimizer.test.ts +125 -0
- package/test/unit/optimizer/rule-based.optimizer.test.ts +358 -0
- package/test/unit/prd-auto-default.test.ts +290 -0
- package/test/unit/prd-failure-category.test.ts +176 -0
- package/test/unit/prd-get-next-story.test.ts +186 -0
- package/test/unit/precheck-checks.test.ts +840 -0
- package/test/unit/precheck-story-size-gate.test.ts +287 -0
- package/test/unit/precheck-types.test.ts +142 -0
- package/test/unit/prompts.test.ts +475 -0
- package/test/unit/queue.test.ts +237 -0
- package/test/unit/rectification.test.ts +284 -0
- package/test/unit/registry.test.ts +287 -0
- package/test/unit/routing.test.ts +937 -0
- package/test/unit/run-lifecycle.test.ts +140 -0
- package/test/unit/storyid-events.test.ts +224 -0
- package/test/unit/tdd-verdict.test.ts +492 -0
- package/test/unit/test-output-parser.test.ts +377 -0
- package/test/unit/verdict.test.ts +324 -0
- package/test/unit/worktree-manager.test.ts +158 -0
- package/tsconfig.json +27 -0
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Configuration Validation
|
|
3
|
+
*
|
|
4
|
+
* @deprecated Use NaxConfigSchema.safeParse() from schema.ts instead.
|
|
5
|
+
* This module is kept for backward compatibility only.
|
|
6
|
+
*
|
|
7
|
+
* Validates NaxConfig structure and constraints.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import type { NaxConfig } from "./schema";
|
|
11
|
+
|
|
12
|
+
/** Validation result */
|
|
13
|
+
export interface ValidationResult {
|
|
14
|
+
valid: boolean;
|
|
15
|
+
errors: string[];
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Validate NaxConfig
|
|
20
|
+
*
|
|
21
|
+
* Checks:
|
|
22
|
+
* - version === 1
|
|
23
|
+
* - maxIterations > 0
|
|
24
|
+
* - costLimit > 0
|
|
25
|
+
* - sessionTimeoutSeconds > 0
|
|
26
|
+
* - defaultAgent is non-empty
|
|
27
|
+
* - escalation.tierOrder has at least one tier with valid attempts
|
|
28
|
+
*/
|
|
29
|
+
export function validateConfig(config: NaxConfig): ValidationResult {
|
|
30
|
+
const errors: string[] = [];
|
|
31
|
+
|
|
32
|
+
// Version check
|
|
33
|
+
if (config.version !== 1) {
|
|
34
|
+
errors.push(`Invalid version: expected 1, got ${config.version}`);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Models mapping
|
|
38
|
+
const requiredTiers = ["fast", "balanced", "powerful"] as const;
|
|
39
|
+
if (!config.models) {
|
|
40
|
+
errors.push("models mapping is required");
|
|
41
|
+
} else {
|
|
42
|
+
for (const tier of requiredTiers) {
|
|
43
|
+
const entry = config.models[tier];
|
|
44
|
+
if (!entry) {
|
|
45
|
+
errors.push(`models.${tier} is required`);
|
|
46
|
+
} else if (typeof entry === "string") {
|
|
47
|
+
if (entry.trim() === "") {
|
|
48
|
+
errors.push(`models.${tier} must be a non-empty model identifier`);
|
|
49
|
+
}
|
|
50
|
+
} else {
|
|
51
|
+
if (!entry.provider || entry.provider.trim() === "") {
|
|
52
|
+
errors.push(`models.${tier}.provider must be non-empty`);
|
|
53
|
+
}
|
|
54
|
+
if (!entry.model || entry.model.trim() === "") {
|
|
55
|
+
errors.push(`models.${tier}.model must be non-empty`);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Execution limits
|
|
62
|
+
if (config.execution.maxIterations <= 0) {
|
|
63
|
+
errors.push(`maxIterations must be > 0, got ${config.execution.maxIterations}`);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
if (config.execution.costLimit <= 0) {
|
|
67
|
+
errors.push(`costLimit must be > 0, got ${config.execution.costLimit}`);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
if (config.execution.sessionTimeoutSeconds <= 0) {
|
|
71
|
+
errors.push(`sessionTimeoutSeconds must be > 0, got ${config.execution.sessionTimeoutSeconds}`);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Auto mode config
|
|
75
|
+
if (!config.autoMode.defaultAgent || config.autoMode.defaultAgent.trim() === "") {
|
|
76
|
+
errors.push("defaultAgent must be non-empty");
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
if (!config.autoMode.escalation.tierOrder || config.autoMode.escalation.tierOrder.length === 0) {
|
|
80
|
+
errors.push("escalation.tierOrder must have at least one tier");
|
|
81
|
+
} else {
|
|
82
|
+
for (const tc of config.autoMode.escalation.tierOrder) {
|
|
83
|
+
if (tc.attempts < 1 || tc.attempts > 20) {
|
|
84
|
+
errors.push(`escalation.tierOrder: tier "${tc.tier}" attempts must be 1-20, got ${tc.attempts}`);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// Validate complexityRouting values reference tiers that exist in models config
|
|
90
|
+
const configuredTiers = Object.keys(config.models);
|
|
91
|
+
const complexities = ["simple", "medium", "complex", "expert"] as const;
|
|
92
|
+
for (const complexity of complexities) {
|
|
93
|
+
const tier = config.autoMode.complexityRouting[complexity];
|
|
94
|
+
if (!configuredTiers.includes(tier)) {
|
|
95
|
+
errors.push(`complexityRouting.${complexity} must be one of: ${configuredTiers.join(", ")} (got '${tier}')`);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
return {
|
|
100
|
+
valid: errors.length === 0,
|
|
101
|
+
errors,
|
|
102
|
+
};
|
|
103
|
+
}
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Constitution Generator Orchestrator
|
|
3
|
+
*
|
|
4
|
+
* Generates agent-specific config files from nax/constitution.md.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { existsSync } from "node:fs";
|
|
8
|
+
import { join } from "node:path";
|
|
9
|
+
import { validateFilePath } from "../config/path-security";
|
|
10
|
+
import { aiderGenerator } from "./generators/aider";
|
|
11
|
+
import { claudeGenerator } from "./generators/claude";
|
|
12
|
+
import { cursorGenerator } from "./generators/cursor";
|
|
13
|
+
import { opencodeGenerator } from "./generators/opencode";
|
|
14
|
+
import type { AgentConfigGenerator, AgentType, ConstitutionContent, GeneratorMap } from "./generators/types";
|
|
15
|
+
import { windsurfGenerator } from "./generators/windsurf";
|
|
16
|
+
|
|
17
|
+
/** Generator registry */
|
|
18
|
+
const GENERATORS: GeneratorMap = {
|
|
19
|
+
claude: claudeGenerator,
|
|
20
|
+
opencode: opencodeGenerator,
|
|
21
|
+
cursor: cursorGenerator,
|
|
22
|
+
windsurf: windsurfGenerator,
|
|
23
|
+
aider: aiderGenerator,
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
/** Generation result for a single agent */
|
|
27
|
+
export interface GenerationResult {
|
|
28
|
+
agent: AgentType;
|
|
29
|
+
outputFile: string;
|
|
30
|
+
content: string;
|
|
31
|
+
written: boolean;
|
|
32
|
+
error?: string;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/** Generate options */
|
|
36
|
+
export interface GenerateOptions {
|
|
37
|
+
/** Constitution file path (default: nax/constitution.md) */
|
|
38
|
+
constitutionPath: string;
|
|
39
|
+
/** Output directory (default: project root) */
|
|
40
|
+
outputDir: string;
|
|
41
|
+
/** Dry run mode (don't write files) */
|
|
42
|
+
dryRun?: boolean;
|
|
43
|
+
/** Specific agent to generate for (default: all) */
|
|
44
|
+
agent?: AgentType;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Load constitution content from file
|
|
49
|
+
*/
|
|
50
|
+
async function loadConstitutionContent(constitutionPath: string): Promise<ConstitutionContent> {
|
|
51
|
+
if (!existsSync(constitutionPath)) {
|
|
52
|
+
throw new Error(`Constitution file not found: ${constitutionPath}`);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const file = Bun.file(constitutionPath);
|
|
56
|
+
const markdown = await file.text();
|
|
57
|
+
|
|
58
|
+
return {
|
|
59
|
+
markdown,
|
|
60
|
+
sections: {}, // TODO: implement section parsing if needed
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Generate config for a specific agent
|
|
66
|
+
*/
|
|
67
|
+
function generateForAgent(
|
|
68
|
+
agent: AgentType,
|
|
69
|
+
constitution: ConstitutionContent,
|
|
70
|
+
): { content: string; outputFile: string } {
|
|
71
|
+
const generator = GENERATORS[agent];
|
|
72
|
+
if (!generator) {
|
|
73
|
+
throw new Error(`Unknown agent type: ${agent}`);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const content = generator.generate(constitution);
|
|
77
|
+
return {
|
|
78
|
+
content,
|
|
79
|
+
outputFile: generator.outputFile,
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Write generated content to file
|
|
85
|
+
*/
|
|
86
|
+
async function writeGeneratedFile(outputDir: string, filename: string, content: string): Promise<void> {
|
|
87
|
+
const outputPath = join(outputDir, filename);
|
|
88
|
+
|
|
89
|
+
// SEC-5: Validate path before writing
|
|
90
|
+
const validatedPath = validateFilePath(outputPath, outputDir);
|
|
91
|
+
|
|
92
|
+
await Bun.write(validatedPath, content);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Generate config for a specific agent
|
|
97
|
+
*
|
|
98
|
+
* @param agent - Agent type to generate for
|
|
99
|
+
* @param constitutionPath - Path to constitution file
|
|
100
|
+
* @param outputDir - Directory to write output file
|
|
101
|
+
* @param dryRun - If true, don't write files
|
|
102
|
+
* @returns Generation result
|
|
103
|
+
*/
|
|
104
|
+
export async function generateFor(
|
|
105
|
+
agent: AgentType,
|
|
106
|
+
constitutionPath: string,
|
|
107
|
+
outputDir: string,
|
|
108
|
+
dryRun = false,
|
|
109
|
+
): Promise<GenerationResult> {
|
|
110
|
+
try {
|
|
111
|
+
const constitution = await loadConstitutionContent(constitutionPath);
|
|
112
|
+
const { content, outputFile } = generateForAgent(agent, constitution);
|
|
113
|
+
|
|
114
|
+
if (!dryRun) {
|
|
115
|
+
await writeGeneratedFile(outputDir, outputFile, content);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
return {
|
|
119
|
+
agent,
|
|
120
|
+
outputFile,
|
|
121
|
+
content,
|
|
122
|
+
written: !dryRun,
|
|
123
|
+
};
|
|
124
|
+
} catch (err) {
|
|
125
|
+
const error = err instanceof Error ? err.message : String(err);
|
|
126
|
+
return {
|
|
127
|
+
agent,
|
|
128
|
+
outputFile: GENERATORS[agent].outputFile,
|
|
129
|
+
content: "",
|
|
130
|
+
written: false,
|
|
131
|
+
error,
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Generate config files for all agents
|
|
138
|
+
*
|
|
139
|
+
* @param constitutionPath - Path to constitution file
|
|
140
|
+
* @param outputDir - Directory to write output files
|
|
141
|
+
* @param dryRun - If true, don't write files
|
|
142
|
+
* @returns Array of generation results
|
|
143
|
+
*/
|
|
144
|
+
export async function generateAll(
|
|
145
|
+
constitutionPath: string,
|
|
146
|
+
outputDir: string,
|
|
147
|
+
dryRun = false,
|
|
148
|
+
): Promise<GenerationResult[]> {
|
|
149
|
+
const agents: AgentType[] = ["claude", "opencode", "cursor", "windsurf", "aider"];
|
|
150
|
+
const results: GenerationResult[] = [];
|
|
151
|
+
|
|
152
|
+
for (const agent of agents) {
|
|
153
|
+
const result = await generateFor(agent, constitutionPath, outputDir, dryRun);
|
|
154
|
+
results.push(result);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
return results;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Check if constitution file is newer than generated configs
|
|
162
|
+
*/
|
|
163
|
+
export function isConstitutionNewer(constitutionPath: string, outputDir: string): boolean {
|
|
164
|
+
if (!existsSync(constitutionPath)) {
|
|
165
|
+
return false;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
const constitutionStat = Bun.file(constitutionPath);
|
|
169
|
+
const constitutionMtime = constitutionStat.lastModified;
|
|
170
|
+
|
|
171
|
+
// Check all generated files
|
|
172
|
+
const agents: AgentType[] = ["claude", "opencode", "cursor", "windsurf", "aider"];
|
|
173
|
+
for (const agent of agents) {
|
|
174
|
+
const outputFile = GENERATORS[agent].outputFile;
|
|
175
|
+
const outputPath = join(outputDir, outputFile);
|
|
176
|
+
|
|
177
|
+
if (!existsSync(outputPath)) {
|
|
178
|
+
// If any config file is missing, consider constitution newer
|
|
179
|
+
return true;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
const outputStat = Bun.file(outputPath);
|
|
183
|
+
const outputMtime = outputStat.lastModified;
|
|
184
|
+
|
|
185
|
+
if (constitutionMtime > outputMtime) {
|
|
186
|
+
return true;
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
return false;
|
|
191
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Aider Config Generator
|
|
3
|
+
*
|
|
4
|
+
* Generates .aider.conf.yml from nax/constitution.md.
|
|
5
|
+
* Aider uses YAML format for configuration.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type { AgentConfigGenerator, ConstitutionContent } from "./types";
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Generate .aider.conf.yml from constitution
|
|
12
|
+
*/
|
|
13
|
+
function generateAiderConfig(constitution: ConstitutionContent): string {
|
|
14
|
+
const { markdown } = constitution;
|
|
15
|
+
|
|
16
|
+
// Build .aider.conf.yml format
|
|
17
|
+
const header = `# Aider Configuration
|
|
18
|
+
# Auto-generated from nax/constitution.md
|
|
19
|
+
# DO NOT EDIT MANUALLY
|
|
20
|
+
|
|
21
|
+
# Project instructions
|
|
22
|
+
instructions: |
|
|
23
|
+
`;
|
|
24
|
+
|
|
25
|
+
// Indent all lines of markdown for YAML multi-line string
|
|
26
|
+
const indentedMarkdown = markdown
|
|
27
|
+
.split("\n")
|
|
28
|
+
.map((line) => ` ${line}`)
|
|
29
|
+
.join("\n");
|
|
30
|
+
|
|
31
|
+
return `${header}${indentedMarkdown}\n`;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Aider generator
|
|
36
|
+
*/
|
|
37
|
+
export const aiderGenerator: AgentConfigGenerator = {
|
|
38
|
+
name: "aider",
|
|
39
|
+
outputFile: ".aider.conf.yml",
|
|
40
|
+
generate: generateAiderConfig,
|
|
41
|
+
};
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Claude Code Config Generator
|
|
3
|
+
*
|
|
4
|
+
* Generates CLAUDE.md from nax/constitution.md.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { AgentConfigGenerator, ConstitutionContent } from "./types";
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Generate CLAUDE.md from constitution
|
|
11
|
+
*/
|
|
12
|
+
function generateClaudeConfig(constitution: ConstitutionContent): string {
|
|
13
|
+
const { markdown } = constitution;
|
|
14
|
+
|
|
15
|
+
// Build CLAUDE.md format
|
|
16
|
+
const header = `# Project Constitution
|
|
17
|
+
|
|
18
|
+
This file is auto-generated from \`nax/constitution.md\`.
|
|
19
|
+
DO NOT EDIT MANUALLY — changes will be overwritten.
|
|
20
|
+
|
|
21
|
+
---
|
|
22
|
+
|
|
23
|
+
`;
|
|
24
|
+
|
|
25
|
+
return header + markdown;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Claude Code generator
|
|
30
|
+
*/
|
|
31
|
+
export const claudeGenerator: AgentConfigGenerator = {
|
|
32
|
+
name: "claude",
|
|
33
|
+
outputFile: "CLAUDE.md",
|
|
34
|
+
generate: generateClaudeConfig,
|
|
35
|
+
};
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cursor Rules Generator
|
|
3
|
+
*
|
|
4
|
+
* Generates .cursorrules from nax/constitution.md.
|
|
5
|
+
* Cursor uses a simple text format similar to Claude but in a dotfile.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type { AgentConfigGenerator, ConstitutionContent } from "./types";
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Generate .cursorrules from constitution
|
|
12
|
+
*/
|
|
13
|
+
function generateCursorRules(constitution: ConstitutionContent): string {
|
|
14
|
+
const { markdown } = constitution;
|
|
15
|
+
|
|
16
|
+
// Build .cursorrules format
|
|
17
|
+
const header = `# Project Rules
|
|
18
|
+
|
|
19
|
+
Auto-generated from nax/constitution.md
|
|
20
|
+
DO NOT EDIT MANUALLY
|
|
21
|
+
|
|
22
|
+
---
|
|
23
|
+
|
|
24
|
+
`;
|
|
25
|
+
|
|
26
|
+
return header + markdown;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Cursor generator
|
|
31
|
+
*/
|
|
32
|
+
export const cursorGenerator: AgentConfigGenerator = {
|
|
33
|
+
name: "cursor",
|
|
34
|
+
outputFile: ".cursorrules",
|
|
35
|
+
generate: generateCursorRules,
|
|
36
|
+
};
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OpenCode Config Generator
|
|
3
|
+
*
|
|
4
|
+
* Generates AGENTS.md from nax/constitution.md.
|
|
5
|
+
* Format is similar to CLAUDE.md but with OpenCode-specific headers.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type { AgentConfigGenerator, ConstitutionContent } from "./types";
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Generate AGENTS.md from constitution
|
|
12
|
+
*/
|
|
13
|
+
function generateOpencodeConfig(constitution: ConstitutionContent): string {
|
|
14
|
+
const { markdown } = constitution;
|
|
15
|
+
|
|
16
|
+
// Build AGENTS.md format (OpenCode/Codex format)
|
|
17
|
+
const header = `# Agent Instructions
|
|
18
|
+
|
|
19
|
+
This file is auto-generated from \`nax/constitution.md\`.
|
|
20
|
+
DO NOT EDIT MANUALLY — changes will be overwritten.
|
|
21
|
+
|
|
22
|
+
These instructions apply to all AI coding agents in this project.
|
|
23
|
+
|
|
24
|
+
---
|
|
25
|
+
|
|
26
|
+
`;
|
|
27
|
+
|
|
28
|
+
return header + markdown;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* OpenCode generator
|
|
33
|
+
*/
|
|
34
|
+
export const opencodeGenerator: AgentConfigGenerator = {
|
|
35
|
+
name: "opencode",
|
|
36
|
+
outputFile: "AGENTS.md",
|
|
37
|
+
generate: generateOpencodeConfig,
|
|
38
|
+
};
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Constitution Generator Types
|
|
3
|
+
*
|
|
4
|
+
* Defines the interface for generating agent-specific config files from nax/constitution.md.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
/** Constitution content structure for generators */
|
|
8
|
+
export interface ConstitutionContent {
|
|
9
|
+
/** Full constitution markdown content */
|
|
10
|
+
markdown: string;
|
|
11
|
+
/** Parsed sections (optional, for structured generation) */
|
|
12
|
+
sections?: Record<string, string>;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/** Agent config generator interface */
|
|
16
|
+
export interface AgentConfigGenerator {
|
|
17
|
+
/** Generator name (e.g., 'claude', 'opencode', 'cursor') */
|
|
18
|
+
name: string;
|
|
19
|
+
/** Output filename (e.g., 'CLAUDE.md', '.cursorrules') */
|
|
20
|
+
outputFile: string;
|
|
21
|
+
/**
|
|
22
|
+
* Generate agent-specific config file content from constitution
|
|
23
|
+
* @param constitution - Constitution content
|
|
24
|
+
* @returns Generated config file content
|
|
25
|
+
*/
|
|
26
|
+
generate(constitution: ConstitutionContent): string;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/** All available generator types */
|
|
30
|
+
export type AgentType = "claude" | "opencode" | "cursor" | "windsurf" | "aider";
|
|
31
|
+
|
|
32
|
+
/** Generator registry map */
|
|
33
|
+
export type GeneratorMap = Record<AgentType, AgentConfigGenerator>;
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Windsurf Rules Generator
|
|
3
|
+
*
|
|
4
|
+
* Generates .windsurfrules from nax/constitution.md.
|
|
5
|
+
* Windsurf uses a similar format to Cursor.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type { AgentConfigGenerator, ConstitutionContent } from "./types";
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Generate .windsurfrules from constitution
|
|
12
|
+
*/
|
|
13
|
+
function generateWindsurfRules(constitution: ConstitutionContent): string {
|
|
14
|
+
const { markdown } = constitution;
|
|
15
|
+
|
|
16
|
+
// Build .windsurfrules format
|
|
17
|
+
const header = `# Windsurf Project Rules
|
|
18
|
+
|
|
19
|
+
Auto-generated from nax/constitution.md
|
|
20
|
+
DO NOT EDIT MANUALLY
|
|
21
|
+
|
|
22
|
+
---
|
|
23
|
+
|
|
24
|
+
`;
|
|
25
|
+
|
|
26
|
+
return header + markdown;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Windsurf generator
|
|
31
|
+
*/
|
|
32
|
+
export const windsurfGenerator: AgentConfigGenerator = {
|
|
33
|
+
name: "windsurf",
|
|
34
|
+
outputFile: ".windsurfrules",
|
|
35
|
+
generate: generateWindsurfRules,
|
|
36
|
+
};
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Constitution system
|
|
3
|
+
*
|
|
4
|
+
* Provides project-level governance by injecting a constitution.md file
|
|
5
|
+
* into every agent session prompt. The constitution defines coding standards,
|
|
6
|
+
* architectural rules, testing requirements, and forbidden patterns.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
export type { ConstitutionConfig, ConstitutionResult } from "./types";
|
|
10
|
+
export { loadConstitution, estimateTokens, truncateToTokens } from "./loader";
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Constitution loader
|
|
3
|
+
*
|
|
4
|
+
* Loads and processes global + project constitution files.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { existsSync } from "node:fs";
|
|
8
|
+
import { join } from "node:path";
|
|
9
|
+
import { validateFilePath } from "../config/path-security";
|
|
10
|
+
import { globalConfigDir } from "../config/paths";
|
|
11
|
+
import type { ConstitutionConfig, ConstitutionResult } from "./types";
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Estimate token count for text
|
|
15
|
+
*
|
|
16
|
+
* Uses simple heuristic: 1 token ≈ 3 characters (conservative estimate)
|
|
17
|
+
* This is a rough approximation sufficient for quota management.
|
|
18
|
+
*
|
|
19
|
+
* @param text - Text to estimate tokens for
|
|
20
|
+
* @returns Estimated token count
|
|
21
|
+
*/
|
|
22
|
+
export function estimateTokens(text: string): number {
|
|
23
|
+
return Math.ceil(text.length / 3);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Truncate text to fit within token limit
|
|
28
|
+
*
|
|
29
|
+
* Truncates at word boundaries to avoid cutting mid-word.
|
|
30
|
+
*
|
|
31
|
+
* @param text - Text to truncate
|
|
32
|
+
* @param maxTokens - Maximum tokens allowed
|
|
33
|
+
* @returns Truncated text
|
|
34
|
+
*/
|
|
35
|
+
export function truncateToTokens(text: string, maxTokens: number): string {
|
|
36
|
+
const maxChars = maxTokens * 3; // 1 token ≈ 3 chars
|
|
37
|
+
|
|
38
|
+
if (text.length <= maxChars) {
|
|
39
|
+
return text;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// Find last word boundary before maxChars
|
|
43
|
+
const truncated = text.slice(0, maxChars);
|
|
44
|
+
const lastSpace = truncated.lastIndexOf(" ");
|
|
45
|
+
const lastNewline = truncated.lastIndexOf("\n");
|
|
46
|
+
const cutPoint = Math.max(lastSpace, lastNewline);
|
|
47
|
+
|
|
48
|
+
if (cutPoint > 0) {
|
|
49
|
+
return truncated.slice(0, cutPoint);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// Fallback: hard cut if no word boundary found
|
|
53
|
+
return truncated;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Load constitution from global and project directories.
|
|
58
|
+
*
|
|
59
|
+
* Prepends global constitution to project constitution with --- separator.
|
|
60
|
+
* Respects skipGlobal flag in config.
|
|
61
|
+
*
|
|
62
|
+
* @param projectDir - Path to project nax/ directory
|
|
63
|
+
* @param config - Constitution configuration
|
|
64
|
+
* @returns Constitution result or null if disabled/missing
|
|
65
|
+
*/
|
|
66
|
+
export async function loadConstitution(
|
|
67
|
+
projectDir: string,
|
|
68
|
+
config: ConstitutionConfig,
|
|
69
|
+
): Promise<ConstitutionResult | null> {
|
|
70
|
+
if (!config.enabled) {
|
|
71
|
+
return null;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
let combinedContent = "";
|
|
75
|
+
|
|
76
|
+
// Load global constitution (unless skipGlobal is true)
|
|
77
|
+
if (!config.skipGlobal) {
|
|
78
|
+
const globalPath = join(globalConfigDir(), config.path);
|
|
79
|
+
if (existsSync(globalPath)) {
|
|
80
|
+
// SEC-5: Validate path before reading
|
|
81
|
+
const validatedPath = validateFilePath(globalPath, globalConfigDir());
|
|
82
|
+
const globalFile = Bun.file(validatedPath);
|
|
83
|
+
const globalContent = await globalFile.text();
|
|
84
|
+
if (globalContent.trim()) {
|
|
85
|
+
combinedContent = globalContent.trim();
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// Load project constitution
|
|
91
|
+
const projectPath = join(projectDir, config.path);
|
|
92
|
+
if (existsSync(projectPath)) {
|
|
93
|
+
// SEC-5: Validate path before reading
|
|
94
|
+
const validatedPath = validateFilePath(projectPath, projectDir);
|
|
95
|
+
const projectFile = Bun.file(validatedPath);
|
|
96
|
+
const projectContent = await projectFile.text();
|
|
97
|
+
if (projectContent.trim()) {
|
|
98
|
+
// Concatenate with separator if both exist
|
|
99
|
+
if (combinedContent) {
|
|
100
|
+
combinedContent += `\n\n---\n\n${projectContent.trim()}`;
|
|
101
|
+
} else {
|
|
102
|
+
// If no global content, preserve exact project content (including trailing newline)
|
|
103
|
+
combinedContent = projectContent;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// Return null if no content loaded
|
|
109
|
+
if (!combinedContent) {
|
|
110
|
+
return null;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const tokens = estimateTokens(combinedContent);
|
|
114
|
+
|
|
115
|
+
if (tokens <= config.maxTokens) {
|
|
116
|
+
return {
|
|
117
|
+
content: combinedContent,
|
|
118
|
+
tokens,
|
|
119
|
+
truncated: false,
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// Truncate to fit within maxTokens
|
|
124
|
+
const truncatedContent = truncateToTokens(combinedContent, config.maxTokens);
|
|
125
|
+
const truncatedTokens = estimateTokens(truncatedContent);
|
|
126
|
+
|
|
127
|
+
return {
|
|
128
|
+
content: truncatedContent,
|
|
129
|
+
tokens: truncatedTokens,
|
|
130
|
+
truncated: true,
|
|
131
|
+
originalTokens: tokens,
|
|
132
|
+
};
|
|
133
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Constitution types
|
|
3
|
+
*
|
|
4
|
+
* The constitution is a project-level governance document that defines coding
|
|
5
|
+
* standards, architectural rules, testing requirements, and forbidden patterns.
|
|
6
|
+
* It gets injected into every agent session prompt.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
/** Constitution configuration */
|
|
10
|
+
export interface ConstitutionConfig {
|
|
11
|
+
/** Enable constitution loading and injection */
|
|
12
|
+
enabled: boolean;
|
|
13
|
+
/** Path to constitution file relative to nax/ directory */
|
|
14
|
+
path: string;
|
|
15
|
+
/** Maximum tokens allowed for constitution content */
|
|
16
|
+
maxTokens: number;
|
|
17
|
+
/** Skip loading global constitution (default: false) */
|
|
18
|
+
skipGlobal?: boolean;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/** Constitution load result */
|
|
22
|
+
export interface ConstitutionResult {
|
|
23
|
+
/** Constitution content (may be truncated) */
|
|
24
|
+
content: string;
|
|
25
|
+
/** Estimated token count */
|
|
26
|
+
tokens: number;
|
|
27
|
+
/** Whether content was truncated */
|
|
28
|
+
truncated: boolean;
|
|
29
|
+
/** Original token count before truncation (if truncated) */
|
|
30
|
+
originalTokens?: number;
|
|
31
|
+
}
|