@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,298 @@
|
|
|
1
|
+
import { appendFileSync } from "node:fs";
|
|
2
|
+
import { type FormatterOptions, type VerbosityMode, formatLogEntry } from "../logging/index.js";
|
|
3
|
+
import { formatConsole, formatJsonl } from "./formatters.js";
|
|
4
|
+
import type { LogEntry, LogLevel, LoggerOptions, StoryLogger } from "./types.js";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Severity ordering for log levels (lower number = more severe)
|
|
8
|
+
*/
|
|
9
|
+
const LOG_LEVEL_PRIORITY: Record<LogLevel, number> = {
|
|
10
|
+
error: 0,
|
|
11
|
+
warn: 1,
|
|
12
|
+
info: 2,
|
|
13
|
+
debug: 3,
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Singleton logger instance
|
|
18
|
+
*/
|
|
19
|
+
let instance: Logger | null = null;
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Structured logger with level gating and dual output (console + JSONL file)
|
|
23
|
+
*
|
|
24
|
+
* @example
|
|
25
|
+
* ```typescript
|
|
26
|
+
* // Initialize logger (usually in CLI entry point)
|
|
27
|
+
* initLogger({ level: "info", filePath: "nax/features/auth/runs/run-123.jsonl" });
|
|
28
|
+
*
|
|
29
|
+
* // Use logger throughout application
|
|
30
|
+
* const logger = getLogger();
|
|
31
|
+
* logger.info("routing", "Task classified", { complexity: "simple" });
|
|
32
|
+
*
|
|
33
|
+
* // Story-scoped logger
|
|
34
|
+
* const storyLogger = logger.withStory("user-auth-001");
|
|
35
|
+
* storyLogger.info("agent.start", "Starting agent session");
|
|
36
|
+
* ```
|
|
37
|
+
*/
|
|
38
|
+
export class Logger {
|
|
39
|
+
private readonly level: LogLevel;
|
|
40
|
+
private readonly filePath?: string;
|
|
41
|
+
private readonly useChalk: boolean;
|
|
42
|
+
private readonly formatterMode?: VerbosityMode;
|
|
43
|
+
private readonly headless: boolean;
|
|
44
|
+
|
|
45
|
+
constructor(options: LoggerOptions) {
|
|
46
|
+
this.level = options.level;
|
|
47
|
+
this.filePath = options.filePath;
|
|
48
|
+
this.useChalk = options.useChalk ?? true;
|
|
49
|
+
this.formatterMode = options.formatterMode;
|
|
50
|
+
this.headless = options.headless ?? false;
|
|
51
|
+
|
|
52
|
+
// Ensure parent directory exists if file path provided
|
|
53
|
+
if (this.filePath) {
|
|
54
|
+
this.initFileDirectory();
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Create parent directory for log file if it doesn't exist
|
|
60
|
+
*/
|
|
61
|
+
private initFileDirectory(): void {
|
|
62
|
+
if (!this.filePath) return;
|
|
63
|
+
|
|
64
|
+
try {
|
|
65
|
+
const dir = this.filePath.substring(0, this.filePath.lastIndexOf("/"));
|
|
66
|
+
if (dir) {
|
|
67
|
+
Bun.spawnSync(["mkdir", "-p", dir]);
|
|
68
|
+
}
|
|
69
|
+
} catch (error) {
|
|
70
|
+
console.error(`[logger] Failed to create log directory: ${error}`);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Check if a log level should be displayed on console
|
|
76
|
+
*/
|
|
77
|
+
private shouldLog(level: LogLevel): boolean {
|
|
78
|
+
return LOG_LEVEL_PRIORITY[level] <= LOG_LEVEL_PRIORITY[this.level];
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Internal log method — writes to console (if level permits) and file (always)
|
|
83
|
+
*/
|
|
84
|
+
private log(level: LogLevel, stage: string, message: string, data?: Record<string, unknown>, storyId?: string): void {
|
|
85
|
+
const entry: LogEntry = {
|
|
86
|
+
timestamp: new Date().toISOString(),
|
|
87
|
+
level,
|
|
88
|
+
stage,
|
|
89
|
+
message,
|
|
90
|
+
...(storyId && { storyId }),
|
|
91
|
+
...(data && { data }),
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
// Console output (level-gated)
|
|
95
|
+
if (this.shouldLog(level)) {
|
|
96
|
+
let consoleOutput: string | null = null;
|
|
97
|
+
|
|
98
|
+
// Use formatter in headless mode if mode is specified
|
|
99
|
+
if (this.headless && this.formatterMode) {
|
|
100
|
+
const formatterOptions: FormatterOptions = {
|
|
101
|
+
mode: this.formatterMode,
|
|
102
|
+
useColor: this.useChalk,
|
|
103
|
+
};
|
|
104
|
+
const formatted = formatLogEntry(entry, formatterOptions);
|
|
105
|
+
if (formatted.shouldDisplay) {
|
|
106
|
+
consoleOutput = formatted.output;
|
|
107
|
+
}
|
|
108
|
+
// If formatter says not to display, consoleOutput stays null
|
|
109
|
+
} else {
|
|
110
|
+
// Default console formatting (existing behavior)
|
|
111
|
+
consoleOutput = this.useChalk ? formatConsole(entry) : this.formatPlainConsole(entry);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// Only log if we have output to display
|
|
115
|
+
if (consoleOutput !== null) {
|
|
116
|
+
console.log(consoleOutput);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// File output (always write all levels)
|
|
121
|
+
if (this.filePath) {
|
|
122
|
+
this.writeToFile(entry);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Plain console format (no chalk) — used when useChalk is false
|
|
128
|
+
*/
|
|
129
|
+
private formatPlainConsole(entry: LogEntry): string {
|
|
130
|
+
const timestamp = new Date(entry.timestamp).toLocaleTimeString("en-US", {
|
|
131
|
+
hour12: false,
|
|
132
|
+
});
|
|
133
|
+
const parts = [`[${timestamp}]`, `[${entry.stage}]`];
|
|
134
|
+
if (entry.storyId) {
|
|
135
|
+
parts.push(`[${entry.storyId}]`);
|
|
136
|
+
}
|
|
137
|
+
parts.push(entry.message);
|
|
138
|
+
let output = parts.join(" ");
|
|
139
|
+
if (entry.data && Object.keys(entry.data).length > 0) {
|
|
140
|
+
output += `\n${JSON.stringify(entry.data, null, 2)}`;
|
|
141
|
+
}
|
|
142
|
+
return output;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Write JSONL line to file (synchronous append)
|
|
147
|
+
*/
|
|
148
|
+
private writeToFile(entry: LogEntry): void {
|
|
149
|
+
if (!this.filePath) return;
|
|
150
|
+
|
|
151
|
+
try {
|
|
152
|
+
const line = `${formatJsonl(entry)}\n`;
|
|
153
|
+
// Use Node.js fs for simple synchronous append
|
|
154
|
+
appendFileSync(this.filePath, line, "utf8");
|
|
155
|
+
} catch (error) {
|
|
156
|
+
console.error(`[logger] Failed to write to log file: ${error}`);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Log an error message
|
|
162
|
+
*/
|
|
163
|
+
error(stage: string, message: string, data?: Record<string, unknown>): void {
|
|
164
|
+
this.log("error", stage, message, data);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Log a warning message
|
|
169
|
+
*/
|
|
170
|
+
warn(stage: string, message: string, data?: Record<string, unknown>): void {
|
|
171
|
+
this.log("warn", stage, message, data);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* Log an info message
|
|
176
|
+
*/
|
|
177
|
+
info(stage: string, message: string, data?: Record<string, unknown>): void {
|
|
178
|
+
this.log("info", stage, message, data);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* Log a debug message
|
|
183
|
+
*/
|
|
184
|
+
debug(stage: string, message: string, data?: Record<string, unknown>): void {
|
|
185
|
+
this.log("debug", stage, message, data);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* Create a story-scoped logger that auto-injects storyId
|
|
190
|
+
*
|
|
191
|
+
* @param storyId - Story identifier to inject into all log calls
|
|
192
|
+
* @returns StoryLogger instance
|
|
193
|
+
*
|
|
194
|
+
* @example
|
|
195
|
+
* ```typescript
|
|
196
|
+
* const logger = getLogger();
|
|
197
|
+
* const storyLogger = logger.withStory("user-auth-001");
|
|
198
|
+
* storyLogger.info("agent.start", "Starting agent"); // storyId auto-added
|
|
199
|
+
* ```
|
|
200
|
+
*/
|
|
201
|
+
withStory(storyId: string): StoryLogger {
|
|
202
|
+
return {
|
|
203
|
+
error: (stage: string, message: string, data?: Record<string, unknown>) =>
|
|
204
|
+
this.log("error", stage, message, data, storyId),
|
|
205
|
+
warn: (stage: string, message: string, data?: Record<string, unknown>) =>
|
|
206
|
+
this.log("warn", stage, message, data, storyId),
|
|
207
|
+
info: (stage: string, message: string, data?: Record<string, unknown>) =>
|
|
208
|
+
this.log("info", stage, message, data, storyId),
|
|
209
|
+
debug: (stage: string, message: string, data?: Record<string, unknown>) =>
|
|
210
|
+
this.log("debug", stage, message, data, storyId),
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* Close logger (cleanup method for shutdown)
|
|
216
|
+
* Note: Bun.write handles file operations automatically, no manual cleanup needed
|
|
217
|
+
*/
|
|
218
|
+
close(): void {
|
|
219
|
+
// No-op: Bun handles file operations internally
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/**
|
|
224
|
+
* Initialize the singleton logger instance
|
|
225
|
+
*
|
|
226
|
+
* @param options - Logger configuration options
|
|
227
|
+
* @throws Error if logger is already initialized
|
|
228
|
+
*
|
|
229
|
+
* @example
|
|
230
|
+
* ```typescript
|
|
231
|
+
* initLogger({
|
|
232
|
+
* level: "info",
|
|
233
|
+
* filePath: "nax/features/auth/runs/2026-02-20T10-30-00Z.jsonl"
|
|
234
|
+
* });
|
|
235
|
+
* ```
|
|
236
|
+
*/
|
|
237
|
+
export function initLogger(options: LoggerOptions): Logger {
|
|
238
|
+
if (instance) {
|
|
239
|
+
throw new Error("Logger already initialized. Call getLogger() to access existing instance.");
|
|
240
|
+
}
|
|
241
|
+
instance = new Logger(options);
|
|
242
|
+
return instance;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
/**
|
|
246
|
+
* Get the singleton logger instance
|
|
247
|
+
*
|
|
248
|
+
* @throws Error if logger has not been initialized
|
|
249
|
+
* @returns Logger instance
|
|
250
|
+
*
|
|
251
|
+
* @example
|
|
252
|
+
* ```typescript
|
|
253
|
+
* const logger = getLogger();
|
|
254
|
+
* logger.info("routing", "Task classified");
|
|
255
|
+
* ```
|
|
256
|
+
*/
|
|
257
|
+
/**
|
|
258
|
+
* No-op logger for tests/environments where logger isn't initialized
|
|
259
|
+
*/
|
|
260
|
+
const noopLogger: Logger = new Logger({ level: "error", useChalk: false, headless: false });
|
|
261
|
+
|
|
262
|
+
export function getLogger(): Logger {
|
|
263
|
+
if (!instance) {
|
|
264
|
+
return noopLogger;
|
|
265
|
+
}
|
|
266
|
+
return instance;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
/**
|
|
270
|
+
* Safely get logger instance, returns null if not initialized
|
|
271
|
+
*
|
|
272
|
+
* @returns Logger instance or null if not initialized
|
|
273
|
+
*
|
|
274
|
+
* @example
|
|
275
|
+
* ```typescript
|
|
276
|
+
* const logger = getSafeLogger();
|
|
277
|
+
* logger?.info("routing", "Task classified");
|
|
278
|
+
* ```
|
|
279
|
+
*/
|
|
280
|
+
export function getSafeLogger(): Logger | null {
|
|
281
|
+
try {
|
|
282
|
+
const logger = getLogger();
|
|
283
|
+
return logger === noopLogger ? null : logger;
|
|
284
|
+
} catch {
|
|
285
|
+
return null;
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
/**
|
|
290
|
+
* Reset logger singleton (for testing only)
|
|
291
|
+
* @internal
|
|
292
|
+
*/
|
|
293
|
+
export function resetLogger(): void {
|
|
294
|
+
if (instance) {
|
|
295
|
+
instance.close();
|
|
296
|
+
}
|
|
297
|
+
instance = null;
|
|
298
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Log level type — ordered from most to least severe
|
|
3
|
+
*/
|
|
4
|
+
export type LogLevel = "error" | "warn" | "info" | "debug";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Structured log entry format
|
|
8
|
+
*/
|
|
9
|
+
export interface LogEntry {
|
|
10
|
+
/** ISO timestamp when log was created */
|
|
11
|
+
timestamp: string;
|
|
12
|
+
/** Severity level */
|
|
13
|
+
level: LogLevel;
|
|
14
|
+
/** Pipeline stage or module name */
|
|
15
|
+
stage: string;
|
|
16
|
+
/** Optional story identifier for context */
|
|
17
|
+
storyId?: string;
|
|
18
|
+
/** Human-readable message */
|
|
19
|
+
message: string;
|
|
20
|
+
/** Optional structured metadata */
|
|
21
|
+
data?: Record<string, unknown>;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Logger initialization options
|
|
26
|
+
*/
|
|
27
|
+
export interface LoggerOptions {
|
|
28
|
+
/** Minimum log level for console output (file gets all levels) */
|
|
29
|
+
level: LogLevel;
|
|
30
|
+
/** Optional path to JSONL log file */
|
|
31
|
+
filePath?: string;
|
|
32
|
+
/** Whether to use chalk for console formatting (default: true) */
|
|
33
|
+
useChalk?: boolean;
|
|
34
|
+
/** Formatter verbosity mode for console output (default: uses formatConsole) */
|
|
35
|
+
formatterMode?: "quiet" | "normal" | "verbose" | "json";
|
|
36
|
+
/** Whether running in headless mode (enables formatter) */
|
|
37
|
+
headless?: boolean;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Story-scoped logger that auto-injects storyId into all log calls
|
|
42
|
+
*/
|
|
43
|
+
export interface StoryLogger {
|
|
44
|
+
error(stage: string, message: string, data?: Record<string, unknown>): void;
|
|
45
|
+
warn(stage: string, message: string, data?: Record<string, unknown>): void;
|
|
46
|
+
info(stage: string, message: string, data?: Record<string, unknown>): void;
|
|
47
|
+
debug(stage: string, message: string, data?: Record<string, unknown>): void;
|
|
48
|
+
}
|
|
@@ -0,0 +1,355 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Human-friendly logging formatter with verbosity levels
|
|
3
|
+
*
|
|
4
|
+
* Transforms JSONL log entries into readable output with emoji indicators
|
|
5
|
+
* and supports multiple verbosity modes: quiet, normal, verbose, json
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import chalk from "chalk";
|
|
9
|
+
import type { LogEntry } from "../logger/types.js";
|
|
10
|
+
import { EMOJI, type FormatterOptions, type RunSummary } from "./types.js";
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Formatted output entry
|
|
14
|
+
*/
|
|
15
|
+
export interface FormattedEntry {
|
|
16
|
+
/** Formatted string ready for console output */
|
|
17
|
+
output: string;
|
|
18
|
+
/** Whether this entry should be shown in the current verbosity mode */
|
|
19
|
+
shouldDisplay: boolean;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Format a timestamp to local timezone HH:MM:SS
|
|
24
|
+
*/
|
|
25
|
+
export function formatTimestamp(isoTimestamp: string): string {
|
|
26
|
+
const date = new Date(isoTimestamp);
|
|
27
|
+
return date.toLocaleTimeString("en-US", {
|
|
28
|
+
hour12: false,
|
|
29
|
+
hour: "2-digit",
|
|
30
|
+
minute: "2-digit",
|
|
31
|
+
second: "2-digit",
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Format duration in milliseconds to human-readable format
|
|
37
|
+
*/
|
|
38
|
+
export function formatDuration(durationMs: number): string {
|
|
39
|
+
if (durationMs < 1000) {
|
|
40
|
+
return `${durationMs}ms`;
|
|
41
|
+
}
|
|
42
|
+
if (durationMs < 60000) {
|
|
43
|
+
return `${(durationMs / 1000).toFixed(1)}s`;
|
|
44
|
+
}
|
|
45
|
+
const minutes = Math.floor(durationMs / 60000);
|
|
46
|
+
const seconds = Math.floor((durationMs % 60000) / 1000);
|
|
47
|
+
return `${minutes}m ${seconds}s`;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Format cost in dollars
|
|
52
|
+
*/
|
|
53
|
+
export function formatCost(cost: number): string {
|
|
54
|
+
return `$${cost.toFixed(4)}`;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Get emoji for stage name
|
|
59
|
+
*/
|
|
60
|
+
function getStageEmoji(stage: string): string {
|
|
61
|
+
if (stage.includes("routing")) return EMOJI.routing;
|
|
62
|
+
if (stage.includes("execution") || stage.includes("agent")) return EMOJI.execution;
|
|
63
|
+
if (stage.includes("review")) return EMOJI.review;
|
|
64
|
+
if (stage.includes("tdd")) return EMOJI.tdd;
|
|
65
|
+
return EMOJI.info;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Check if entry should be displayed based on verbosity mode
|
|
70
|
+
*/
|
|
71
|
+
function shouldDisplay(entry: LogEntry, mode: string): boolean {
|
|
72
|
+
if (mode === "json") return true;
|
|
73
|
+
if (mode === "quiet") {
|
|
74
|
+
// Only show critical events: run start/end, story pass/fail
|
|
75
|
+
return (
|
|
76
|
+
entry.stage === "run.start" ||
|
|
77
|
+
entry.stage === "run.end" ||
|
|
78
|
+
entry.stage === "story.complete" ||
|
|
79
|
+
entry.level === "error"
|
|
80
|
+
);
|
|
81
|
+
}
|
|
82
|
+
if (mode === "verbose") return true;
|
|
83
|
+
|
|
84
|
+
// Normal mode: filter out debug logs
|
|
85
|
+
return entry.level !== "debug";
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Format a log entry for human-readable output
|
|
90
|
+
*
|
|
91
|
+
* Supports different verbosity modes and styling options
|
|
92
|
+
*/
|
|
93
|
+
export function formatLogEntry(entry: LogEntry, options: FormatterOptions): FormattedEntry {
|
|
94
|
+
const { mode, useColor = true } = options;
|
|
95
|
+
|
|
96
|
+
// JSON mode: pass through raw JSONL
|
|
97
|
+
if (mode === "json") {
|
|
98
|
+
return {
|
|
99
|
+
output: JSON.stringify(entry),
|
|
100
|
+
shouldDisplay: true,
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// Check if should display based on mode
|
|
105
|
+
if (!shouldDisplay(entry, mode)) {
|
|
106
|
+
return {
|
|
107
|
+
output: "",
|
|
108
|
+
shouldDisplay: false,
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
const timestamp = formatTimestamp(entry.timestamp);
|
|
113
|
+
const colorize = useColor ? chalk : createNoopChalk();
|
|
114
|
+
|
|
115
|
+
// Handle special stages with custom formatting
|
|
116
|
+
if (entry.stage === "run.start") {
|
|
117
|
+
return formatRunStart(entry, colorize, timestamp, mode);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
if (entry.stage === "story.start" || entry.stage === "iteration.start") {
|
|
121
|
+
return formatStoryStart(entry, colorize, timestamp, mode);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
if (entry.stage === "story.complete" || entry.stage === "agent.complete") {
|
|
125
|
+
return formatStoryComplete(entry, colorize, timestamp, mode);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
if (entry.stage.includes("tdd") && entry.message.startsWith("→ Session:")) {
|
|
129
|
+
return formatTDDSession(entry, colorize, timestamp, mode);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// Default formatting for other entries
|
|
133
|
+
return formatDefault(entry, colorize, timestamp, mode);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Format run start event
|
|
138
|
+
*/
|
|
139
|
+
function formatRunStart(entry: LogEntry, c: ChalkLike, timestamp: string, mode: string): FormattedEntry {
|
|
140
|
+
const data = entry.data as Record<string, unknown>;
|
|
141
|
+
const lines: string[] = [];
|
|
142
|
+
|
|
143
|
+
lines.push("");
|
|
144
|
+
lines.push(c.bold(c.blue("═".repeat(60))));
|
|
145
|
+
lines.push(c.bold(c.blue(` ${EMOJI.storyStart} NAX RUN STARTED`)));
|
|
146
|
+
lines.push(c.blue("═".repeat(60)));
|
|
147
|
+
lines.push(` ${c.gray("Time:")} ${timestamp}`);
|
|
148
|
+
lines.push(` ${c.gray("Feature:")} ${c.cyan(String(data.feature || "unknown"))}`);
|
|
149
|
+
lines.push(` ${c.gray("Run ID:")} ${c.dim(String(data.runId || "unknown"))}`);
|
|
150
|
+
lines.push(` ${c.gray("Workdir:")} ${c.dim(String(data.workdir || "."))}`);
|
|
151
|
+
lines.push(c.blue("═".repeat(60)));
|
|
152
|
+
lines.push("");
|
|
153
|
+
|
|
154
|
+
return {
|
|
155
|
+
output: lines.join("\n"),
|
|
156
|
+
shouldDisplay: true,
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Format story start event
|
|
162
|
+
*/
|
|
163
|
+
function formatStoryStart(entry: LogEntry, c: ChalkLike, timestamp: string, mode: string): FormattedEntry {
|
|
164
|
+
const data = entry.data as Record<string, unknown>;
|
|
165
|
+
const storyId = String(data.storyId || entry.storyId || "unknown");
|
|
166
|
+
const title = String(data.storyTitle || data.title || "Untitled story");
|
|
167
|
+
const complexity = typeof data.complexity === "string" ? data.complexity : "unknown";
|
|
168
|
+
const tier = typeof data.modelTier === "string" ? data.modelTier : "unknown";
|
|
169
|
+
const attempt = typeof data.attempt === "number" ? data.attempt : 1;
|
|
170
|
+
|
|
171
|
+
const lines: string[] = [];
|
|
172
|
+
lines.push("");
|
|
173
|
+
lines.push(c.bold(`${EMOJI.storyStart} ${c.cyan(storyId)}: ${title}`));
|
|
174
|
+
|
|
175
|
+
if (mode === "verbose") {
|
|
176
|
+
lines.push(` ${c.gray("├─")} Complexity: ${c.yellow(complexity)}`);
|
|
177
|
+
lines.push(` ${c.gray("├─")} Tier: ${c.magenta(tier)}`);
|
|
178
|
+
if (attempt > 1) {
|
|
179
|
+
lines.push(` ${c.gray("└─")} Attempt: ${c.yellow(`#${attempt}`)} ${EMOJI.retry}`);
|
|
180
|
+
} else {
|
|
181
|
+
lines.push(` ${c.gray("└─")} Status: ${c.green("starting")}`);
|
|
182
|
+
}
|
|
183
|
+
} else {
|
|
184
|
+
const metadata = [complexity, tier];
|
|
185
|
+
if (attempt > 1) metadata.push(`attempt #${attempt} ${EMOJI.retry}`);
|
|
186
|
+
lines.push(` ${c.gray(metadata.join(" • "))}`);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
return {
|
|
190
|
+
output: lines.join("\n"),
|
|
191
|
+
shouldDisplay: true,
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* Format story completion event
|
|
197
|
+
*/
|
|
198
|
+
function formatStoryComplete(entry: LogEntry, c: ChalkLike, timestamp: string, mode: string): FormattedEntry {
|
|
199
|
+
const data = entry.data as Record<string, unknown>;
|
|
200
|
+
const storyId = String(data.storyId || entry.storyId || "unknown");
|
|
201
|
+
const success = data.success ?? true;
|
|
202
|
+
const cost =
|
|
203
|
+
typeof data.cost === "number" ? data.cost : typeof data.estimatedCost === "number" ? data.estimatedCost : 0;
|
|
204
|
+
const duration = typeof data.durationMs === "number" ? data.durationMs : 0;
|
|
205
|
+
const action = data.finalAction || data.action;
|
|
206
|
+
|
|
207
|
+
const emoji = success ? EMOJI.success : action === "escalate" ? EMOJI.retry : EMOJI.failure;
|
|
208
|
+
const statusColor = success ? c.green : action === "escalate" ? c.yellow : c.red;
|
|
209
|
+
const status = success ? "PASSED" : action === "escalate" ? "ESCALATED" : "FAILED";
|
|
210
|
+
|
|
211
|
+
const lines: string[] = [];
|
|
212
|
+
lines.push(statusColor(` ${emoji} ${c.bold(storyId)}: ${status}`));
|
|
213
|
+
|
|
214
|
+
if (mode === "verbose" || mode === "normal") {
|
|
215
|
+
const metadata: string[] = [];
|
|
216
|
+
if (cost > 0) metadata.push(`${EMOJI.cost} ${formatCost(cost)}`);
|
|
217
|
+
if (duration > 0) metadata.push(`${EMOJI.duration} ${formatDuration(duration)}`);
|
|
218
|
+
if (metadata.length > 0) {
|
|
219
|
+
lines.push(` ${c.gray(metadata.join(" "))}`);
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
if (mode === "verbose" && data.reason) {
|
|
224
|
+
lines.push(` ${c.gray(`Reason: ${data.reason}`)}`);
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
lines.push("");
|
|
228
|
+
|
|
229
|
+
return {
|
|
230
|
+
output: lines.join("\n"),
|
|
231
|
+
shouldDisplay: true,
|
|
232
|
+
};
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
/**
|
|
236
|
+
* Format TDD session start
|
|
237
|
+
*/
|
|
238
|
+
function formatTDDSession(entry: LogEntry, c: ChalkLike, timestamp: string, mode: string): FormattedEntry {
|
|
239
|
+
if (mode === "quiet") {
|
|
240
|
+
return { output: "", shouldDisplay: false };
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
const data = entry.data as Record<string, unknown>;
|
|
244
|
+
const role = typeof data.role === "string" ? data.role : "unknown";
|
|
245
|
+
const roleLabel = role.replace(/-/g, " ").replace(/\b\w/g, (l: string) => l.toUpperCase());
|
|
246
|
+
|
|
247
|
+
return {
|
|
248
|
+
output: ` ${c.gray("│")} ${EMOJI.tdd} ${c.cyan(roleLabel)}`,
|
|
249
|
+
shouldDisplay: true,
|
|
250
|
+
};
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
/**
|
|
254
|
+
* Format default log entry
|
|
255
|
+
*/
|
|
256
|
+
function formatDefault(entry: LogEntry, c: ChalkLike, timestamp: string, mode: string): FormattedEntry {
|
|
257
|
+
const levelEmoji = entry.level === "error" ? EMOJI.failure : entry.level === "warn" ? EMOJI.warning : EMOJI.info;
|
|
258
|
+
const levelColor = entry.level === "error" ? c.red : entry.level === "warn" ? c.yellow : c.gray;
|
|
259
|
+
const stageEmoji = getStageEmoji(entry.stage);
|
|
260
|
+
|
|
261
|
+
const parts = [c.gray(`[${timestamp}]`), levelColor(`${levelEmoji} ${entry.stage}`)];
|
|
262
|
+
|
|
263
|
+
if (entry.storyId) {
|
|
264
|
+
parts.push(c.dim(`[${entry.storyId}]`));
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
parts.push(entry.message);
|
|
268
|
+
|
|
269
|
+
let output = parts.join(" ");
|
|
270
|
+
|
|
271
|
+
// Include data in verbose mode
|
|
272
|
+
if (mode === "verbose" && entry.data && Object.keys(entry.data).length > 0) {
|
|
273
|
+
output += `\n${c.gray(JSON.stringify(entry.data, null, 2))}`;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
return {
|
|
277
|
+
output,
|
|
278
|
+
shouldDisplay: true,
|
|
279
|
+
};
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
/**
|
|
283
|
+
* Format run summary footer
|
|
284
|
+
*/
|
|
285
|
+
export function formatRunSummary(summary: RunSummary, options: FormatterOptions): string {
|
|
286
|
+
const { mode, useColor = true } = options;
|
|
287
|
+
|
|
288
|
+
if (mode === "json") {
|
|
289
|
+
return JSON.stringify(summary);
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
const c = useColor ? chalk : createNoopChalk();
|
|
293
|
+
const lines: string[] = [];
|
|
294
|
+
|
|
295
|
+
lines.push("");
|
|
296
|
+
lines.push(c.blue("═".repeat(60)));
|
|
297
|
+
lines.push(c.bold(c.blue(` ${EMOJI.storyComplete} RUN SUMMARY`)));
|
|
298
|
+
lines.push(c.blue("═".repeat(60)));
|
|
299
|
+
|
|
300
|
+
const successRate = summary.total > 0 ? ((summary.passed / summary.total) * 100).toFixed(1) : "0.0";
|
|
301
|
+
const statusColor = summary.failed === 0 ? c.green : summary.passed > summary.failed ? c.yellow : c.red;
|
|
302
|
+
|
|
303
|
+
lines.push(` ${c.gray("Total:")} ${c.bold(summary.total.toString())}`);
|
|
304
|
+
lines.push(` ${c.green(`${EMOJI.success} Passed:`)} ${c.bold(summary.passed.toString())}`);
|
|
305
|
+
|
|
306
|
+
if (summary.failed > 0) {
|
|
307
|
+
lines.push(` ${c.red(`${EMOJI.failure} Failed:`)} ${c.bold(summary.failed.toString())}`);
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
if (summary.skipped > 0) {
|
|
311
|
+
lines.push(` ${c.yellow(`${EMOJI.skip} Skipped:`)} ${c.bold(summary.skipped.toString())}`);
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
lines.push(` ${c.gray("Success:")} ${statusColor(c.bold(`${successRate}%`))}`);
|
|
315
|
+
lines.push(c.blue("─".repeat(60)));
|
|
316
|
+
lines.push(` ${EMOJI.duration} Duration: ${c.bold(formatDuration(summary.durationMs))}`);
|
|
317
|
+
lines.push(` ${EMOJI.cost} Cost: ${c.bold(formatCost(summary.totalCost))}`);
|
|
318
|
+
lines.push(c.blue("═".repeat(60)));
|
|
319
|
+
lines.push("");
|
|
320
|
+
|
|
321
|
+
return lines.join("\n");
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
/**
|
|
325
|
+
* Chalk-like interface for no-op mode (no colors)
|
|
326
|
+
*/
|
|
327
|
+
interface ChalkLike {
|
|
328
|
+
bold: (s: string) => string;
|
|
329
|
+
dim: (s: string) => string;
|
|
330
|
+
gray: (s: string) => string;
|
|
331
|
+
red: (s: string) => string;
|
|
332
|
+
green: (s: string) => string;
|
|
333
|
+
yellow: (s: string) => string;
|
|
334
|
+
blue: (s: string) => string;
|
|
335
|
+
magenta: (s: string) => string;
|
|
336
|
+
cyan: (s: string) => string;
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
/**
|
|
340
|
+
* Create a no-op chalk instance (returns strings unchanged)
|
|
341
|
+
*/
|
|
342
|
+
function createNoopChalk(): ChalkLike {
|
|
343
|
+
const noop = (s: string) => s;
|
|
344
|
+
return {
|
|
345
|
+
bold: noop,
|
|
346
|
+
dim: noop,
|
|
347
|
+
gray: noop,
|
|
348
|
+
red: noop,
|
|
349
|
+
green: noop,
|
|
350
|
+
yellow: noop,
|
|
351
|
+
blue: noop,
|
|
352
|
+
magenta: noop,
|
|
353
|
+
cyan: noop,
|
|
354
|
+
};
|
|
355
|
+
}
|