diff --git a/scripts/architecture-baseline.json b/scripts/architecture-baseline.json index 3d1514d..a9325d5 100644 --- a/scripts/architecture-baseline.json +++ b/scripts/architecture-baseline.json @@ -2,10 +2,7 @@ "version": 1, "violations": [ "clone 003d2210c257 src/rsi/__tests__/controller-candidates.test.ts,src/rsi/__tests__/controller-fleet-results.test.ts", - "clone 00803486e758 src/engine/__tests__/loop-helpers-history.test.ts,src/engine/__tests__/loop-helpers-readonly.test.ts,src/engine/__tests__/parser.test.ts,src/engine/__tests__/summarize-tool-arg.test.ts,src/memory/__tests__/embed.test.ts,src/memory/__tests__/summarizer.test.ts", "clone 00815c1c8497 src/rsi/__tests__/controller-candidates.test.ts,src/rsi/__tests__/controller-integrity.test.ts", - "clone 0081a7d462ab src/orchestrator/__tests__/cost-history-cli.test.ts,src/orchestrator/__tests__/cost-history.test.ts", - "clone 0152e2318e4e src/engine/__tests__/loop-helpers-history.test.ts,src/engine/__tests__/loop-helpers-readonly.test.ts,src/engine/__tests__/new-task.test.ts,src/engine/__tests__/parser.test.ts,src/engine/__tests__/summarize-tool-arg.test.ts,src/engine/__tests__/switch-mode.test.ts", "clone 01a56a433e82 src/orchestrator/task-files.ts", "clone 01e5839d0892 src/permissions/commands.ts", "clone 0288db0ddefd src/orchestrator/cli.ts", @@ -13,14 +10,12 @@ "clone 0338cd3dd229 src/orchestrator/__tests__/cli.test.ts", "clone 03d1744fcd1c src/orchestrator/task-files.ts", "clone 04288103b140 src/dashboard/__tests__/server.test.ts,src/dashboard/__tests__/session-launch.test.ts", - "clone 044be972ef45 src/codesearch/__tests__/backend-selection.test.ts,src/codesearch/__tests__/index.test.ts,src/codesearch/__tests__/search.test.ts,src/config/__tests__/mode-models.test.ts,src/dashboard/__tests__/file-browser-permissions.test.ts,src/dashboard/__tests__/settings.test.ts,src/orchestrator/__tests__/cli.test.ts,src/orchestrator/__tests__/model-resolution.test.ts,src/orchestrator/__tests__/resume.test.ts,src/orchestrator/__tests__/watch.test.ts,src/permissions/__tests__/protected-files.test.ts,src/tools/__tests__/codebase_search.test.ts", "clone 0502285714dd src/engine/__tests__/loop.test.ts", "clone 0547f20476cc src/rsi/__tests__/postgres-queue.test.ts", "clone 058c180b1b12 src/orchestrator/resume.ts", "clone 05d4573a34dd src/codesearch/embedder.ts,src/memory/embed.ts", "clone 05f9940a16d5 src/decision-proxy/cli.ts,src/orchestrator/cli.ts,src/orchestrator/resume.ts", "clone 0654c838248c src/llm/muse.ts,src/llm/openrouter.ts", - "clone 0665ce33abe0 src/engine/__tests__/loop-helpers-history.test.ts,src/engine/__tests__/loop-helpers-readonly.test.ts,src/engine/__tests__/parser.test.ts,src/engine/__tests__/summarize-tool-arg.test.ts", "clone 06b27f6a49e2 src/orchestrator/__tests__/analyze-cli.test.ts", "clone 06c2ac64ec1e src/engine/__tests__/condense.test.ts", "clone 070bf0935a24 src/engine/loop.ts", @@ -32,11 +27,9 @@ "clone 085a3dc0f084 src/orchestrator/__tests__/spawn-model.test.ts,src/orchestrator/__tests__/spawn-state-reset.test.ts", "clone 08693511f4f2 src/orchestrator/task-files.ts", "clone 08a6c01fdb2d src/orchestrator/__tests__/analyze-cli.test.ts", - "clone 08c495e8fc18 src/orchestrator/__tests__/cost-history-cli.test.ts,src/orchestrator/__tests__/cost-history.test.ts", "clone 093e5318134f src/codesearch/embedder.ts,src/memory/embed.ts", "clone 097bb86875ad src/llm/openrouter.ts", "clone 097dbebdafaf src/github/__tests__/installations.test.ts,src/github/__tests__/pr.test.ts", - "clone 0a607f8c651c src/orchestrator/__tests__/cost-history-cli.test.ts,src/orchestrator/__tests__/cost-history.test.ts", "clone 0a8cd67b23bf src/cloud/__tests__/openshell-provider.test.ts", "clone 0ab505c08776 src/engine/loop.ts", "clone 0aba39a8736e src/checkpoints/cli.ts,src/decision-proxy/cli.ts", @@ -74,27 +67,20 @@ "clone 12dd86c98dc3 src/orchestrator/__tests__/cli.test.ts,src/orchestrator/__tests__/resume.test.ts", "clone 136eebd8cce3 src/dashboard/server.ts", "clone 1405529d1b27 src/engine/__tests__/condense.test.ts,src/engine/__tests__/events.test.ts", - "clone 1418c0249247 src/engine/__tests__/global-modes.test.ts,src/engine/__tests__/prompt.test.ts,src/engine/__tests__/stack-rules-content.test.ts,src/engine/__tests__/stacks.test.ts,src/llm/__tests__/muse.test.ts,src/llm/__tests__/openrouter.test.ts", - "clone 14ae7a9a5f1d src/codesearch/__tests__/backend-selection.test.ts,src/codesearch/__tests__/index.test.ts,src/codesearch/__tests__/search.test.ts,src/config/__tests__/mode-models.test.ts,src/dashboard/__tests__/file-browser-permissions.test.ts,src/dashboard/__tests__/settings.test.ts,src/orchestrator/__tests__/cli.test.ts,src/orchestrator/__tests__/model-resolution.test.ts,src/orchestrator/__tests__/resume.test.ts,src/orchestrator/__tests__/spawn-model.test.ts,src/orchestrator/__tests__/watch.test.ts,src/permissions/__tests__/protected-files.test.ts,src/tools/__tests__/codebase_search.test.ts", "clone 14d7e81b5443 src/checkpoints/__tests__/cli.test.ts,src/codemap/__tests__/cli.test.ts,src/dashboard/__tests__/cli.test.ts", "clone 15440c983d57 src/checkpoints/cli.ts,src/decision-proxy/cli.ts", - "clone 157b83b06b3d src/engine/__tests__/loop-helpers-history.test.ts,src/engine/__tests__/loop-helpers-readonly.test.ts,src/engine/__tests__/parser.test.ts,src/engine/__tests__/summarize-tool-arg.test.ts,src/memory/__tests__/embed.test.ts,src/memory/__tests__/summarizer.test.ts", "clone 15d1e6928f43 src/dashboard/server.ts", "clone 16271d25ae1f src/llm/preflight.ts", "clone 162af6dd3851 src/decision-proxy/__tests__/proxy.test.ts,src/engine/__tests__/switch-mode.test.ts,src/tools/__tests__/codebase_search.test.ts,src/tools/__tests__/execute_command.test.ts", "clone 162cb5b72716 src/memory/__tests__/memory-wiring.test.ts,src/tools/__tests__/decision.test.ts", - "clone 168bc796174d src/codeintel/__tests__/registration.test.ts,src/tools/__tests__/batch.test.ts", "clone 16b63330e573 src/orchestrator/task-files.ts", "clone 16baec87b0bf src/orchestrator/task-files.ts", - "clone 17203871d0e8 src/orchestrator/__tests__/cost-history-cli.test.ts,src/orchestrator/__tests__/cost-history.test.ts", "clone 17924b477eb9 src/orchestrator/cli.ts", "clone 180e79a01e9e src/dashboard/__tests__/server.test.ts,src/dashboard/__tests__/session-launch.test.ts", "clone 18209827df5b src/llm/preflight.ts", - "clone 182bd02a4dda src/__tests__/cli-env.test.ts,src/codesearch/__tests__/embedder-types.test.ts,src/evidence/__tests__/verify-files.test.ts,src/orchestrator/__tests__/task-files.test.ts,src/orchestrator/__tests__/terminal-status.test.ts,src/tools/__tests__/batch-types.test.ts,src/tools/__tests__/paths.test.ts", "clone 1858558ef1ae src/engine/__tests__/new-task.test.ts,src/engine/__tests__/switch-mode.test.ts", "clone 1918e1837e10 src/orchestrator/task-files.ts", "clone 191b28b096c9 src/dashboard/server.ts", - "clone 19a8c1d20a2f src/codesearch/__tests__/backend-selection.test.ts,src/codesearch/__tests__/index.test.ts,src/codesearch/__tests__/search.test.ts,src/config/__tests__/mode-models.test.ts,src/dashboard/__tests__/file-browser-permissions.test.ts,src/dashboard/__tests__/settings.test.ts,src/orchestrator/__tests__/cli.test.ts,src/orchestrator/__tests__/model-resolution.test.ts,src/orchestrator/__tests__/resume.test.ts,src/orchestrator/__tests__/watch.test.ts,src/permissions/__tests__/protected-files.test.ts,src/tools/__tests__/codebase_search.test.ts", "clone 19c4c40edbae src/engine/__tests__/loop.test.ts", "clone 19d774f4b47a src/engine/__tests__/new-task.test.ts,src/engine/__tests__/switch-mode.test.ts", "clone 19de83260e74 src/engine/__tests__/loop.test.ts", @@ -107,7 +93,6 @@ "clone 1b2b48c0199b src/orchestrator/cli.ts,src/watcher/cli.ts", "clone 1b404c3cbfae src/llm/__tests__/muse.test.ts,src/llm/__tests__/openrouter.test.ts", "clone 1b48355447df src/llm/__tests__/muse.test.ts,src/llm/__tests__/openrouter.test.ts", - "clone 1b759012c29a src/checkpoints/__tests__/cli.test.ts,src/codemap/__tests__/cli.test.ts,src/codemap/__tests__/units.test.ts,src/dashboard/__tests__/cli.test.ts,src/dashboard/__tests__/page-route.test.ts,src/dashboard/__tests__/self-improvement-metrics.test.ts,src/dashboard/__tests__/sigint-shutdown.test.ts,src/github/__tests__/installations.test.ts,src/orchestrator/__tests__/analyze-cli.test.ts,src/orchestrator/__tests__/cost-estimate.test.ts,src/orchestrator/__tests__/log-analysis.test.ts,src/orchestrator/__tests__/spawn-state-reset.test.ts,src/orchestrator/__tests__/status.test.ts,src/run-tests-script.test.ts,src/tools/__tests__/set-indentation.test.ts", "clone 1bac564acc3f src/engine/loop.ts", "clone 1c3546eef909 src/orchestrator/__tests__/cli.test.ts", "clone 1c4bbb9846bf src/orchestrator/task-files.ts", @@ -115,10 +100,8 @@ "clone 1c95522b17e5 src/dashboard/__tests__/projects-routes.test.ts,src/projects/__tests__/cli.test.ts", "clone 1cb8d4dbc25b src/codeintel/__tests__/call-graph.test.ts,src/codeintel/__tests__/outline.test.ts,src/codeintel/__tests__/registration.test.ts", "clone 1d93450ce46b src/dashboard/server.ts", - "clone 1daf4022de6f src/codeintel/__tests__/implementations.test.ts,src/orchestrator/__tests__/review-gates.test.ts", "clone 1e9f5c07bbb1 src/dashboard/__tests__/cost-history-routes.test.ts,src/orchestrator/__tests__/cost-history-cli.test.ts", "clone 1ea2ad860baf src/codesearch/files.ts,src/engine/stacks.ts", - "clone 1ec0943d340c src/engine/__tests__/global-modes.test.ts,src/engine/__tests__/stack-rules-content.test.ts", "clone 1ec65e3ed9d8 src/orchestrator/task-files.ts", "clone 1ed752dc9308 src/engine/local-explore.ts,src/llm/ollama.ts", "clone 1ee2355c21d5 src/llm/openrouter.ts", @@ -135,24 +118,18 @@ "clone 21fd3bfb944d src/dashboard/__tests__/cost-history-routes.test.ts,src/orchestrator/__tests__/cost-history-cli.test.ts", "clone 224a1f3d5365 src/orchestrator/resume.ts", "clone 22f7d06a0abd src/rsi/__tests__/controller-candidates.test.ts,src/rsi/__tests__/controller-fleet-results.test.ts", - "clone 230967798bac src/engine/__tests__/global-modes.test.ts,src/engine/__tests__/prompt.test.ts,src/engine/__tests__/stack-rules-content.test.ts,src/engine/__tests__/stacks.test.ts,src/llm/__tests__/muse.test.ts,src/llm/__tests__/openrouter.test.ts,src/stack-rules-content.test.ts", "clone 231bc945ff50 src/control/emergency-controller.ts", "clone 232f1b0a8918 src/engine/__tests__/loop.test.ts", "clone 2359b270d07f src/orchestrator/cli.ts", - "clone 23c11a917a3b src/codeintel/__tests__/implementations.test.ts,src/orchestrator/__tests__/review-gates.test.ts", - "clone 23ccd9c01538 src/__tests__/cli-env.test.ts,src/codesearch/__tests__/embedder-types.test.ts,src/evidence/__tests__/verify-files.test.ts,src/orchestrator/__tests__/task-files.test.ts,src/orchestrator/__tests__/terminal-status.test.ts,src/tools/__tests__/batch-types.test.ts,src/tools/__tests__/paths.test.ts", "clone 243c8df9399b src/engine/__tests__/switch-mode.test.ts", "clone 244ddb70203e src/dashboard/server.ts", "clone 245b47905e93 src/tools/__tests__/edit_file.test.ts,src/tools/__tests__/search_replace.test.ts", "clone 24948eb63643 src/checkpoints/__tests__/cli.test.ts,src/codemap/__tests__/cli.test.ts,src/dashboard/__tests__/cli.test.ts", "clone 24a65346f0d9 src/orchestrator/task-files.ts", - "clone 252a9b7735d0 src/codeintel/__tests__/implementations.test.ts,src/orchestrator/__tests__/review-gates.test.ts", "clone 25a39d706027 src/orchestrator/__tests__/cli.test.ts", "clone 25d503f4631a src/orchestrator/__tests__/reviewer.test.ts,src/qa/__tests__/qa.test.ts", "clone 2603e91a093c src/llm/preflight.ts", "clone 262737545dd9 src/cloud/docker-provider.ts,src/cloud/provider.ts", - "clone 2691a8885b90 src/orchestrator/__tests__/cost-history-cli.test.ts,src/orchestrator/__tests__/cost-history.test.ts", - "clone 271ce8cbe5bc src/codeintel/__tests__/registration.test.ts,src/tools/__tests__/batch.test.ts", "clone 27804cea573f src/codesearch/__tests__/airunner-embedder.test.ts,src/codesearch/__tests__/ollama-embedder.test.ts", "clone 27f1585b3e2a src/orchestrator/cli.ts", "clone 27f5663efaad src/orchestrator/task-files.ts", @@ -175,11 +152,9 @@ "clone 2c9f8493e38e src/llm/muse.ts,src/llm/openrouter.ts", "clone 2cb1493c835d src/orchestrator/__tests__/main-tree-tripwire.test.ts,src/orchestrator/__tests__/review-gates.test.ts", "clone 2e4f5c004508 src/init/__tests__/cli.test.ts,src/orchestrator/__tests__/cost-history-cli.test.ts", - "clone 2eefefd492bd src/checkpoints/__tests__/cli.test.ts,src/codemap/__tests__/cli.test.ts,src/codemap/__tests__/units.test.ts,src/dashboard/__tests__/cli.test.ts,src/dashboard/__tests__/page-route.test.ts,src/dashboard/__tests__/self-improvement-metrics.test.ts,src/dashboard/__tests__/sigint-shutdown.test.ts,src/github/__tests__/installations.test.ts,src/orchestrator/__tests__/analyze-cli.test.ts,src/orchestrator/__tests__/cost-estimate.test.ts,src/orchestrator/__tests__/log-analysis.test.ts,src/orchestrator/__tests__/spawn-state-reset.test.ts,src/orchestrator/__tests__/status.test.ts,src/run-tests-script.test.ts,src/tools/__tests__/set-indentation.test.ts", "clone 2ef48d0cbf0f src/engine/__tests__/new-task.test.ts,src/engine/__tests__/switch-mode.test.ts", "clone 2efe67471984 src/orchestrator/cli.ts", "clone 2f137ce3a1d0 src/llm/muse.ts,src/llm/openrouter.ts", - "clone 2f29adde55ae src/engine/__tests__/global-modes.test.ts,src/engine/__tests__/prompt.test.ts,src/engine/__tests__/stack-rules-content.test.ts,src/engine/__tests__/stacks.test.ts,src/llm/__tests__/muse.test.ts,src/llm/__tests__/openrouter.test.ts", "clone 2f4521043a87 src/engine/__tests__/condense.test.ts", "clone 2fc443c44382 src/orchestrator/cli.ts", "clone 30244c9090b3 src/checkpoints/cli.ts,src/cli.ts,src/codemap/cli.ts,src/codesearch/cli.ts,src/dashboard/cli.ts,src/dashboard/trend-cli.ts,src/decision-proxy/cli.ts,src/github/cli.ts,src/init/cli.ts,src/orchestrator/cleanup.ts,src/watcher/cli.ts", @@ -192,7 +167,6 @@ "clone 3319637fc066 src/engine/local-explore.ts,src/llm/ollama.ts", "clone 3341cac7ce47 src/orchestrator/task-files.ts", "clone 338cc9087bf2 src/dashboard/__tests__/cost-history-routes.test.ts,src/orchestrator/__tests__/cost-history-cli.test.ts,src/orchestrator/__tests__/cost-history.test.ts", - "clone 3390241b01d9 src/budget/__tests__/budget.test.ts,src/budget/__tests__/concurrency.test.ts,src/budget/__tests__/cost.test.ts,src/checkpoints/__tests__/service.test.ts,src/cloud/__tests__/provider.test.ts,src/codeintel/__tests__/call-graph.test.ts,src/codeintel/__tests__/call-hierarchy.test.ts,src/codeintel/__tests__/find-references.test.ts,src/codeintel/__tests__/go-to-definition.test.ts,src/codeintel/__tests__/hover.test.ts,src/codeintel/__tests__/import-graph.test.ts,src/codeintel/__tests__/outline.test.ts,src/codeintel/__tests__/rename-symbol.test.ts,src/codemap/__tests__/build.test.ts,src/codesearch/__tests__/airunner-embedder.test.ts,src/codesearch/__tests__/chunk.test.ts,src/codesearch/__tests__/files.test.ts,src/codesearch/__tests__/ollama-embedder.test.ts,src/dashboard/__tests__/aggregate.test.ts,src/dashboard/__tests__/chat-thread.test.ts,src/dashboard/__tests__/checkpoints-routes.test.ts,src/dashboard/__tests__/codemap-routes.test.ts,src/dashboard/__tests__/cost-history-routes.test.ts,src/dashboard/__tests__/events.test.ts,src/dashboard/__tests__/projects-routes.test.ts,src/dashboard/__tests__/server.test.ts,src/dashboard/__tests__/session-launch.test.ts,src/dashboard/__tests__/timeline.test.ts,src/deploy/__tests__/gate.test.ts,src/engine/__tests__/condense-async.test.ts,src/engine/__tests__/condense.test.ts,src/engine/__tests__/events.test.ts,src/engine/__tests__/inject-message.test.ts,src/engine/__tests__/local-explore.test.ts,src/engine/__tests__/loop.test.ts,src/engine/__tests__/new-task.test.ts,src/engine/__tests__/pause.test.ts,src/engine/__tests__/switch-mode.test.ts,src/github/__tests__/app-auth.test.ts,src/github/__tests__/cli.test.ts,src/github/__tests__/pr.test.ts,src/github/__tests__/provision.test.ts,src/github/__tests__/push.test.ts,src/index-util.test.ts,src/init/__tests__/cli.test.ts,src/init/__tests__/gitignore.test.ts,src/memory/__tests__/local.test.ts,src/memory/__tests__/memory-wiring.test.ts,src/memory/__tests__/uwuchat.test.ts,src/migrate/cli.test.ts,src/orchestrator/__tests__/arg-cursor.test.ts,src/orchestrator/__tests__/auto-split.test.ts,src/orchestrator/__tests__/cleanup.test.ts,src/orchestrator/__tests__/git-sync.test.ts,src/orchestrator/__tests__/merge-check.test.ts,src/orchestrator/__tests__/pipeline.test.ts,src/orchestrator/__tests__/reviewer.test.ts,src/orchestrator/__tests__/split.test.ts,src/orchestrator/__tests__/state.test.ts,src/orchestrator/__tests__/status-cli.test.ts,src/orchestrator/__tests__/stop-cli.test.ts,src/permissions/__tests__/commands.test.ts,src/permissions/__tests__/store-protection.test.ts,src/project-store.test.ts,src/projects/__tests__/cli.test.ts,src/qa/__tests__/qa.test.ts,src/tools/__tests__/apply_diff.test.ts,src/tools/__tests__/browser-action.test.ts,src/tools/__tests__/decision.test.ts,src/tools/__tests__/describe_image.test.ts,src/tools/__tests__/edit_file.test.ts,src/tools/__tests__/execute_command.test.ts,src/tools/__tests__/language-detect.test.ts,src/tools/__tests__/list-files.test.ts,src/tools/__tests__/local-summarization-gate.test.ts,src/tools/__tests__/output-summarizer.test.ts,src/tools/__tests__/read-cache.test.ts,src/tools/__tests__/run-tests.test.ts,src/tools/__tests__/search_replace.test.ts,src/tools/__tests__/update_todo_list.test.ts,src/tools/__tests__/write_to_file.test.ts,src/vision/__tests__/describe.test.ts,src/watcher/__tests__/github.test.ts,src/watcher/__tests__/state.test.ts,src/watcher/__tests__/watch.test.ts", "clone 33c630390d21 src/rsi/__tests__/controller-candidates.test.ts,src/rsi/__tests__/controller-integrity.test.ts", "clone 33e68b8dfd03 src/llm/muse.ts,src/llm/openrouter.ts", "clone 3437313b4dfa src/llm/__tests__/muse.test.ts,src/llm/__tests__/openrouter.test.ts", @@ -229,11 +203,8 @@ "clone 3ee06defdfda src/llm/preflight.ts", "clone 3f80ca564a0a src/orchestrator/cli.ts,src/orchestrator/resume.ts", "clone 3fb76f830d07 src/engine/__tests__/inject-message.test.ts,src/engine/__tests__/loop.test.ts,src/engine/__tests__/pause.test.ts", - "clone 3fc784cdd83c src/__tests__/cli-env.test.ts,src/codesearch/__tests__/embedder-types.test.ts,src/evidence/__tests__/verify-files.test.ts,src/orchestrator/__tests__/task-files.test.ts,src/orchestrator/__tests__/terminal-status.test.ts,src/tools/__tests__/batch-types.test.ts,src/tools/__tests__/paths.test.ts", - "clone 3fd94d4af296 src/codeintel/__tests__/registration.test.ts,src/tools/__tests__/batch.test.ts", "clone 3fe29973675e src/orchestrator/resume.ts", "clone 404168195169 src/engine/loop.ts", - "clone 409d3c6aa64e src/engine/__tests__/new-task.test.ts,src/engine/__tests__/switch-mode.test.ts", "clone 40d56ba05f73 src/engine/__tests__/loop.test.ts,src/tools/__tests__/execute_command.test.ts", "clone 40df3509282c src/engine/__tests__/new-task.test.ts,src/engine/__tests__/switch-mode.test.ts", "clone 40e273ce917f src/engine/local-explore.ts,src/llm/ollama.ts", @@ -248,14 +219,12 @@ "clone 42dd6d326705 src/llm/__tests__/muse.test.ts,src/llm/__tests__/openrouter.test.ts", "clone 4372b69d7f78 src/orchestrator/resume.ts", "clone 43b0c11b7347 src/engine/loop.ts", - "clone 44848edc370f src/engine/__tests__/new-task.test.ts,src/engine/__tests__/switch-mode.test.ts", "clone 4494882fe028 src/llm/preflight.ts", "clone 44999a278fcd src/orchestrator/resume.ts", "clone 44d299e7bc4c src/dashboard/server.ts", "clone 44f4c5f6148f src/cloud/__tests__/openshell-provider.test.ts", "clone 45181f328e82 src/orchestrator/task-files.ts", "clone 45edfde1d33f src/orchestrator/task-files.ts", - "clone 464303e3e594 src/codeintel/__tests__/implementations.test.ts,src/orchestrator/__tests__/main-tree-tripwire.test.ts,src/orchestrator/__tests__/review-gates.test.ts", "clone 465b90f368f7 src/dashboard/__tests__/server.test.ts,src/dashboard/__tests__/session-launch.test.ts", "clone 4666d04d2662 src/llm/muse.ts,src/llm/openrouter.ts", "clone 46ffdeff854a src/orchestrator/resume.ts", @@ -279,7 +248,6 @@ "clone 4c52d635b095 src/cloud/docker-provider.ts,src/cloud/provider.ts", "clone 4d4b7ced132f src/orchestrator/cli.ts", "clone 4d70161ff45e src/checkpoints/cli.ts,src/decision-proxy/cli.ts", - "clone 4dd292628b52 src/codeintel/__tests__/registration.test.ts,src/tools/__tests__/batch.test.ts", "clone 4dee6b8a0f46 src/llm/preflight.ts", "clone 4e55134d37dc src/orchestrator/cli.ts,src/orchestrator/resume.ts", "clone 4eaaac6576f7 src/dashboard/__tests__/cost-history-routes.test.ts,src/orchestrator/__tests__/cost-history-cli.test.ts", @@ -345,24 +313,18 @@ "clone 5e6943aa1599 src/dashboard/__tests__/session-launch.test.ts", "clone 5e9eb7a40cd3 src/engine/__tests__/condense.test.ts", "clone 5f09b011f3c9 src/engine/__tests__/condense.test.ts", - "clone 5f2c88e38fbe src/budget/__tests__/budget.test.ts,src/budget/__tests__/concurrency.test.ts,src/budget/__tests__/cost.test.ts,src/checkpoints/__tests__/service.test.ts,src/cloud/__tests__/provider.test.ts,src/codesearch/__tests__/airunner-embedder.test.ts,src/codesearch/__tests__/chunk.test.ts,src/codesearch/__tests__/files.test.ts,src/codesearch/__tests__/ollama-embedder.test.ts,src/dashboard/__tests__/aggregate.test.ts,src/dashboard/__tests__/chat-thread.test.ts,src/dashboard/__tests__/checkpoints-routes.test.ts,src/dashboard/__tests__/events.test.ts,src/dashboard/__tests__/projects-routes.test.ts,src/dashboard/__tests__/server.test.ts,src/dashboard/__tests__/session-launch.test.ts,src/dashboard/__tests__/timeline.test.ts,src/deploy/__tests__/gate.test.ts,src/engine/__tests__/condense-async.test.ts,src/engine/__tests__/condense.test.ts,src/engine/__tests__/events.test.ts,src/engine/__tests__/inject-message.test.ts,src/engine/__tests__/loop.test.ts,src/engine/__tests__/new-task.test.ts,src/engine/__tests__/pause.test.ts,src/engine/__tests__/switch-mode.test.ts,src/github/__tests__/app-auth.test.ts,src/github/__tests__/cli.test.ts,src/github/__tests__/pr.test.ts,src/github/__tests__/provision.test.ts,src/github/__tests__/push.test.ts,src/index-util.test.ts,src/init/__tests__/cli.test.ts,src/init/__tests__/gitignore.test.ts,src/memory/__tests__/local.test.ts,src/memory/__tests__/memory-wiring.test.ts,src/memory/__tests__/uwuchat.test.ts,src/migrate/cli.test.ts,src/orchestrator/__tests__/arg-cursor.test.ts,src/orchestrator/__tests__/auto-split.test.ts,src/orchestrator/__tests__/cleanup.test.ts,src/orchestrator/__tests__/git-sync.test.ts,src/orchestrator/__tests__/merge-check.test.ts,src/orchestrator/__tests__/pipeline.test.ts,src/orchestrator/__tests__/reviewer.test.ts,src/orchestrator/__tests__/split.test.ts,src/orchestrator/__tests__/state.test.ts,src/orchestrator/__tests__/status-cli.test.ts,src/orchestrator/__tests__/stop-cli.test.ts,src/permissions/__tests__/commands.test.ts,src/permissions/__tests__/store-protection.test.ts,src/project-store.test.ts,src/projects/__tests__/cli.test.ts,src/qa/__tests__/qa.test.ts,src/tools/__tests__/apply_diff.test.ts,src/tools/__tests__/decision.test.ts,src/tools/__tests__/describe_image.test.ts,src/tools/__tests__/edit_file.test.ts,src/tools/__tests__/execute_command.test.ts,src/tools/__tests__/language-detect.test.ts,src/tools/__tests__/list-files.test.ts,src/tools/__tests__/local-summarization-gate.test.ts,src/tools/__tests__/output-summarizer.test.ts,src/tools/__tests__/path-safety.test.ts,src/tools/__tests__/read-cache.test.ts,src/tools/__tests__/run-tests.test.ts,src/tools/__tests__/search_replace.test.ts,src/tools/__tests__/update_todo_list.test.ts,src/tools/__tests__/write_to_file.test.ts,src/vision/__tests__/describe.test.ts,src/watcher/__tests__/github.test.ts,src/watcher/__tests__/state.test.ts,src/watcher/__tests__/watch.test.ts", "clone 5f2ee4383718 src/github/pr.ts", "clone 5f66e587d856 src/engine/local-explore.ts,src/llm/ollama.ts", "clone 5f6b2a16f10d src/checkpoints/cli.ts,src/cli.ts,src/codemap/cli.ts,src/codesearch/cli.ts,src/dashboard/cli.ts,src/dashboard/trend-cli.ts,src/decision-proxy/cli.ts,src/github/cli.ts,src/init/cli.ts,src/orchestrator/cleanup.ts,src/watcher/cli.ts", "clone 5fff6e3ebed7 src/dashboard/__tests__/cost-history-routes.test.ts,src/orchestrator/__tests__/cost-history-cli.test.ts", "clone 60c4fdaf31bd src/codesearch/airunner-embedder.ts,src/codesearch/ollama-embedder.ts", - "clone 61046dbb6410 src/budget/__tests__/budget.test.ts,src/budget/__tests__/concurrency.test.ts,src/budget/__tests__/cost.test.ts,src/checkpoints/__tests__/service.test.ts,src/cloud/__tests__/provider.test.ts,src/codeintel/__tests__/call-graph.test.ts,src/codeintel/__tests__/call-hierarchy.test.ts,src/codeintel/__tests__/find-references.test.ts,src/codeintel/__tests__/go-to-definition.test.ts,src/codeintel/__tests__/hover.test.ts,src/codeintel/__tests__/import-graph.test.ts,src/codeintel/__tests__/outline.test.ts,src/codeintel/__tests__/rename-symbol.test.ts,src/codemap/__tests__/build.test.ts,src/codesearch/__tests__/airunner-embedder.test.ts,src/codesearch/__tests__/chunk.test.ts,src/codesearch/__tests__/files.test.ts,src/codesearch/__tests__/ollama-embedder.test.ts,src/dashboard/__tests__/aggregate.test.ts,src/dashboard/__tests__/chat-thread.test.ts,src/dashboard/__tests__/checkpoints-routes.test.ts,src/dashboard/__tests__/codemap-routes.test.ts,src/dashboard/__tests__/cost-history-routes.test.ts,src/dashboard/__tests__/events.test.ts,src/dashboard/__tests__/projects-routes.test.ts,src/dashboard/__tests__/server.test.ts,src/dashboard/__tests__/session-launch.test.ts,src/dashboard/__tests__/timeline.test.ts,src/deploy/__tests__/gate.test.ts,src/engine/__tests__/condense-async.test.ts,src/engine/__tests__/condense.test.ts,src/engine/__tests__/events.test.ts,src/engine/__tests__/inject-message.test.ts,src/engine/__tests__/local-explore.test.ts,src/engine/__tests__/loop.test.ts,src/engine/__tests__/new-task.test.ts,src/engine/__tests__/pause.test.ts,src/engine/__tests__/switch-mode.test.ts,src/github/__tests__/app-auth.test.ts,src/github/__tests__/cli.test.ts,src/github/__tests__/pr.test.ts,src/github/__tests__/provision.test.ts,src/github/__tests__/push.test.ts,src/index-util.test.ts,src/init/__tests__/cli.test.ts,src/init/__tests__/gitignore.test.ts,src/memory/__tests__/local.test.ts,src/memory/__tests__/memory-wiring.test.ts,src/memory/__tests__/uwuchat.test.ts,src/migrate/cli.test.ts,src/orchestrator/__tests__/arg-cursor.test.ts,src/orchestrator/__tests__/auto-split.test.ts,src/orchestrator/__tests__/cleanup.test.ts,src/orchestrator/__tests__/git-sync.test.ts,src/orchestrator/__tests__/merge-check.test.ts,src/orchestrator/__tests__/pipeline.test.ts,src/orchestrator/__tests__/reviewer.test.ts,src/orchestrator/__tests__/split.test.ts,src/orchestrator/__tests__/state.test.ts,src/orchestrator/__tests__/status-cli.test.ts,src/orchestrator/__tests__/stop-cli.test.ts,src/permissions/__tests__/commands.test.ts,src/permissions/__tests__/store-protection.test.ts,src/project-store.test.ts,src/projects/__tests__/cli.test.ts,src/qa/__tests__/qa.test.ts,src/tools/__tests__/apply_diff.test.ts,src/tools/__tests__/browser-action.test.ts,src/tools/__tests__/decision.test.ts,src/tools/__tests__/describe_image.test.ts,src/tools/__tests__/edit_file.test.ts,src/tools/__tests__/execute_command.test.ts,src/tools/__tests__/language-detect.test.ts,src/tools/__tests__/list-files.test.ts,src/tools/__tests__/local-summarization-gate.test.ts,src/tools/__tests__/output-summarizer.test.ts,src/tools/__tests__/path-safety.test.ts,src/tools/__tests__/read-cache.test.ts,src/tools/__tests__/run-tests.test.ts,src/tools/__tests__/search_replace.test.ts,src/tools/__tests__/update_todo_list.test.ts,src/tools/__tests__/write_to_file.test.ts,src/vision/__tests__/describe.test.ts,src/watcher/__tests__/github.test.ts,src/watcher/__tests__/state.test.ts,src/watcher/__tests__/watch.test.ts", "clone 61b9caaf613a src/rsi/__tests__/controller-candidates.test.ts,src/rsi/__tests__/controller-fleet-results.test.ts", - "clone 6255fbea917d src/orchestrator/__tests__/cost-history-cli.test.ts,src/orchestrator/__tests__/cost-history.test.ts", - "clone 6287c1413aa4 src/budget/__tests__/budget.test.ts,src/budget/__tests__/concurrency.test.ts,src/budget/__tests__/cost.test.ts,src/checkpoints/__tests__/service.test.ts,src/cloud/__tests__/provider.test.ts,src/codeintel/__tests__/call-graph.test.ts,src/codeintel/__tests__/call-hierarchy.test.ts,src/codeintel/__tests__/find-references.test.ts,src/codeintel/__tests__/go-to-definition.test.ts,src/codeintel/__tests__/hover.test.ts,src/codeintel/__tests__/import-graph.test.ts,src/codeintel/__tests__/outline.test.ts,src/codeintel/__tests__/rename-symbol.test.ts,src/codemap/__tests__/build.test.ts,src/codesearch/__tests__/airunner-embedder.test.ts,src/codesearch/__tests__/chunk.test.ts,src/codesearch/__tests__/files.test.ts,src/codesearch/__tests__/ollama-embedder.test.ts,src/dashboard/__tests__/aggregate.test.ts,src/dashboard/__tests__/chat-thread.test.ts,src/dashboard/__tests__/checkpoints-routes.test.ts,src/dashboard/__tests__/codemap-routes.test.ts,src/dashboard/__tests__/cost-history-routes.test.ts,src/dashboard/__tests__/events.test.ts,src/dashboard/__tests__/projects-routes.test.ts,src/dashboard/__tests__/server.test.ts,src/dashboard/__tests__/session-launch.test.ts,src/dashboard/__tests__/timeline.test.ts,src/deploy/__tests__/gate.test.ts,src/engine/__tests__/condense-async.test.ts,src/engine/__tests__/condense.test.ts,src/engine/__tests__/events.test.ts,src/engine/__tests__/inject-message.test.ts,src/engine/__tests__/local-explore.test.ts,src/engine/__tests__/loop-helpers-history.test.ts,src/engine/__tests__/loop-helpers-readonly.test.ts,src/engine/__tests__/loop.test.ts,src/engine/__tests__/new-task.test.ts,src/engine/__tests__/parser.test.ts,src/engine/__tests__/pause.test.ts,src/engine/__tests__/summarize-tool-arg.test.ts,src/engine/__tests__/switch-mode.test.ts,src/github/__tests__/app-auth.test.ts,src/github/__tests__/cli.test.ts,src/github/__tests__/pr.test.ts,src/github/__tests__/provision.test.ts,src/github/__tests__/push.test.ts,src/index-util.test.ts,src/init/__tests__/cli.test.ts,src/init/__tests__/gitignore.test.ts,src/memory/__tests__/embed.test.ts,src/memory/__tests__/local.test.ts,src/memory/__tests__/memory-wiring.test.ts,src/memory/__tests__/summarizer.test.ts,src/memory/__tests__/uwuchat.test.ts,src/migrate/cli.test.ts,src/orchestrator/__tests__/arg-cursor.test.ts,src/orchestrator/__tests__/auto-split.test.ts,src/orchestrator/__tests__/cleanup.test.ts,src/orchestrator/__tests__/git-sync.test.ts,src/orchestrator/__tests__/merge-check.test.ts,src/orchestrator/__tests__/pipeline.test.ts,src/orchestrator/__tests__/reviewer.test.ts,src/orchestrator/__tests__/split.test.ts,src/orchestrator/__tests__/state.test.ts,src/orchestrator/__tests__/status-cli.test.ts,src/orchestrator/__tests__/stop-cli.test.ts,src/permissions/__tests__/commands.test.ts,src/permissions/__tests__/store-protection.test.ts,src/project-store.test.ts,src/projects/__tests__/cli.test.ts,src/qa/__tests__/qa.test.ts,src/tools/__tests__/apply_diff.test.ts,src/tools/__tests__/browser-action.test.ts,src/tools/__tests__/decision.test.ts,src/tools/__tests__/describe_image.test.ts,src/tools/__tests__/edit_file.test.ts,src/tools/__tests__/execute_command.test.ts,src/tools/__tests__/language-detect.test.ts,src/tools/__tests__/list-files.test.ts,src/tools/__tests__/local-summarization-gate.test.ts,src/tools/__tests__/output-summarizer.test.ts,src/tools/__tests__/read-cache.test.ts,src/tools/__tests__/run-tests.test.ts,src/tools/__tests__/search_replace.test.ts,src/tools/__tests__/update_todo_list.test.ts,src/tools/__tests__/write_to_file.test.ts,src/vision/__tests__/describe.test.ts,src/watcher/__tests__/github.test.ts,src/watcher/__tests__/state.test.ts,src/watcher/__tests__/watch.test.ts", "clone 62d28c84ccfe src/orchestrator/cli.ts", "clone 62fb95ada976 src/rsi/controller.ts", - "clone 62ff58c84315 src/engine/__tests__/loop-helpers-history.test.ts,src/engine/__tests__/loop-helpers-readonly.test.ts,src/engine/__tests__/parser.test.ts,src/engine/__tests__/summarize-tool-arg.test.ts,src/memory/__tests__/embed.test.ts,src/memory/__tests__/summarizer.test.ts,src/stack-rules-content.test.ts", "clone 6376e4c9bfd3 src/github/__tests__/provision.test.ts", "clone 63fb58a6271f src/orchestrator/__tests__/cli.test.ts", "clone 6433777a2a28 src/orchestrator/__tests__/spawn-model.test.ts,src/orchestrator/__tests__/spawn-state-reset.test.ts", "clone 6466c80a5290 src/github/pr.ts", - "clone 6472f40613ee src/codesearch/__tests__/backend-selection.test.ts,src/codesearch/__tests__/index.test.ts,src/codesearch/__tests__/search.test.ts,src/config/__tests__/mode-models.test.ts,src/dashboard/__tests__/file-browser-permissions.test.ts,src/dashboard/__tests__/settings.test.ts,src/orchestrator/__tests__/cli.test.ts,src/orchestrator/__tests__/model-resolution.test.ts,src/orchestrator/__tests__/resume.test.ts,src/orchestrator/__tests__/spawn-model.test.ts,src/orchestrator/__tests__/watch.test.ts,src/permissions/__tests__/protected-files.test.ts,src/tools/__tests__/codebase_search.test.ts", "clone 64820517d48e src/llm/__tests__/muse.test.ts,src/llm/__tests__/openrouter.test.ts", "clone 6489529d68a1 src/dashboard/server.ts", "clone 64a8e2de29d4 src/engine/__tests__/new-task.test.ts,src/engine/__tests__/switch-mode.test.ts", @@ -370,7 +332,6 @@ "clone 655648fae3f0 src/orchestrator/__tests__/cli.test.ts,src/orchestrator/__tests__/resume.test.ts", "clone 659104753a4c src/checkpoints/cli.ts,src/cli.ts,src/codemap/cli.ts,src/codesearch/cli.ts,src/dashboard/cli.ts,src/dashboard/trend-cli.ts,src/decision-proxy/cli.ts,src/github/cli.ts,src/init/cli.ts,src/orchestrator/cleanup.ts,src/watcher/cli.ts", "clone 6592f5270188 src/orchestrator/__tests__/reviewer.test.ts,src/qa/__tests__/qa.test.ts", - "clone 65bf7deda970 src/engine/__tests__/stack-rules-content.test.ts,src/stack-rules-content.test.ts", "clone 65e7527aaf4e src/watcher/watch.ts", "clone 6620858d5a93 src/orchestrator/__tests__/cli.test.ts", "clone 663e0371f2b8 src/engine/__tests__/new-task.test.ts,src/engine/__tests__/switch-mode.test.ts", @@ -380,27 +341,21 @@ "clone 66ec447e84b0 src/engine/__tests__/switch-mode.test.ts", "clone 672782f20b04 src/orchestrator/__tests__/resume.test.ts", "clone 67833e34f406 src/rsi/controller.ts", - "clone 67cd395b3adb src/checkpoints/__tests__/cli.test.ts,src/codemap/__tests__/cli.test.ts,src/codemap/__tests__/units.test.ts,src/dashboard/__tests__/cli.test.ts,src/dashboard/__tests__/page-route.test.ts,src/dashboard/__tests__/self-improvement-metrics.test.ts,src/dashboard/__tests__/sigint-shutdown.test.ts,src/github/__tests__/installations.test.ts,src/orchestrator/__tests__/analyze-cli.test.ts,src/orchestrator/__tests__/cost-estimate.test.ts,src/orchestrator/__tests__/log-analysis.test.ts,src/orchestrator/__tests__/spawn-state-reset.test.ts,src/orchestrator/__tests__/status.test.ts,src/run-tests-script.test.ts,src/tools/__tests__/set-indentation.test.ts", "clone 6846b0b81f2a src/llm/__tests__/preflight.test.ts,src/orchestrator/__tests__/cli.test.ts", "clone 68612079ddc5 src/llm/preflight.ts", - "clone 68654f999273 src/engine/__tests__/stack-rules-content.test.ts,src/stack-rules-content.test.ts", "clone 687e46486dc5 src/engine/__tests__/stack-rules-content.test.ts,src/engine/__tests__/stacks.test.ts", "clone 6893410ec1b1 src/orchestrator/__tests__/main-tree-tripwire.test.ts,src/orchestrator/__tests__/review-gates.test.ts", "clone 68b1356447b2 src/orchestrator/cli.ts", "clone 68ceda9cfcd3 src/codesearch/files.ts,src/engine/stacks.ts", + "clone 6910fdd8603d src/codesearch/__tests__/backend-selection.test.ts,src/codesearch/__tests__/index.test.ts,src/codesearch/__tests__/search.test.ts,src/config/__tests__/mode-models.test.ts,src/dashboard/__tests__/file-browser-permissions.test.ts,src/dashboard/__tests__/settings.test.ts,src/orchestrator/__tests__/cli.test.ts,src/orchestrator/__tests__/cost-history-cli.test.ts,src/orchestrator/__tests__/cost-history.test.ts,src/orchestrator/__tests__/model-resolution.test.ts,src/orchestrator/__tests__/resume.test.ts,src/orchestrator/__tests__/watch.test.ts,src/permissions/__tests__/protected-files.test.ts,src/tools/__tests__/codebase_search.test.ts", "clone 694e054dcbfb src/codesearch/files.ts,src/engine/stacks.ts", "clone 695d76cf4fae src/dashboard/__tests__/projects-routes.test.ts,src/projects/__tests__/cli.test.ts", - "clone 699b3b419af4 src/orchestrator/__tests__/cost-history-cli.test.ts,src/orchestrator/__tests__/cost-history.test.ts", "clone 69c5c3472368 src/dashboard/server.ts", - "clone 69d258407ea9 src/codesearch/__tests__/backend-selection.test.ts,src/codesearch/__tests__/index.test.ts,src/codesearch/__tests__/search.test.ts,src/config/__tests__/mode-models.test.ts,src/dashboard/__tests__/file-browser-permissions.test.ts,src/dashboard/__tests__/settings.test.ts,src/orchestrator/__tests__/cli.test.ts,src/orchestrator/__tests__/model-resolution.test.ts,src/orchestrator/__tests__/resume.test.ts,src/orchestrator/__tests__/watch.test.ts,src/permissions/__tests__/protected-files.test.ts,src/tools/__tests__/codebase_search.test.ts", "clone 69ff81f6294e src/engine/__tests__/condense.test.ts,src/engine/__tests__/loop.test.ts", "clone 6ad43b2053fa src/engine/__tests__/loop.test.ts", - "clone 6b322a1d1b11 src/orchestrator/__tests__/cost-history-cli.test.ts,src/orchestrator/__tests__/cost-history.test.ts", - "clone 6b35c8a20e34 src/orchestrator/__tests__/cost-history-cli.test.ts,src/orchestrator/__tests__/cost-history.test.ts", "clone 6b5f2e4eff30 src/llm/muse.ts", "clone 6bf4008bd80c src/orchestrator/__tests__/cli.test.ts", "clone 6c4b7719d621 src/engine/__tests__/loop.test.ts", - "clone 6c650d02957b src/codeintel/__tests__/registration.test.ts,src/tools/__tests__/batch.test.ts", "clone 6cade99be57e src/rsi/__tests__/controller-candidates.test.ts,src/rsi/__tests__/controller-fleet-results.test.ts", "clone 6cc5e85990d1 src/orchestrator/__tests__/spawn-model.test.ts,src/orchestrator/__tests__/spawn-state-reset.test.ts", "clone 6cd34638ecf4 src/codesearch/airunner-embedder.ts,src/codesearch/ollama-embedder.ts", @@ -410,7 +365,6 @@ "clone 6de3bc55de51 src/engine/loop.ts", "clone 6e0f7e4d8bbd src/orchestrator/cli.ts,src/orchestrator/resume.ts", "clone 6e3afc8af712 src/codesearch/files.ts,src/engine/stacks.ts", - "clone 6e67c36eb0ac src/codeintel/__tests__/registration.test.ts,src/tools/__tests__/batch.test.ts", "clone 6ec154ac699f src/llm/preflight.ts", "clone 700ed9396d5d src/orchestrator/cli.ts", "clone 70f80df8706f src/orchestrator/cli.ts,src/watcher/cli.ts", @@ -439,8 +393,6 @@ "clone 76fd2afa5e53 src/dashboard/__tests__/projects-routes.test.ts,src/projects/__tests__/cli.test.ts", "clone 76fe651f8360 src/dashboard/server.ts", "clone 775035a6a231 src/dashboard/server.ts", - "clone 7771faf0bdbe src/budget/__tests__/budget.test.ts,src/budget/__tests__/concurrency.test.ts,src/budget/__tests__/cost.test.ts,src/checkpoints/__tests__/service.test.ts,src/cloud/__tests__/docker-provider.test.ts,src/cloud/__tests__/provider.test.ts,src/codeintel/__tests__/call-graph.test.ts,src/codeintel/__tests__/call-hierarchy.test.ts,src/codeintel/__tests__/find-references.test.ts,src/codeintel/__tests__/go-to-definition.test.ts,src/codeintel/__tests__/hover.test.ts,src/codeintel/__tests__/import-graph.test.ts,src/codeintel/__tests__/outline.test.ts,src/codeintel/__tests__/rename-symbol.test.ts,src/codemap/__tests__/build.test.ts,src/codesearch/__tests__/airunner-embedder.test.ts,src/codesearch/__tests__/backend-selection.test.ts,src/codesearch/__tests__/chunk.test.ts,src/codesearch/__tests__/files.test.ts,src/codesearch/__tests__/index.test.ts,src/codesearch/__tests__/ollama-embedder.test.ts,src/codesearch/__tests__/search.test.ts,src/config/__tests__/mode-models.test.ts,src/dashboard/__tests__/aggregate.test.ts,src/dashboard/__tests__/chat-thread.test.ts,src/dashboard/__tests__/checkpoints-routes.test.ts,src/dashboard/__tests__/codemap-routes.test.ts,src/dashboard/__tests__/cost-history-routes.test.ts,src/dashboard/__tests__/events.test.ts,src/dashboard/__tests__/file-browser-permissions.test.ts,src/dashboard/__tests__/projects-routes.test.ts,src/dashboard/__tests__/server.test.ts,src/dashboard/__tests__/session-launch.test.ts,src/dashboard/__tests__/settings.test.ts,src/dashboard/__tests__/timeline.test.ts,src/deploy/__tests__/gate.test.ts,src/engine/__tests__/condense-async.test.ts,src/engine/__tests__/condense.test.ts,src/engine/__tests__/events.test.ts,src/engine/__tests__/inject-message.test.ts,src/engine/__tests__/loop.test.ts,src/engine/__tests__/new-task.test.ts,src/engine/__tests__/pause.test.ts,src/engine/__tests__/switch-mode.test.ts,src/github/__tests__/app-auth.test.ts,src/github/__tests__/cli.test.ts,src/github/__tests__/pr.test.ts,src/github/__tests__/provision.test.ts,src/github/__tests__/push.test.ts,src/index-util.test.ts,src/init/__tests__/cli.test.ts,src/init/__tests__/gitignore.test.ts,src/memory/__tests__/local.test.ts,src/memory/__tests__/memory-wiring.test.ts,src/memory/__tests__/uwuchat.test.ts,src/migrate/cli.test.ts,src/orchestrator/__tests__/arg-cursor.test.ts,src/orchestrator/__tests__/auto-split.test.ts,src/orchestrator/__tests__/cleanup.test.ts,src/orchestrator/__tests__/cli.test.ts,src/orchestrator/__tests__/git-sync.test.ts,src/orchestrator/__tests__/merge-check.test.ts,src/orchestrator/__tests__/model-resolution.test.ts,src/orchestrator/__tests__/pipeline.test.ts,src/orchestrator/__tests__/resume.test.ts,src/orchestrator/__tests__/reviewer.test.ts,src/orchestrator/__tests__/spawn-model.test.ts,src/orchestrator/__tests__/split.test.ts,src/orchestrator/__tests__/state.test.ts,src/orchestrator/__tests__/status-cli.test.ts,src/orchestrator/__tests__/stop-cli.test.ts,src/orchestrator/__tests__/watch.test.ts,src/permissions/__tests__/commands.test.ts,src/permissions/__tests__/protected-files.test.ts,src/permissions/__tests__/store-protection.test.ts,src/project-store.test.ts,src/projects/__tests__/cli.test.ts,src/qa/__tests__/qa.test.ts,src/tools/__tests__/apply_diff.test.ts,src/tools/__tests__/browser-action.test.ts,src/tools/__tests__/codebase_search.test.ts,src/tools/__tests__/decision.test.ts,src/tools/__tests__/describe_image.test.ts,src/tools/__tests__/edit_file.test.ts,src/tools/__tests__/execute_command.test.ts,src/tools/__tests__/language-detect.test.ts,src/tools/__tests__/list-files.test.ts,src/tools/__tests__/local-summarization-gate.test.ts,src/tools/__tests__/output-summarizer.test.ts,src/tools/__tests__/path-safety.test.ts,src/tools/__tests__/read-cache.test.ts,src/tools/__tests__/run-tests.test.ts,src/tools/__tests__/search_replace.test.ts,src/tools/__tests__/update_todo_list.test.ts,src/tools/__tests__/write_to_file.test.ts,src/vision/__tests__/describe.test.ts,src/watcher/__tests__/github.test.ts,src/watcher/__tests__/state.test.ts,src/watcher/__tests__/watch.test.ts", - "clone 77a1f2eb17cb src/codesearch/__tests__/backend-selection.test.ts,src/codesearch/__tests__/index.test.ts,src/codesearch/__tests__/search.test.ts,src/config/__tests__/mode-models.test.ts,src/dashboard/__tests__/file-browser-permissions.test.ts,src/dashboard/__tests__/settings.test.ts,src/orchestrator/__tests__/cli.test.ts,src/orchestrator/__tests__/model-resolution.test.ts,src/orchestrator/__tests__/resume.test.ts,src/orchestrator/__tests__/spawn-model.test.ts,src/orchestrator/__tests__/watch.test.ts,src/permissions/__tests__/protected-files.test.ts,src/tools/__tests__/codebase_search.test.ts", "clone 77aa86621a85 src/decision-proxy/__tests__/proxy.test.ts,src/engine/__tests__/switch-mode.test.ts,src/tools/__tests__/codebase_search.test.ts,src/tools/__tests__/execute_command.test.ts", "clone 77db3aa3bdb6 src/dashboard/__tests__/projects-routes.test.ts,src/projects/__tests__/cli.test.ts", "clone 77f73cc7bae5 src/cli.ts,src/dashboard/cli.ts,src/dashboard/trend-cli.ts,src/orchestrator/cleanup.ts,src/watcher/cli.ts", @@ -459,7 +411,7 @@ "clone 7a1b9f629554 src/engine/__tests__/new-task.test.ts,src/engine/__tests__/switch-mode.test.ts", "clone 7a53d4a3fcc7 src/codesearch/files.ts,src/engine/stacks.ts", "clone 7aa1ee87f445 src/orchestrator/resume.ts", - "clone 7aff3d23b7c0 src/engine/__tests__/global-modes.test.ts,src/engine/__tests__/stack-rules-content.test.ts", + "clone 7aac966a5ced src/codesearch/__tests__/backend-selection.test.ts,src/codesearch/__tests__/index.test.ts,src/codesearch/__tests__/search.test.ts,src/config/__tests__/mode-models.test.ts,src/dashboard/__tests__/file-browser-permissions.test.ts,src/dashboard/__tests__/settings.test.ts,src/orchestrator/__tests__/cli.test.ts,src/orchestrator/__tests__/cost-history-cli.test.ts,src/orchestrator/__tests__/cost-history.test.ts,src/orchestrator/__tests__/model-resolution.test.ts,src/orchestrator/__tests__/resume.test.ts,src/orchestrator/__tests__/watch.test.ts,src/permissions/__tests__/protected-files.test.ts,src/tools/__tests__/codebase_search.test.ts", "clone 7b4511a4efb0 src/orchestrator/__tests__/spawn-model.test.ts,src/orchestrator/__tests__/spawn-state-reset.test.ts", "clone 7bab51178bf0 src/orchestrator/task-files.ts", "clone 7bae656083ca src/dashboard/__tests__/server.test.ts,src/dashboard/__tests__/session-launch.test.ts", @@ -473,7 +425,6 @@ "clone 7dd51a187649 src/rsi/__tests__/controller-candidates.test.ts,src/rsi/__tests__/controller-fleet-results.test.ts,src/rsi/__tests__/controller-integrity.test.ts", "clone 7ddc99484482 src/dashboard/server.ts", "clone 7dfc9cbdc2b8 src/orchestrator/task-files.ts", - "clone 7e1bcf45c7b2 src/engine/__tests__/loop-helpers-history.test.ts,src/engine/__tests__/loop-helpers-readonly.test.ts,src/engine/__tests__/parser.test.ts,src/engine/__tests__/summarize-tool-arg.test.ts,src/memory/__tests__/embed.test.ts,src/memory/__tests__/summarizer.test.ts", "clone 7e604775e29f src/orchestrator/cli.ts", "clone 7e9f9c4012ef src/orchestrator/__tests__/analyze-cli.test.ts", "clone 7f13e4c18807 src/orchestrator/__tests__/cli.test.ts", @@ -505,7 +456,6 @@ "clone 86154b195a9d src/engine/__tests__/loop.test.ts", "clone 862b3b509236 src/engine/__tests__/loop.test.ts,src/tools/__tests__/execute_command.test.ts", "clone 862e77a7bd8b src/engine/__tests__/loop.test.ts", - "clone 86477118fb95 src/__tests__/cli-env.test.ts,src/codesearch/__tests__/embedder-types.test.ts,src/evidence/__tests__/verify-files.test.ts,src/orchestrator/__tests__/task-files.test.ts,src/orchestrator/__tests__/terminal-status.test.ts,src/tools/__tests__/batch-types.test.ts,src/tools/__tests__/paths.test.ts", "clone 868374f44959 src/orchestrator/cli.ts,src/watcher/cli.ts", "clone 8737d3d18a94 src/dashboard/trend-cli.ts,src/orchestrator/cleanup.ts", "clone 8744bca39679 src/engine/__tests__/pause.test.ts", @@ -516,7 +466,6 @@ "clone 88f198a525f7 src/orchestrator/task-files.ts", "clone 896cd21c8bf7 src/orchestrator/resume.ts", "clone 898239c06549 src/engine/__tests__/condense.test.ts", - "clone 8a0887cd8c83 src/engine/__tests__/new-task.test.ts,src/engine/__tests__/switch-mode.test.ts", "clone 8a2e5c3b48db src/orchestrator/cli.ts,src/watcher/cli.ts", "clone 8a757993c7f9 src/dashboard/__tests__/projects-routes.test.ts,src/projects/__tests__/cli.test.ts", "clone 8aa676959e90 src/codesearch/embedder.ts,src/memory/embed.ts", @@ -525,7 +474,6 @@ "clone 8b4721c34bf4 src/engine/loop.ts", "clone 8b665567608f src/orchestrator/__tests__/spawn-model.test.ts,src/orchestrator/__tests__/spawn-state-reset.test.ts", "clone 8c15aef04331 src/orchestrator/task-files.ts", - "clone 8ca0903cd323 src/codesearch/__tests__/backend-selection.test.ts,src/codesearch/__tests__/index.test.ts,src/codesearch/__tests__/search.test.ts,src/config/__tests__/mode-models.test.ts,src/dashboard/__tests__/file-browser-permissions.test.ts,src/dashboard/__tests__/settings.test.ts,src/orchestrator/__tests__/cli.test.ts,src/orchestrator/__tests__/cost-history-cli.test.ts,src/orchestrator/__tests__/cost-history.test.ts,src/orchestrator/__tests__/model-resolution.test.ts,src/orchestrator/__tests__/resume.test.ts,src/orchestrator/__tests__/watch.test.ts,src/permissions/__tests__/protected-files.test.ts,src/tools/__tests__/codebase_search.test.ts", "clone 8cbf4767d294 src/engine/local-explore.ts,src/llm/ollama.ts", "clone 8d24cff0cf64 src/engine/__tests__/loop.test.ts", "clone 8d49805d7bfd src/orchestrator/__tests__/status-cli.test.ts", @@ -542,7 +490,6 @@ "clone 8fbaf4437025 src/dashboard/__tests__/cost-history-routes.test.ts,src/orchestrator/__tests__/cost-history-cli.test.ts", "clone 8fe789c46bc4 src/orchestrator/__tests__/reviewer.test.ts,src/qa/__tests__/qa.test.ts", "clone 9003da86dcf3 src/codesearch/files.ts,src/engine/stacks.ts", - "clone 902284a95450 src/budget/__tests__/budget.test.ts,src/budget/__tests__/concurrency.test.ts,src/budget/__tests__/cost.test.ts,src/checkpoints/__tests__/service.test.ts,src/cloud/__tests__/provider.test.ts,src/codeintel/__tests__/call-graph.test.ts,src/codeintel/__tests__/call-hierarchy.test.ts,src/codeintel/__tests__/find-references.test.ts,src/codeintel/__tests__/go-to-definition.test.ts,src/codeintel/__tests__/hover.test.ts,src/codeintel/__tests__/import-graph.test.ts,src/codeintel/__tests__/outline.test.ts,src/codeintel/__tests__/rename-symbol.test.ts,src/codemap/__tests__/build.test.ts,src/codesearch/__tests__/airunner-embedder.test.ts,src/codesearch/__tests__/chunk.test.ts,src/codesearch/__tests__/files.test.ts,src/codesearch/__tests__/ollama-embedder.test.ts,src/dashboard/__tests__/aggregate.test.ts,src/dashboard/__tests__/chat-thread.test.ts,src/dashboard/__tests__/checkpoints-routes.test.ts,src/dashboard/__tests__/codemap-routes.test.ts,src/dashboard/__tests__/cost-history-routes.test.ts,src/dashboard/__tests__/events.test.ts,src/dashboard/__tests__/projects-routes.test.ts,src/dashboard/__tests__/server.test.ts,src/dashboard/__tests__/session-launch.test.ts,src/dashboard/__tests__/timeline.test.ts,src/deploy/__tests__/gate.test.ts,src/engine/__tests__/condense-async.test.ts,src/engine/__tests__/condense.test.ts,src/engine/__tests__/events.test.ts,src/engine/__tests__/inject-message.test.ts,src/engine/__tests__/local-explore.test.ts,src/engine/__tests__/loop-helpers-history.test.ts,src/engine/__tests__/loop-helpers-readonly.test.ts,src/engine/__tests__/loop.test.ts,src/engine/__tests__/new-task.test.ts,src/engine/__tests__/parser.test.ts,src/engine/__tests__/pause.test.ts,src/engine/__tests__/summarize-tool-arg.test.ts,src/engine/__tests__/switch-mode.test.ts,src/github/__tests__/app-auth.test.ts,src/github/__tests__/cli.test.ts,src/github/__tests__/pr.test.ts,src/github/__tests__/provision.test.ts,src/github/__tests__/push.test.ts,src/index-util.test.ts,src/init/__tests__/cli.test.ts,src/init/__tests__/gitignore.test.ts,src/memory/__tests__/embed.test.ts,src/memory/__tests__/local.test.ts,src/memory/__tests__/memory-wiring.test.ts,src/memory/__tests__/summarizer.test.ts,src/memory/__tests__/uwuchat.test.ts,src/migrate/cli.test.ts,src/orchestrator/__tests__/arg-cursor.test.ts,src/orchestrator/__tests__/auto-split.test.ts,src/orchestrator/__tests__/cleanup.test.ts,src/orchestrator/__tests__/git-sync.test.ts,src/orchestrator/__tests__/merge-check.test.ts,src/orchestrator/__tests__/pipeline.test.ts,src/orchestrator/__tests__/reviewer.test.ts,src/orchestrator/__tests__/split.test.ts,src/orchestrator/__tests__/state.test.ts,src/orchestrator/__tests__/status-cli.test.ts,src/orchestrator/__tests__/stop-cli.test.ts,src/permissions/__tests__/commands.test.ts,src/permissions/__tests__/store-protection.test.ts,src/project-store.test.ts,src/projects/__tests__/cli.test.ts,src/qa/__tests__/qa.test.ts,src/tools/__tests__/apply_diff.test.ts,src/tools/__tests__/browser-action.test.ts,src/tools/__tests__/decision.test.ts,src/tools/__tests__/describe_image.test.ts,src/tools/__tests__/edit_file.test.ts,src/tools/__tests__/execute_command.test.ts,src/tools/__tests__/language-detect.test.ts,src/tools/__tests__/list-files.test.ts,src/tools/__tests__/local-summarization-gate.test.ts,src/tools/__tests__/output-summarizer.test.ts,src/tools/__tests__/read-cache.test.ts,src/tools/__tests__/run-tests.test.ts,src/tools/__tests__/search_replace.test.ts,src/tools/__tests__/update_todo_list.test.ts,src/tools/__tests__/write_to_file.test.ts,src/vision/__tests__/describe.test.ts,src/watcher/__tests__/github.test.ts,src/watcher/__tests__/state.test.ts,src/watcher/__tests__/watch.test.ts", "clone 90309d08a06e src/engine/local-explore.ts,src/llm/ollama.ts", "clone 90919b289545 src/engine/__tests__/loop.test.ts,src/tools/__tests__/execute_command.test.ts", "clone 90ad7e210e7a src/engine/__tests__/condense.test.ts,src/engine/__tests__/loop.test.ts", @@ -563,12 +510,10 @@ "clone 95c346543423 src/orchestrator/task-files.ts", "clone 95e9585718ac src/engine/loop.ts", "clone 96160c6998c6 src/dashboard/__tests__/server.test.ts,src/dashboard/__tests__/session-launch.test.ts", - "clone 9624c77068ad src/engine/__tests__/loop-helpers-history.test.ts,src/engine/__tests__/loop-helpers-readonly.test.ts,src/engine/__tests__/parser.test.ts,src/engine/__tests__/summarize-tool-arg.test.ts,src/memory/__tests__/embed.test.ts,src/memory/__tests__/summarizer.test.ts", "clone 9653e98afda5 src/codesearch/__tests__/airunner-embedder.test.ts,src/codesearch/__tests__/ollama-embedder.test.ts", "clone 966fcd888ef7 src/orchestrator/cli.ts,src/orchestrator/resume.ts", "clone 9674b1ab1696 src/engine/loop.ts", "clone 9677b9319ac8 src/engine/__tests__/condense.test.ts", - "clone 96a19c419888 src/codeintel/__tests__/registration.test.ts,src/tools/__tests__/batch.test.ts", "clone 96d294f3a39d src/orchestrator/resume.ts", "clone 96d730011cef src/llm/openrouter.ts", "clone 9716d28923cf src/dashboard/trend-cli.ts,src/orchestrator/cleanup.ts", @@ -580,14 +525,12 @@ "clone 981c36f38d34 src/orchestrator/reviewer.ts,src/qa/qa.ts", "clone 9820cd6df71a src/codesearch/files.ts,src/engine/stacks.ts", "clone 986c6f8c6c8e src/checkpoints/__tests__/cli.test.ts,src/codemap/__tests__/cli.test.ts,src/dashboard/__tests__/cli.test.ts", - "clone 988e6970e5ab src/codesearch/__tests__/backend-selection.test.ts,src/codesearch/__tests__/index.test.ts,src/codesearch/__tests__/search.test.ts,src/config/__tests__/mode-models.test.ts,src/dashboard/__tests__/file-browser-permissions.test.ts,src/dashboard/__tests__/settings.test.ts,src/orchestrator/__tests__/cli.test.ts,src/orchestrator/__tests__/cost-history-cli.test.ts,src/orchestrator/__tests__/cost-history.test.ts,src/orchestrator/__tests__/model-resolution.test.ts,src/orchestrator/__tests__/resume.test.ts,src/orchestrator/__tests__/spawn-model.test.ts,src/orchestrator/__tests__/watch.test.ts,src/permissions/__tests__/protected-files.test.ts,src/tools/__tests__/codebase_search.test.ts", "clone 98d735782817 src/llm/muse.ts", "clone 98e64d6f23d8 src/checkpoints/cli.ts,src/decision-proxy/cli.ts", "clone 993a1632940a src/llm/muse.ts,src/llm/openrouter.ts", "clone 998a739cc867 src/engine/__tests__/new-task.test.ts,src/engine/__tests__/switch-mode.test.ts", "clone 99b150e9e3dc src/codesearch/__tests__/airunner-embedder.test.ts,src/codesearch/__tests__/ollama-embedder.test.ts", "clone 99ba2224e54b src/llm/muse.ts,src/llm/openrouter.ts", - "clone 99c8753a4cab src/checkpoints/__tests__/cli.test.ts,src/codemap/__tests__/cli.test.ts,src/codemap/__tests__/units.test.ts,src/dashboard/__tests__/cli.test.ts,src/dashboard/__tests__/page-route.test.ts,src/dashboard/__tests__/self-improvement-metrics.test.ts,src/dashboard/__tests__/sigint-shutdown.test.ts,src/dashboard/__tests__/trend.test.ts,src/github/__tests__/installations.test.ts,src/orchestrator/__tests__/analyze-cli.test.ts,src/orchestrator/__tests__/cost-estimate.test.ts,src/orchestrator/__tests__/log-analysis.test.ts,src/orchestrator/__tests__/spawn-state-reset.test.ts,src/orchestrator/__tests__/status.test.ts,src/run-tests-script.test.ts,src/tools/__tests__/set-indentation.test.ts", "clone 99ebebf858ee src/codesearch/__tests__/airunner-embedder.test.ts,src/codesearch/__tests__/ollama-embedder.test.ts", "clone 9a2d9c8ece5a src/rsi/__tests__/controller-candidates.test.ts,src/rsi/__tests__/controller-integrity.test.ts", "clone 9a34727f0f71 src/engine/__tests__/condense.test.ts", @@ -617,13 +560,9 @@ "clone 9f5a1c3e6be5 src/orchestrator/resume.ts", "clone 9fd2184a9f18 src/rsi/__tests__/controller-candidates.test.ts,src/rsi/__tests__/controller-integrity.test.ts", "clone a003fde0f022 src/llm/muse.ts,src/llm/openrouter.ts", - "clone a023cb3be29b src/budget/__tests__/budget.test.ts,src/budget/__tests__/concurrency.test.ts,src/budget/__tests__/cost.test.ts,src/checkpoints/__tests__/cli.test.ts,src/checkpoints/__tests__/service.test.ts,src/cloud/__tests__/provider.test.ts,src/codemap/__tests__/cli.test.ts,src/codemap/__tests__/units.test.ts,src/codesearch/__tests__/airunner-embedder.test.ts,src/codesearch/__tests__/chunk.test.ts,src/codesearch/__tests__/files.test.ts,src/codesearch/__tests__/ollama-embedder.test.ts,src/dashboard/__tests__/aggregate.test.ts,src/dashboard/__tests__/chat-thread.test.ts,src/dashboard/__tests__/checkpoints-routes.test.ts,src/dashboard/__tests__/cli.test.ts,src/dashboard/__tests__/events.test.ts,src/dashboard/__tests__/page-route.test.ts,src/dashboard/__tests__/projects-routes.test.ts,src/dashboard/__tests__/self-improvement-metrics.test.ts,src/dashboard/__tests__/server.test.ts,src/dashboard/__tests__/session-launch.test.ts,src/dashboard/__tests__/sigint-shutdown.test.ts,src/dashboard/__tests__/timeline.test.ts,src/deploy/__tests__/gate.test.ts,src/engine/__tests__/condense-async.test.ts,src/engine/__tests__/condense.test.ts,src/engine/__tests__/events.test.ts,src/engine/__tests__/inject-message.test.ts,src/engine/__tests__/new-task.test.ts,src/engine/__tests__/pause.test.ts,src/engine/__tests__/prompt.test.ts,src/engine/__tests__/switch-mode.test.ts,src/github/__tests__/app-auth.test.ts,src/github/__tests__/cli.test.ts,src/github/__tests__/installations.test.ts,src/github/__tests__/pr.test.ts,src/github/__tests__/provision.test.ts,src/github/__tests__/push.test.ts,src/index-util.test.ts,src/init/__tests__/cli.test.ts,src/init/__tests__/gitignore.test.ts,src/llm/__tests__/muse.test.ts,src/llm/__tests__/openrouter.test.ts,src/memory/__tests__/local.test.ts,src/memory/__tests__/memory-wiring.test.ts,src/memory/__tests__/uwuchat.test.ts,src/migrate/cli.test.ts,src/orchestrator/__tests__/analyze-cli.test.ts,src/orchestrator/__tests__/arg-cursor.test.ts,src/orchestrator/__tests__/auto-split.test.ts,src/orchestrator/__tests__/cleanup.test.ts,src/orchestrator/__tests__/cost-estimate.test.ts,src/orchestrator/__tests__/git-sync.test.ts,src/orchestrator/__tests__/log-analysis.test.ts,src/orchestrator/__tests__/merge-check.test.ts,src/orchestrator/__tests__/pipeline.test.ts,src/orchestrator/__tests__/review-gates.test.ts,src/orchestrator/__tests__/reviewer.test.ts,src/orchestrator/__tests__/spawn-state-reset.test.ts,src/orchestrator/__tests__/split.test.ts,src/orchestrator/__tests__/state.test.ts,src/orchestrator/__tests__/status-cli.test.ts,src/orchestrator/__tests__/status.test.ts,src/orchestrator/__tests__/stop-cli.test.ts,src/permissions/__tests__/commands.test.ts,src/permissions/__tests__/store-protection.test.ts,src/project-store.test.ts,src/projects/__tests__/cli.test.ts,src/qa/__tests__/qa.test.ts,src/run-tests-script.test.ts,src/tools/__tests__/apply_diff.test.ts,src/tools/__tests__/decision.test.ts,src/tools/__tests__/describe_image.test.ts,src/tools/__tests__/edit_file.test.ts,src/tools/__tests__/execute_command.test.ts,src/tools/__tests__/language-detect.test.ts,src/tools/__tests__/list-files.test.ts,src/tools/__tests__/local-summarization-gate.test.ts,src/tools/__tests__/output-summarizer.test.ts,src/tools/__tests__/path-safety.test.ts,src/tools/__tests__/read-cache.test.ts,src/tools/__tests__/run-tests.test.ts,src/tools/__tests__/search_replace.test.ts,src/tools/__tests__/set-indentation.test.ts,src/tools/__tests__/update_todo_list.test.ts,src/tools/__tests__/write_to_file.test.ts,src/vision/__tests__/describe.test.ts,src/watcher/__tests__/github.test.ts,src/watcher/__tests__/state.test.ts,src/watcher/__tests__/watch.test.ts", - "clone a0e38675893c src/checkpoints/__tests__/cli.test.ts,src/codemap/__tests__/cli.test.ts,src/codemap/__tests__/units.test.ts,src/dashboard/__tests__/cli.test.ts,src/dashboard/__tests__/page-route.test.ts,src/dashboard/__tests__/self-improvement-metrics.test.ts,src/dashboard/__tests__/sigint-shutdown.test.ts,src/github/__tests__/installations.test.ts,src/orchestrator/__tests__/analyze-cli.test.ts,src/orchestrator/__tests__/cost-estimate.test.ts,src/orchestrator/__tests__/log-analysis.test.ts,src/orchestrator/__tests__/spawn-state-reset.test.ts,src/orchestrator/__tests__/status.test.ts,src/run-tests-script.test.ts,src/tools/__tests__/set-indentation.test.ts", "clone a100929d0996 src/codesearch/__tests__/airunner-embedder.test.ts,src/codesearch/__tests__/ollama-embedder.test.ts", "clone a1132d316866 src/checkpoints/cli.ts,src/cli.ts,src/codemap/cli.ts,src/codesearch/cli.ts,src/dashboard/cli.ts,src/dashboard/trend-cli.ts,src/decision-proxy/cli.ts,src/github/cli.ts,src/init/cli.ts,src/orchestrator/cleanup.ts,src/watcher/cli.ts", - "clone a11ed931f825 src/codeintel/__tests__/registration.test.ts,src/tools/__tests__/batch.test.ts", "clone a1a26fbf0a56 src/orchestrator/__tests__/status-cli.test.ts", - "clone a1c96da14be8 src/codesearch/__tests__/backend-selection.test.ts,src/codesearch/__tests__/index.test.ts,src/codesearch/__tests__/search.test.ts,src/config/__tests__/mode-models.test.ts,src/dashboard/__tests__/file-browser-permissions.test.ts,src/dashboard/__tests__/settings.test.ts,src/orchestrator/__tests__/cli.test.ts,src/orchestrator/__tests__/model-resolution.test.ts,src/orchestrator/__tests__/resume.test.ts,src/orchestrator/__tests__/watch.test.ts,src/permissions/__tests__/protected-files.test.ts,src/tools/__tests__/codebase_search.test.ts", "clone a1cf343dc5c6 src/orchestrator/cli.ts", "clone a1f6b098886c src/rsi/__tests__/controller-candidates.test.ts,src/rsi/__tests__/controller-integrity.test.ts", "clone a1fd626e0e34 src/dashboard/__tests__/checkpoints-routes.test.ts,src/dashboard/__tests__/file-browser-permissions.test.ts", @@ -633,7 +572,6 @@ "clone a2856ade0b68 src/llm/muse.ts,src/llm/openrouter.ts", "clone a290c29733ab src/codeintel/__tests__/call-graph.test.ts,src/codeintel/__tests__/find-references.test.ts,src/codeintel/__tests__/hover.test.ts,src/codeintel/__tests__/import-graph.test.ts,src/codeintel/__tests__/outline.test.ts,src/codeintel/__tests__/registration.test.ts,src/codeintel/__tests__/rename-symbol.test.ts,src/tools/__tests__/run-tests.test.ts", "clone a331eb790bd5 src/orchestrator/resume.ts", - "clone a3534d1a9458 src/engine/__tests__/global-modes.test.ts,src/engine/__tests__/prompt.test.ts,src/engine/__tests__/stack-rules-content.test.ts,src/engine/__tests__/stacks.test.ts,src/llm/__tests__/muse.test.ts,src/llm/__tests__/openrouter.test.ts", "clone a3552e07fb57 src/dashboard/server.ts", "clone a42a6d879ca3 src/dashboard/__tests__/session-launch.test.ts,src/dashboard/__tests__/settings.test.ts", "clone a4983caf62b6 src/llm/preflight.ts", @@ -646,11 +584,11 @@ "clone a705b5057bf3 src/codesearch/embedder.ts,src/memory/embed.ts", "clone a70726307e4f src/orchestrator/cli.ts", "clone a7524c1d0b75 src/dashboard/__tests__/projects-routes.test.ts,src/projects/__tests__/cli.test.ts", - "clone a7ac9999b88e src/checkpoints/__tests__/cli.test.ts,src/codemap/__tests__/cli.test.ts,src/codemap/__tests__/units.test.ts,src/dashboard/__tests__/cli.test.ts,src/dashboard/__tests__/page-route.test.ts,src/dashboard/__tests__/self-improvement-metrics.test.ts,src/dashboard/__tests__/sigint-shutdown.test.ts,src/github/__tests__/installations.test.ts,src/orchestrator/__tests__/analyze-cli.test.ts,src/orchestrator/__tests__/cost-estimate.test.ts,src/orchestrator/__tests__/cost-history-cli.test.ts,src/orchestrator/__tests__/cost-history.test.ts,src/orchestrator/__tests__/log-analysis.test.ts,src/orchestrator/__tests__/spawn-state-reset.test.ts,src/orchestrator/__tests__/status.test.ts,src/run-tests-script.test.ts,src/tools/__tests__/set-indentation.test.ts", "clone a85870eb1a5b src/decision-proxy/__tests__/proxy.test.ts,src/engine/__tests__/switch-mode.test.ts", "clone a89b7f2f4a03 src/checkpoints/cli.ts,src/decision-proxy/cli.ts", "clone a8a18bc5e095 src/engine/local-explore.ts,src/llm/ollama.ts", "clone a8b8282008b3 src/rsi/__tests__/controller-candidates.test.ts,src/rsi/__tests__/controller-fleet-results.test.ts", + "clone a94c299950cc src/codesearch/__tests__/backend-selection.test.ts,src/codesearch/__tests__/index.test.ts,src/codesearch/__tests__/search.test.ts,src/config/__tests__/mode-models.test.ts,src/dashboard/__tests__/file-browser-permissions.test.ts,src/dashboard/__tests__/settings.test.ts,src/orchestrator/__tests__/cli.test.ts,src/orchestrator/__tests__/cost-history-cli.test.ts,src/orchestrator/__tests__/cost-history.test.ts,src/orchestrator/__tests__/model-resolution.test.ts,src/orchestrator/__tests__/resume.test.ts,src/orchestrator/__tests__/watch.test.ts,src/permissions/__tests__/protected-files.test.ts,src/tools/__tests__/codebase_search.test.ts", "clone a990dfadf953 src/tools/__tests__/apply_diff.test.ts,src/tools/__tests__/edit_file.test.ts,src/tools/__tests__/search_replace.test.ts", "clone a99cd9437614 src/permissions/commands.ts", "clone aa35046643a4 src/rsi/controller-proof-archive.ts", @@ -677,7 +615,6 @@ "clone afbcfb5dcc72 src/rsi/controller.ts", "clone afec75fcac46 src/dashboard/server.ts", "clone b013f5230fd0 src/dashboard/server.ts", - "clone b0d2cf6ccae4 src/codeintel/__tests__/call-graph.test.ts,src/codeintel/__tests__/call-hierarchy.test.ts,src/codeintel/__tests__/find-references.test.ts,src/codeintel/__tests__/go-to-definition.test.ts,src/codeintel/__tests__/hover.test.ts,src/codeintel/__tests__/import-graph.test.ts,src/codeintel/__tests__/outline.test.ts,src/codeintel/__tests__/rename-symbol.test.ts,src/codemap/__tests__/build.test.ts,src/dashboard/__tests__/codemap-routes.test.ts,src/dashboard/__tests__/cost-history-routes.test.ts", "clone b1157b77dcf6 src/budget/__tests__/budget.test.ts", "clone b12348060bcb src/llm/muse.ts", "clone b1263c10d333 src/orchestrator/task-files.ts", @@ -712,7 +649,6 @@ "clone b94a982634f6 src/orchestrator/reviewer.ts,src/qa/qa.ts", "clone b9cced506295 src/orchestrator/__tests__/cli.test.ts,src/orchestrator/__tests__/resume.test.ts", "clone ba0241fb0a20 src/engine/events.ts,src/engine/usage.ts", - "clone ba4c232793d7 src/budget/__tests__/budget.test.ts,src/budget/__tests__/concurrency.test.ts,src/budget/__tests__/cost.test.ts,src/checkpoints/__tests__/service.test.ts,src/cloud/__tests__/provider.test.ts,src/codeintel/__tests__/call-graph.test.ts,src/codeintel/__tests__/call-hierarchy.test.ts,src/codeintel/__tests__/find-references.test.ts,src/codeintel/__tests__/go-to-definition.test.ts,src/codeintel/__tests__/hover.test.ts,src/codeintel/__tests__/import-graph.test.ts,src/codeintel/__tests__/outline.test.ts,src/codeintel/__tests__/rename-symbol.test.ts,src/codemap/__tests__/build.test.ts,src/codesearch/__tests__/airunner-embedder.test.ts,src/codesearch/__tests__/chunk.test.ts,src/codesearch/__tests__/files.test.ts,src/codesearch/__tests__/ollama-embedder.test.ts,src/dashboard/__tests__/aggregate.test.ts,src/dashboard/__tests__/chat-thread.test.ts,src/dashboard/__tests__/checkpoints-routes.test.ts,src/dashboard/__tests__/codemap-routes.test.ts,src/dashboard/__tests__/cost-history-routes.test.ts,src/dashboard/__tests__/events.test.ts,src/dashboard/__tests__/projects-routes.test.ts,src/dashboard/__tests__/server.test.ts,src/dashboard/__tests__/session-launch.test.ts,src/dashboard/__tests__/timeline.test.ts,src/deploy/__tests__/gate.test.ts,src/engine/__tests__/condense-async.test.ts,src/engine/__tests__/condense.test.ts,src/engine/__tests__/events.test.ts,src/engine/__tests__/inject-message.test.ts,src/engine/__tests__/loop.test.ts,src/engine/__tests__/new-task.test.ts,src/engine/__tests__/pause.test.ts,src/engine/__tests__/switch-mode.test.ts,src/github/__tests__/app-auth.test.ts,src/github/__tests__/cli.test.ts,src/github/__tests__/pr.test.ts,src/github/__tests__/provision.test.ts,src/github/__tests__/push.test.ts,src/index-util.test.ts,src/init/__tests__/cli.test.ts,src/init/__tests__/gitignore.test.ts,src/memory/__tests__/local.test.ts,src/memory/__tests__/memory-wiring.test.ts,src/memory/__tests__/uwuchat.test.ts,src/migrate/cli.test.ts,src/orchestrator/__tests__/arg-cursor.test.ts,src/orchestrator/__tests__/auto-split.test.ts,src/orchestrator/__tests__/cleanup.test.ts,src/orchestrator/__tests__/git-sync.test.ts,src/orchestrator/__tests__/merge-check.test.ts,src/orchestrator/__tests__/pipeline.test.ts,src/orchestrator/__tests__/reviewer.test.ts,src/orchestrator/__tests__/split.test.ts,src/orchestrator/__tests__/state.test.ts,src/orchestrator/__tests__/status-cli.test.ts,src/orchestrator/__tests__/stop-cli.test.ts,src/permissions/__tests__/commands.test.ts,src/permissions/__tests__/store-protection.test.ts,src/project-store.test.ts,src/projects/__tests__/cli.test.ts,src/qa/__tests__/qa.test.ts,src/tools/__tests__/apply_diff.test.ts,src/tools/__tests__/browser-action.test.ts,src/tools/__tests__/decision.test.ts,src/tools/__tests__/describe_image.test.ts,src/tools/__tests__/edit_file.test.ts,src/tools/__tests__/execute_command.test.ts,src/tools/__tests__/language-detect.test.ts,src/tools/__tests__/list-files.test.ts,src/tools/__tests__/local-summarization-gate.test.ts,src/tools/__tests__/output-summarizer.test.ts,src/tools/__tests__/path-safety.test.ts,src/tools/__tests__/read-cache.test.ts,src/tools/__tests__/run-tests.test.ts,src/tools/__tests__/search_replace.test.ts,src/tools/__tests__/update_todo_list.test.ts,src/tools/__tests__/write_to_file.test.ts,src/vision/__tests__/describe.test.ts,src/watcher/__tests__/github.test.ts,src/watcher/__tests__/state.test.ts,src/watcher/__tests__/watch.test.ts", "clone baa20b9ca397 src/codesearch/__tests__/airunner-embedder.test.ts,src/codesearch/__tests__/ollama-embedder.test.ts", "clone bac150db44ec src/llm/preflight.ts", "clone bad5d67208cf src/orchestrator/task-files.ts", @@ -732,29 +668,26 @@ "clone bd99f1f211f3 src/engine/__tests__/inject-message.test.ts,src/engine/__tests__/loop.test.ts,src/engine/__tests__/pause.test.ts", "clone be4b5da32f15 src/orchestrator/cli.ts", "clone bf4577eed8ec src/engine/__tests__/loop.test.ts", - "clone bf5199ff5773 src/codeintel/__tests__/implementations.test.ts,src/orchestrator/__tests__/review-gates.test.ts", "clone bf96094425f1 src/orchestrator/__tests__/cli.test.ts,src/orchestrator/__tests__/resume.test.ts", - "clone bfda8ae55db7 src/engine/__tests__/loop-helpers-history.test.ts,src/engine/__tests__/loop-helpers-readonly.test.ts,src/engine/__tests__/loop.test.ts,src/engine/__tests__/new-task.test.ts,src/engine/__tests__/parser.test.ts,src/engine/__tests__/summarize-tool-arg.test.ts,src/engine/__tests__/switch-mode.test.ts", "clone c1b7cee2cf47 src/codesearch/files.ts,src/engine/stacks.ts", "clone c1e1b41c0b87 src/orchestrator/__tests__/spawn-model.test.ts,src/orchestrator/__tests__/spawn-state-reset.test.ts", "clone c1f158e49080 src/orchestrator/reviewer.ts,src/qa/qa.ts", "clone c25de2e7a85a src/orchestrator/cli.ts", "clone c26e4fd9d44b src/rsi/__tests__/controller-candidates.test.ts,src/rsi/__tests__/controller-integrity.test.ts", "clone c28d9afd9221 src/engine/__tests__/loop.test.ts,src/tools/__tests__/execute_command.test.ts", + "clone c29c3c3368bb src/engine/__tests__/global-modes.test.ts,src/engine/__tests__/stack-rules-content.test.ts", "clone c3486aeec199 src/codesearch/__tests__/airunner-embedder.test.ts,src/codesearch/__tests__/ollama-embedder.test.ts", "clone c3571181b0ad src/orchestrator/__tests__/cli.test.ts,src/orchestrator/__tests__/resume.test.ts", "clone c3716be39566 src/orchestrator/cli.ts", "clone c383dd436114 src/engine/__tests__/switch-mode.test.ts", "clone c3e667db531c src/github/cli.ts", "clone c46a996d1f67 src/dashboard/server.ts", - "clone c4a733213681 src/codesearch/__tests__/backend-selection.test.ts,src/codesearch/__tests__/index.test.ts,src/codesearch/__tests__/search.test.ts,src/config/__tests__/mode-models.test.ts,src/dashboard/__tests__/file-browser-permissions.test.ts,src/dashboard/__tests__/settings.test.ts,src/orchestrator/__tests__/cli.test.ts,src/orchestrator/__tests__/model-resolution.test.ts,src/orchestrator/__tests__/resume.test.ts,src/orchestrator/__tests__/watch.test.ts,src/permissions/__tests__/protected-files.test.ts,src/tools/__tests__/codebase_search.test.ts", "clone c4fb71be60ac src/codesearch/embedder.ts,src/memory/embed.ts", "clone c4ffe9d533ad src/orchestrator/cli.ts", "clone c514b8a70ac1 src/engine/__tests__/condense.test.ts", "clone c5206c729975 src/dashboard/__tests__/cost-history-routes.test.ts,src/orchestrator/__tests__/cost-history-cli.test.ts", "clone c5f99dad818d src/engine/__tests__/new-task.test.ts,src/engine/__tests__/switch-mode.test.ts", "clone c5fae546f069 src/watcher/watch.ts", - "clone c620cb9d14ba src/engine/__tests__/loop-helpers-history.test.ts,src/engine/__tests__/loop-helpers-readonly.test.ts,src/engine/__tests__/parser.test.ts,src/engine/__tests__/summarize-tool-arg.test.ts,src/memory/__tests__/embed.test.ts,src/memory/__tests__/summarizer.test.ts", "clone c68a90c711b3 src/codesearch/files.ts,src/engine/stacks.ts", "clone c6af3683a9b2 src/rsi/__tests__/worker.test.ts", "clone c6b95dabb537 src/engine/local-explore.ts,src/llm/ollama.ts", @@ -766,7 +699,6 @@ "clone c7d2cfba43b6 src/engine/__tests__/new-task.test.ts,src/engine/__tests__/switch-mode.test.ts", "clone c9648e69e45e src/orchestrator/__tests__/cli.test.ts", "clone c9726dcfaf3b src/engine/local-explore.ts,src/llm/ollama.ts", - "clone c9fe2d9273f4 src/codeintel/__tests__/implementations.test.ts,src/orchestrator/__tests__/main-tree-tripwire.test.ts,src/orchestrator/__tests__/review-gates.test.ts", "clone ca31b6bb0f50 src/budget/__tests__/concurrency.test.ts,src/watcher/__tests__/watch.test.ts", "clone cb208607f464 src/dashboard/server.ts", "clone cb54ce2ca134 src/orchestrator/__tests__/cli.test.ts,src/orchestrator/__tests__/resume.test.ts", @@ -783,10 +715,7 @@ "clone cdff5c34ea22 src/orchestrator/task-files.ts", "clone ce27f097c90c src/rsi/__tests__/controller-candidates.test.ts,src/rsi/__tests__/controller-fleet-results.test.ts", "clone ce4455f3e99f src/orchestrator/task-files.ts", - "clone ce8ca6626552 src/engine/__tests__/global-modes.test.ts,src/engine/__tests__/prompt.test.ts,src/engine/__tests__/stack-rules-content.test.ts,src/engine/__tests__/stacks.test.ts,src/llm/__tests__/muse.test.ts,src/llm/__tests__/openrouter.test.ts,src/stack-rules-content.test.ts", - "clone cebb36184bf4 src/codesearch/__tests__/backend-selection.test.ts,src/codesearch/__tests__/index.test.ts,src/codesearch/__tests__/search.test.ts,src/config/__tests__/mode-models.test.ts,src/dashboard/__tests__/file-browser-permissions.test.ts,src/dashboard/__tests__/settings.test.ts,src/orchestrator/__tests__/cli.test.ts,src/orchestrator/__tests__/model-resolution.test.ts,src/orchestrator/__tests__/resume.test.ts,src/orchestrator/__tests__/spawn-model.test.ts,src/orchestrator/__tests__/watch.test.ts,src/permissions/__tests__/protected-files.test.ts,src/tools/__tests__/codebase_search.test.ts", "clone cee648fd9d03 src/engine/__tests__/new-task.test.ts,src/engine/__tests__/switch-mode.test.ts", - "clone cf91fc906ce8 src/engine/__tests__/global-modes.test.ts,src/engine/__tests__/prompt.test.ts,src/engine/__tests__/stack-rules-content.test.ts,src/engine/__tests__/stacks.test.ts,src/llm/__tests__/muse.test.ts,src/llm/__tests__/openrouter.test.ts", "clone cfbc5b2083c1 src/engine/local-explore.ts,src/llm/ollama.ts", "clone d07fbc6b767f src/tools/__tests__/edit_file.test.ts,src/tools/__tests__/search_replace.test.ts", "clone d08cb9f5e40b src/dashboard/__tests__/projects-routes.test.ts,src/projects/__tests__/cli.test.ts", @@ -828,7 +757,6 @@ "clone d8dfa6d1a846 src/orchestrator/cli.ts", "clone d8e97665e9ec src/checkpoints/__tests__/cli.test.ts,src/codemap/__tests__/cli.test.ts,src/dashboard/__tests__/cli.test.ts", "clone d9003b766f88 src/dashboard/__tests__/server.test.ts,src/dashboard/__tests__/session-launch.test.ts", - "clone d95677f0fc38 src/codeintel/__tests__/implementations.test.ts,src/orchestrator/__tests__/main-tree-tripwire.test.ts,src/orchestrator/__tests__/review-gates.test.ts", "clone d96d4c124839 src/dashboard/trend-cli.ts,src/orchestrator/cleanup.ts", "clone d9ab3b61fdbc src/llm/preflight.ts", "clone d9cbef8f79d1 src/engine/local-explore.ts,src/llm/ollama.ts", @@ -842,7 +770,6 @@ "clone dc983745440d src/llm/muse.ts,src/llm/openrouter.ts", "clone dcf2b502a3dd src/github/__tests__/pr.test.ts,src/orchestrator/__tests__/merge-check.test.ts", "clone dd0c493b152f src/tools/__tests__/execute_command.test.ts", - "clone dda5b65e734c src/engine/__tests__/prompt.test.ts,src/llm/__tests__/muse.test.ts,src/llm/__tests__/openrouter.test.ts", "clone dde98d818073 src/cloud/provider.ts,src/orchestrator/cli.ts", "clone ddf5d3691b74 src/checkpoints/cli.ts,src/cli.ts,src/codemap/cli.ts,src/codesearch/cli.ts,src/dashboard/cli.ts,src/dashboard/trend-cli.ts,src/decision-proxy/cli.ts,src/github/cli.ts,src/init/cli.ts,src/orchestrator/cleanup.ts,src/watcher/cli.ts", "clone de0450878713 src/llm/muse.ts,src/llm/openrouter.ts", @@ -853,7 +780,6 @@ "clone df2e214d885a src/engine/__tests__/loop.test.ts", "clone dfe022ad1548 src/orchestrator/cli.ts", "clone e010fc53c9f4 src/engine/loop.ts", - "clone e023eaa49b35 src/tools/__tests__/apply_diff.test.ts,src/tools/__tests__/edit_file.test.ts,src/tools/__tests__/search_replace.test.ts", "clone e0baba4315c3 src/orchestrator/cli.ts,src/orchestrator/resume.ts,src/watcher/cli.ts", "clone e0f1e0a708cc src/orchestrator/__tests__/spawn-model.test.ts,src/orchestrator/__tests__/spawn-state-reset.test.ts", "clone e1591587b296 src/engine/local-explore.ts,src/llm/ollama.ts", @@ -862,7 +788,6 @@ "clone e217ea15e165 src/orchestrator/task-files.ts", "clone e24148d569bb src/github/__tests__/installations.test.ts,src/orchestrator/__tests__/merge-check.test.ts", "clone e244829eb955 src/orchestrator/cli.ts,src/orchestrator/resume.ts", - "clone e29c9dd71a32 src/codeintel/__tests__/call-hierarchy.test.ts,src/codeintel/__tests__/find-references.test.ts,src/codeintel/__tests__/import-graph.test.ts", "clone e2f7c4138acf src/llm/muse.ts,src/llm/openrouter.ts", "clone e2f849efd7f2 src/cloud/docker-provider.ts,src/cloud/provider.ts", "clone e34ac968803e src/engine/__tests__/inject-message.test.ts,src/engine/__tests__/pause.test.ts", @@ -875,16 +800,13 @@ "clone e416c30b4843 src/llm/preflight.ts", "clone e498eb38bb29 src/orchestrator/resume.ts", "clone e4c0c632d2e8 src/engine/__tests__/condense.test.ts", - "clone e4cd97daa489 src/__tests__/cli-env.test.ts,src/codesearch/__tests__/embedder-types.test.ts,src/evidence/__tests__/verify-files.test.ts,src/orchestrator/__tests__/task-files.test.ts,src/orchestrator/__tests__/terminal-status.test.ts,src/tools/__tests__/batch-types.test.ts,src/tools/__tests__/paths.test.ts", "clone e4f3cf1fa942 src/rsi/__tests__/controller-candidates.test.ts,src/rsi/__tests__/controller-fleet-results.test.ts", "clone e5b435a1ec6d src/llm/openrouter.ts", "clone e5c94c55dadd src/orchestrator/__tests__/spawn-model.test.ts,src/orchestrator/__tests__/spawn-state-reset.test.ts", "clone e5e1bcaff8b1 src/orchestrator/__tests__/spawn-model.test.ts,src/orchestrator/__tests__/spawn-state-reset.test.ts", "clone e627e238091f src/engine/__tests__/condense.test.ts", "clone e670cea95cf5 src/llm/muse.ts", - "clone e69e899590f4 src/checkpoints/__tests__/cli.test.ts,src/codemap/__tests__/cli.test.ts,src/codemap/__tests__/units.test.ts,src/dashboard/__tests__/cli.test.ts,src/dashboard/__tests__/page-route.test.ts,src/dashboard/__tests__/self-improvement-metrics.test.ts,src/dashboard/__tests__/sigint-shutdown.test.ts,src/dashboard/__tests__/trend.test.ts,src/github/__tests__/installations.test.ts,src/orchestrator/__tests__/analyze-cli.test.ts,src/orchestrator/__tests__/cost-estimate.test.ts,src/orchestrator/__tests__/log-analysis.test.ts,src/orchestrator/__tests__/spawn-state-reset.test.ts,src/orchestrator/__tests__/status.test.ts,src/run-tests-script.test.ts,src/tools/__tests__/set-indentation.test.ts", "clone e6c233c3ca88 src/llm/muse.ts,src/llm/openrouter.ts", - "clone e76f1d15c039 src/tools/__tests__/apply_diff.test.ts,src/tools/__tests__/edit_file.test.ts,src/tools/__tests__/search_replace.test.ts", "clone e7c40e42987c src/orchestrator/task-files.ts", "clone e8c2bd5a02ed src/engine/__tests__/new-task.test.ts,src/engine/__tests__/switch-mode.test.ts", "clone e8c3d99ff835 src/engine/__tests__/inject-message.test.ts,src/engine/__tests__/pause.test.ts", @@ -913,12 +835,10 @@ "clone ef26fdd9523d src/llm/preflight.ts", "clone ef55d3fea271 src/dashboard/__tests__/cost-history-routes.test.ts,src/orchestrator/__tests__/cost-history-cli.test.ts", "clone ef70ff0a9386 src/engine/local-explore.ts,src/llm/ollama.ts", - "clone ef71bbe16ec5 src/codeintel/__tests__/registration.test.ts,src/tools/__tests__/batch.test.ts", "clone f01f54f65b50 src/codesearch/files.ts,src/engine/stacks.ts", "clone f075d1e0c08a src/orchestrator/resume.ts", "clone f0ce557b48e0 src/watcher/watch.ts", "clone f0f979cf4060 src/llm/muse.ts,src/llm/openrouter.ts", - "clone f0fbb3826fed src/codeintel/__tests__/registration.test.ts,src/tools/__tests__/batch.test.ts", "clone f14b65dc96ac src/engine/__tests__/loop.test.ts,src/tools/__tests__/execute_command.test.ts", "clone f22d61a091e1 src/engine/__tests__/condense.test.ts", "clone f2684b8a8b7a src/permissions/commands.ts", @@ -936,10 +856,10 @@ "clone f602bc3ce671 src/orchestrator/__tests__/cli.test.ts,src/orchestrator/__tests__/resume.test.ts", "clone f687d6368f68 src/llm/muse.ts", "clone f69d991fe89e src/orchestrator/__tests__/cli.test.ts", - "clone f6a98fae1855 src/engine/__tests__/new-task.test.ts,src/engine/__tests__/switch-mode.test.ts", "clone f6c0de337c8b src/orchestrator/resume.ts", "clone f6ed5475b786 src/orchestrator/task-files.ts", "clone f730e676e214 src/orchestrator/task-files.ts", + "clone f7e01a037e17 src/codesearch/__tests__/backend-selection.test.ts,src/codesearch/__tests__/index.test.ts,src/codesearch/__tests__/search.test.ts,src/config/__tests__/mode-models.test.ts,src/dashboard/__tests__/file-browser-permissions.test.ts,src/dashboard/__tests__/settings.test.ts,src/orchestrator/__tests__/cli.test.ts,src/orchestrator/__tests__/cost-history-cli.test.ts,src/orchestrator/__tests__/cost-history.test.ts,src/orchestrator/__tests__/model-resolution.test.ts,src/orchestrator/__tests__/resume.test.ts,src/orchestrator/__tests__/spawn-model.test.ts,src/orchestrator/__tests__/watch.test.ts,src/permissions/__tests__/protected-files.test.ts,src/tools/__tests__/codebase_search.test.ts", "clone f83833b6f3b1 src/engine/loop.ts", "clone f876220170d5 src/orchestrator/__tests__/analyze-cli.test.ts", "clone f8a191f41b2a src/orchestrator/__tests__/cli.test.ts,src/orchestrator/__tests__/resume.test.ts", @@ -953,7 +873,6 @@ "clone fa817379580e src/checkpoints/__tests__/cli.test.ts,src/codemap/__tests__/cli.test.ts,src/dashboard/__tests__/cli.test.ts", "clone facb47dc5d4e src/llm/muse.ts,src/llm/openrouter.ts", "clone faf040a5922d src/orchestrator/task-files.ts", - "clone fbc6a19ebf9a src/__tests__/cli-env.test.ts,src/codesearch/__tests__/embedder-types.test.ts,src/evidence/__tests__/verify-files.test.ts,src/orchestrator/__tests__/task-files.test.ts,src/orchestrator/__tests__/terminal-status.test.ts,src/tools/__tests__/batch-types.test.ts,src/tools/__tests__/paths.test.ts", "clone fc018e6a7371 src/orchestrator/__tests__/cli.test.ts", "clone fc35fa2848fb src/codesearch/files.ts,src/engine/stacks.ts", "clone fc4aa9a9577b src/dashboard/__tests__/projects-routes.test.ts,src/projects/__tests__/cli.test.ts", @@ -1025,9 +944,6 @@ "src/codemap/extract.ts console 1e0b59d485f1", "src/codemap/extract.ts console bece561cd9ec", "src/codemap/flows.ts complexity 8005c8ba0de4", - "src/codesearch/__tests__/backend-selection.test.ts nesting 5a1db74d0b54", - "src/codesearch/__tests__/index.test.ts nesting 5a1db74d0b54", - "src/codesearch/__tests__/search.test.ts nesting 5a1db74d0b54", "src/codesearch/airunner-embedder.ts complexity 0fdf477e9e4b", "src/codesearch/chunk.ts nesting 0521a2813b3b", "src/codesearch/cli.ts complexity 0f0bb1f16669", @@ -1040,7 +956,6 @@ "src/codesearch/index.ts complexity f2fd182e4c58", "src/codesearch/lexical.ts complexity 7f089b589f5d", "src/codesearch/ollama-embedder.ts complexity 0fdf477e9e4b", - "src/config/__tests__/mode-models.test.ts nesting 5a1db74d0b54", "src/config/mode-models.ts complexity 54346a7c8ba3", "src/config/mode-models.ts complexity 81355004b185", "src/config/mode-models.ts nesting 54346a7c8ba3", @@ -1050,8 +965,6 @@ "src/control/emergency-controller.ts nesting 71308ea5154c", "src/control/session-runtime.ts complexity d35d302346c1", "src/control/session-runtime.ts nesting d35d302346c1", - "src/dashboard/__tests__/file-browser-permissions.test.ts nesting 5a1db74d0b54", - "src/dashboard/__tests__/settings.test.ts nesting 5a1db74d0b54", "src/dashboard/chat-thread.ts complexity 7a1720a83e64", "src/dashboard/checkpoints.ts complexity 80236917ad55", "src/dashboard/cli.ts complexity b22fbbebd876", @@ -1185,14 +1098,7 @@ "src/memory/__tests__/uwuchat.test.ts nesting 2d2e45536b71", "src/memory/summarizer.ts nesting 7cee04a9645b", "src/monitoring/features.ts complexity 24d38f3a6c14", - "src/orchestrator/__tests__/cli.test.ts nesting 5a1db74d0b54", - "src/orchestrator/__tests__/cost-history-cli.test.ts nesting 5a1db74d0b54", - "src/orchestrator/__tests__/cost-history.test.ts nesting 5a1db74d0b54", - "src/orchestrator/__tests__/model-resolution.test.ts nesting 5a1db74d0b54", "src/orchestrator/__tests__/muse-executor.test.ts complexity 8e2ad4b546b7", - "src/orchestrator/__tests__/resume.test.ts nesting 5a1db74d0b54", - "src/orchestrator/__tests__/spawn-model.test.ts nesting 5a1db74d0b54", - "src/orchestrator/__tests__/watch.test.ts nesting 5a1db74d0b54", "src/orchestrator/analyze-cli.ts complexity aa16ab05bcec", "src/orchestrator/analyze-cli.ts nesting aa16ab05bcec", "src/orchestrator/cleanup.ts complexity 192b651b4c9a", @@ -1249,7 +1155,6 @@ "src/orchestrator/watch.ts complexity 4b74c496e627", "src/orchestrator/watch.ts complexity 5ef3a7678de8", "src/orchestrator/watch.ts complexity 73c5129596ac", - "src/permissions/__tests__/protected-files.test.ts nesting 5a1db74d0b54", "src/permissions/commands.ts complexity 603320ccaacb", "src/permissions/commands.ts complexity 88bfec36eee2", "src/permissions/commands.ts complexity a59b0df8679a", @@ -1503,8 +1408,14 @@ "src/terminal/chat.ts nesting a0d2a773413a", "src/terminal/input.ts complexity 12599998b43a", "src/terminal/renderer.ts complexity 7f5ba673c15f", + "src/test-runner.ts console 820c4507ed73", + "src/test-runner.ts console 8d8544d9af06", + "src/test-runner.ts console a8f563293e4f", + "src/test-runner.ts console c770bfde2dce", + "src/test-runner.ts console e17350daf90f", + "src/test-runner.ts console e450d263e401", + "src/test-runner.ts process-exit cbde29b17391", "src/tools/__tests__/browser-close-backstop.test.ts complexity d96a5159e95f", - "src/tools/__tests__/codebase_search.test.ts nesting 5a1db74d0b54", "src/tools/browser/handler.ts complexity 564b954e5e1c", "src/tools/browser/handler.ts nesting 564b954e5e1c", "src/tools/browser/service.ts complexity 79f179fd0b89", diff --git a/src/__tests__/cli-env.test.ts b/src/__tests__/cli-env.test.ts index 552d216..59eed41 100644 --- a/src/__tests__/cli-env.test.ts +++ b/src/__tests__/cli-env.test.ts @@ -19,6 +19,7 @@ import { envBooleanValue as cliEnvBooleanValue, resolvePerModeEnv as cliResolvePerModeEnv, } from "../cli.js" +import { runMain } from "../test-runner.js" async function testPerModeOverrideWins(): Promise { const env = { HEADLESSCODE_FOO: "global", HEADLESSCODE_FOO__CODE: "per-mode" } @@ -60,40 +61,13 @@ async function testCliReexportsTheLeaf(): Promise { assert.equal(cliEnvBoolean, envBoolean) } -async function runOne(name: string, fn: () => Promise): Promise { - try { - await fn() - console.log(` ok ${name}`) - return true - } catch (error) { - console.error(` FAIL ${name}`) - if (error instanceof Error) console.error(error.stack ?? error) - else console.error(String(error)) - return false - } -} - -async function main(): Promise { - const tests: Array<[string, () => Promise]> = [ - ["per-mode override wins", testPerModeOverrideWins], - ["global falls back", testGlobalFallsBack], - ["mode slug normalization", testModeSlugNormalization], - ["missing is undefined", testMissingIsUndefined], - ["env boolean values", testEnvBooleanValues], - ["cli re-exports the leaf", testCliReexportsTheLeaf], - ] - let failed = 0 - for (const [name, fn] of tests) { - if (!(await runOne(name, fn))) failed++ - } - if (failed > 0) { - console.error(`\n${failed} test(s) failed`) - process.exit(1) - } - console.log(`\nAll ${tests.length} cli-env tests passed`) -} +const tests: Array<[string, () => Promise]> = [ + ["per-mode override wins", testPerModeOverrideWins], + ["global falls back", testGlobalFallsBack], + ["mode slug normalization", testModeSlugNormalization], + ["missing is undefined", testMissingIsUndefined], + ["env boolean values", testEnvBooleanValues], + ["cli re-exports the leaf", testCliReexportsTheLeaf], +] -main().catch((error) => { - console.error("Test runner crashed:", error) - process.exit(1) -}) +runMain("cli-env", tests) diff --git a/src/budget/__tests__/budget.test.ts b/src/budget/__tests__/budget.test.ts index cdbf6ab..044d3e2 100644 --- a/src/budget/__tests__/budget.test.ts +++ b/src/budget/__tests__/budget.test.ts @@ -19,6 +19,7 @@ import { type SessionBudget, } from "../budget.js" import type { PricingTable } from "../cost.js" +import { runMain } from "../../test-runner.js" // Cheap model for deterministic math: $1/1M in, $2/1M out → 1000 in + 1000 out // = (1000*1 + 1000*2)/1e6 = $0.003 per call. @@ -346,26 +347,4 @@ const tests: Array<[string, () => Promise]> = [ ["session: iterations cap trips before the next LLM call", testSessionBudgetIterationsCap], ] -async function main(): Promise { - let failed = 0 - for (const [name, fn] of tests) { - try { - await fn() - console.log(` ok ${name}`) - } catch (err) { - failed++ - console.error(` FAIL ${name}`) - console.error(err instanceof Error ? err.stack ?? err.message : String(err)) - } - } - if (failed > 0) { - console.error(`\n${failed} test(s) failed`) - process.exit(1) - } - console.log(`\nAll ${tests.length} budget tests passed`) -} - -main().catch((err) => { - console.error("Test runner crashed:", err) - process.exit(1) -}) +runMain("budget", tests) diff --git a/src/budget/__tests__/concurrency.test.ts b/src/budget/__tests__/concurrency.test.ts index f340746..9d65778 100644 --- a/src/budget/__tests__/concurrency.test.ts +++ b/src/budget/__tests__/concurrency.test.ts @@ -24,6 +24,7 @@ import { saveWatcherState, type WatcherState } from "../../watcher/state.js" import { watchIssues, type SpawnBatch, type WatcherConfig } from "../../watcher/watch.js" import { loadWatcherState } from "../../watcher/state.js" import type { GhClient, GitHubIssue } from "../../watcher/github.js" +import { runMain } from "../../test-runner.js" const SILENT = new Logger({ level: "silent" }) @@ -260,26 +261,4 @@ const tests: Array<[string, () => Promise]> = [ ["watcher: pre-existing running fleet at cap -> all new issues deferred", testWatcherGlobalCapRespectsActiveFleet], ] -async function main(): Promise { - let failed = 0 - for (const [name, fn] of tests) { - try { - await fn() - console.log(` ok ${name}`) - } catch (err) { - failed++ - console.error(` FAIL ${name}`) - console.error(err instanceof Error ? err.stack ?? err.message : String(err)) - } - } - if (failed > 0) { - console.error(`\n${failed} test(s) failed`) - process.exit(1) - } - console.log(`\nAll ${tests.length} concurrency tests passed`) -} - -main().catch((err) => { - console.error("Test runner crashed:", err) - process.exit(1) -}) +runMain("concurrency", tests) diff --git a/src/budget/__tests__/cost.test.ts b/src/budget/__tests__/cost.test.ts index 69578b2..11101ef 100644 --- a/src/budget/__tests__/cost.test.ts +++ b/src/budget/__tests__/cost.test.ts @@ -21,6 +21,7 @@ import { type EndpointPricingEntry, type PricingTable, } from "../cost.js" +import { runMain } from "../../test-runner.js" /** * Regression test for a real incident (2026-08-01): `deepseek/deepseek-v4-flash` @@ -356,26 +357,4 @@ const tests: Array<[string, () => Promise]> = [ ["mergeLivePrice overrides the hardcoded entry, feeds estimateCost", testMergeLivePriceOverridesDefaults], ] -async function main(): Promise { - let failed = 0 - for (const [name, fn] of tests) { - try { - await fn() - console.log(` ok ${name}`) - } catch (err) { - failed++ - console.error(` FAIL ${name}`) - console.error(err instanceof Error ? err.stack ?? err.message : String(err)) - } - } - if (failed > 0) { - console.error(`\n${failed} test(s) failed`) - process.exit(1) - } - console.log(`\nAll ${tests.length} cost tests passed`) -} - -main().catch((err) => { - console.error("Test runner crashed:", err) - process.exit(1) -}) +runMain("cost", tests) diff --git a/src/checkpoints/__tests__/cli.test.ts b/src/checkpoints/__tests__/cli.test.ts index 249f50c..9ae7759 100644 --- a/src/checkpoints/__tests__/cli.test.ts +++ b/src/checkpoints/__tests__/cli.test.ts @@ -16,6 +16,7 @@ import * as path from "node:path" import { checkpointsMain, parseCheckpointsArgs } from "../cli.js" import { createCheckpointService } from "../service.js" +import { runMain } from "../../test-runner.js" async function tmpRepo(prefix = "hc-ckpt-cli-"): Promise { return fs.mkdtemp(path.join(os.tmpdir(), prefix)) @@ -223,26 +224,4 @@ const tests: Array<[string, () => void | Promise]> = [ ["checkpointsMain: diff against the working tree exits 0", testMainDiffAgainstWorkingTree], ] -async function main(): Promise { - let failed = 0 - for (const [name, fn] of tests) { - try { - await fn() - console.log(` ok ${name}`) - } catch (err) { - failed++ - console.error(` FAIL ${name}`) - console.error(err instanceof Error ? (err.stack ?? err.message) : String(err)) - } - } - if (failed > 0) { - console.error(`\n${failed} test(s) failed`) - process.exit(1) - } - console.log(`\nAll ${tests.length} checkpoints CLI tests passed`) -} - -main().catch((err) => { - console.error("Test runner crashed:", err) - process.exit(1) -}) +runMain("checkpoints CLI", tests) diff --git a/src/checkpoints/__tests__/service.test.ts b/src/checkpoints/__tests__/service.test.ts index e171dc3..5e4b0ea 100644 --- a/src/checkpoints/__tests__/service.test.ts +++ b/src/checkpoints/__tests__/service.test.ts @@ -15,6 +15,7 @@ import * as os from "node:os" import * as path from "node:path" import { createCheckpointService } from "../service.js" +import { runMain } from "../../test-runner.js" async function mkTmpWorkspace(prefix: string): Promise { return fs.mkdtemp(path.join(os.tmpdir(), prefix)) @@ -198,26 +199,4 @@ const tests: Array<[string, () => Promise]> = [ ["diff: reports before/after content for a changed file", testDiffReportsChangedFile], ] -async function main(): Promise { - let failed = 0 - for (const [name, fn] of tests) { - try { - await fn() - console.log(` ok ${name}`) - } catch (err) { - failed++ - console.error(` FAIL ${name}`) - console.error(err instanceof Error ? err.stack ?? err.message : String(err)) - } - } - if (failed > 0) { - console.error(`\n${failed} test(s) failed`) - process.exit(1) - } - console.log(`\nAll ${tests.length} checkpoints tests passed`) -} - -main().catch((err) => { - console.error("Test runner crashed:", err) - process.exit(1) -}) +runMain("checkpoints", tests) diff --git a/src/cloud/__tests__/docker-provider.test.ts b/src/cloud/__tests__/docker-provider.test.ts index 0c96b98..bb340eb 100644 --- a/src/cloud/__tests__/docker-provider.test.ts +++ b/src/cloud/__tests__/docker-provider.test.ts @@ -32,6 +32,7 @@ import * as path from "node:path" import { DockerSessionProvider } from "../docker-provider.js" import type { CloudSessionRequest, CommandResult, SessionHandle } from "../provider.js" +import { runSuites } from "../../test-runner.js" // ─── Docker availability probe ─────────────────────────────────────────────── @@ -302,18 +303,8 @@ if (probe.ok) { async function main(): Promise { console.log(` note: ${dockerNote}`) - let failed = 0 let skipped = 0 - for (const [name, fn] of tests) { - try { - await fn() - console.log(` ok ${name}`) - } catch (err) { - failed++ - console.error(` FAIL ${name}`) - console.error(err instanceof Error ? err.stack ?? err.message : String(err)) - } - } + const failed = await runSuites("docker-provider", tests) if (!probe.ok) { skipped = 2 console.log(` SKIP ${skipped} real-daemon test(s) — ${probe.reason}`) @@ -322,7 +313,7 @@ async function main(): Promise { console.error(`\n${failed} test(s) failed`) process.exit(1) } - console.log(`\nAll ${tests.length} docker-provider tests passed (${skipped} skipped: no Docker daemon)`) + console.log(` note: ${skipped} skipped (no Docker daemon)`) } main().catch((err) => { diff --git a/src/cloud/__tests__/provider.test.ts b/src/cloud/__tests__/provider.test.ts index 4746bee..822b9a0 100644 --- a/src/cloud/__tests__/provider.test.ts +++ b/src/cloud/__tests__/provider.test.ts @@ -26,6 +26,7 @@ import { type CommandResult, type SessionHandle, } from "../provider.js" +import { runMain } from "../../test-runner.js" async function tmpRepo(): Promise { return fs.mkdtemp(path.join(os.tmpdir(), "headlesscode-provider-repo-")) @@ -188,26 +189,4 @@ const tests: Array<[string, () => Promise]> = [ ["HetznerDockerProvider is a documented sketch (not implemented)", testHetznerSketchIsDocumentedNotImplemented], ] -async function main(): Promise { - let failed = 0 - for (const [name, fn] of tests) { - try { - await fn() - console.log(` ok ${name}`) - } catch (err) { - failed++ - console.error(` FAIL ${name}`) - console.error(err instanceof Error ? err.stack ?? err.message : String(err)) - } - } - if (failed > 0) { - console.error(`\n${failed} test(s) failed`) - process.exit(1) - } - console.log(`\nAll ${tests.length} provider tests passed`) -} - -main().catch((err) => { - console.error("Test runner crashed:", err) - process.exit(1) -}) +runMain("provider", tests) diff --git a/src/codeintel/__tests__/call-graph.test.ts b/src/codeintel/__tests__/call-graph.test.ts index 8df7eed..e9626ba 100644 --- a/src/codeintel/__tests__/call-graph.test.ts +++ b/src/codeintel/__tests__/call-graph.test.ts @@ -18,6 +18,7 @@ import * as path from "node:path" import { getCallGraph } from "../call-graph.js" import { getCodeIntelCache, resetCodeIntelCaches } from "../program.js" +import { runMain } from "../../test-runner.js" const FIXTURE_A = `import { greet, Greeter } from "./b.js" @@ -101,30 +102,8 @@ async function testCallTargetsResolveToDefiningFile(): Promise { } } -async function main(): Promise { - const tests: Array<[string, () => Promise]> = [ - ["call targets resolve to their defining file via the TS checker", testCallTargetsResolveToDefiningFile], - ] - - let failed = 0 - for (const [name, fn] of tests) { - try { - await fn() - console.log(` ok ${name}`) - } catch (err) { - failed++ - console.error(` FAIL ${name}`) - console.error(err instanceof Error ? err.stack ?? err.message : String(err)) - } - } - if (failed > 0) { - console.error(`\n${failed} test(s) failed`) - process.exit(1) - } - console.log(`\nAll ${tests.length} call-graph tests passed`) -} +const tests: Array<[string, () => Promise]> = [ + ["call targets resolve to their defining file via the TS checker", testCallTargetsResolveToDefiningFile], +] -main().catch((err) => { - console.error("Test runner crashed:", err) - process.exit(1) -}) +runMain("call-graph", tests) diff --git a/src/codeintel/__tests__/call-hierarchy.test.ts b/src/codeintel/__tests__/call-hierarchy.test.ts index f8a98c1..26f48bf 100644 --- a/src/codeintel/__tests__/call-hierarchy.test.ts +++ b/src/codeintel/__tests__/call-hierarchy.test.ts @@ -18,6 +18,7 @@ import * as path from "node:path" import type { ToolContext } from "../../engine/types.js" import { resetCodeIntelCaches } from "../program.js" import { callHierarchyHandler } from "../call-hierarchy.js" +import { runMain } from "../../test-runner.js" // Line numbers below are 1-based and VERIFIED against the fixture text: // leaf=1, mid=5 (calls leaf on 6 and 7), top=10 (calls mid on 11), @@ -202,37 +203,15 @@ async function testNonTsFile(): Promise { } } -async function main(): Promise { - const tests: Array<[string, () => Promise]> = [ - ["direct caller found from a definition", testDirectCallerFound], - ["caller lookup works from a call site", testCallerViaCallSite], - ["direct callees found", testDirectCalleesFound], - ["callee lookup from a call site starts at the selected callee", testCalleesFromCallSiteStartAtSelectedCallee], - ["default direction returns both sides", testBothDirectionsDefault], - ["max_depth=1 excludes depth-2 edges", testMaxDepthPrunesDeeperEdges], - ["any-typed call is tolerated", testAnyTypedCallTolerated], - ["non-TS file returns the not-supported result", testNonTsFile], - ] - - let failed = 0 - for (const [name, fn] of tests) { - try { - await fn() - console.log(` ok ${name}`) - } catch (err) { - failed++ - console.error(` FAIL ${name}`) - console.error(err instanceof Error ? err.stack ?? err.message : String(err)) - } - } - if (failed > 0) { - console.error(`\n${failed} test(s) failed`) - process.exit(1) - } - console.log(`\nAll ${tests.length} call_hierarchy tests passed`) -} - -main().catch((err) => { - console.error("Test runner crashed:", err) - process.exit(1) -}) +const tests: Array<[string, () => Promise]> = [ + ["direct caller found from a definition", testDirectCallerFound], + ["caller lookup works from a call site", testCallerViaCallSite], + ["direct callees found", testDirectCalleesFound], + ["callee lookup from a call site starts at the selected callee", testCalleesFromCallSiteStartAtSelectedCallee], + ["default direction returns both sides", testBothDirectionsDefault], + ["max_depth=1 excludes depth-2 edges", testMaxDepthPrunesDeeperEdges], + ["any-typed call is tolerated", testAnyTypedCallTolerated], + ["non-TS file returns the not-supported result", testNonTsFile], +] + +runMain("call_hierarchy", tests) diff --git a/src/codeintel/__tests__/find-references.test.ts b/src/codeintel/__tests__/find-references.test.ts index 0c194b0..4695d79 100644 --- a/src/codeintel/__tests__/find-references.test.ts +++ b/src/codeintel/__tests__/find-references.test.ts @@ -23,6 +23,7 @@ import * as path from "node:path" import { createHeadlessExecutor } from "../../tools/executor.js" import { resetCodeIntelCaches } from "../program.js" import { MAX_REFERENCES } from "../find-references.js" +import { runMain } from "../../test-runner.js" const FIXTURE_UTIL = `export function util(): number { return 1 @@ -139,33 +140,11 @@ async function testNonTsFile(): Promise { } } -async function main(): Promise { - const tests: Array<[string, () => Promise]> = [ - ["returns exact reference sites for a symbol with few usages", testExactReferenceSites], - ["caps output on a symbol with many usages and announces the overflow", testCapOnManyUsages], - ["no-resolvable-references gets a clear message", testNoReferences], - ["non-TS file returns the not-supported result", testNonTsFile], - ] - - let failed = 0 - for (const [name, fn] of tests) { - try { - await fn() - console.log(` ok ${name}`) - } catch (err) { - failed++ - console.error(` FAIL ${name}`) - console.error(err instanceof Error ? err.stack ?? err.message : String(err)) - } - } - if (failed > 0) { - console.error(`\n${failed} test(s) failed`) - process.exit(1) - } - console.log(`\nAll ${tests.length} find_references tests passed`) -} +const tests: Array<[string, () => Promise]> = [ + ["returns exact reference sites for a symbol with few usages", testExactReferenceSites], + ["caps output on a symbol with many usages and announces the overflow", testCapOnManyUsages], + ["no-resolvable-references gets a clear message", testNoReferences], + ["non-TS file returns the not-supported result", testNonTsFile], +] -main().catch((err) => { - console.error("Test runner crashed:", err) - process.exit(1) -}) +runMain("find_references", tests) diff --git a/src/codeintel/__tests__/go-to-definition.test.ts b/src/codeintel/__tests__/go-to-definition.test.ts index 9f426ad..8e73c90 100644 --- a/src/codeintel/__tests__/go-to-definition.test.ts +++ b/src/codeintel/__tests__/go-to-definition.test.ts @@ -22,6 +22,7 @@ import * as path from "node:path" import { createHeadlessExecutor } from "../../tools/executor.js" import { resetCodeIntelCaches } from "../program.js" +import { runMain } from "../../test-runner.js" // Line numbers below are 1-based and VERIFIED against the fixture text: // add=6, PI=10, first overload=18, second overload=19, loose=24, @@ -216,36 +217,14 @@ async function testMissingFile(): Promise { } } -async function main(): Promise { - const tests: Array<[string, () => Promise]> = [ - ["plain function resolves to its declaration", testPlainFunctionDefinition], - ["character disambiguates symbols on one line", testCharacterDisambiguation], - ["definition in a .d.ts via tsconfig paths alias resolves", testDefinitionInDtsThroughPathsAlias], - ["overloaded function returns ALL definitions", testOverloadReturnsAllDefinitions], - ["unresolvable (any-typed) symbol gets a clear message", testUnresolvableSymbol], - ["non-TS file returns the not-supported result", testNonTsFile], - ["missing file errors clearly", testMissingFile], - ] - - let failed = 0 - for (const [name, fn] of tests) { - try { - await fn() - console.log(` ok ${name}`) - } catch (err) { - failed++ - console.error(` FAIL ${name}`) - console.error(err instanceof Error ? err.stack ?? err.message : String(err)) - } - } - if (failed > 0) { - console.error(`\n${failed} test(s) failed`) - process.exit(1) - } - console.log(`\nAll ${tests.length} go_to_definition tests passed`) -} - -main().catch((err) => { - console.error("Test runner crashed:", err) - process.exit(1) -}) +const tests: Array<[string, () => Promise]> = [ + ["plain function resolves to its declaration", testPlainFunctionDefinition], + ["character disambiguates symbols on one line", testCharacterDisambiguation], + ["definition in a .d.ts via tsconfig paths alias resolves", testDefinitionInDtsThroughPathsAlias], + ["overloaded function returns ALL definitions", testOverloadReturnsAllDefinitions], + ["unresolvable (any-typed) symbol gets a clear message", testUnresolvableSymbol], + ["non-TS file returns the not-supported result", testNonTsFile], + ["missing file errors clearly", testMissingFile], +] + +runMain("go_to_definition", tests) diff --git a/src/codeintel/__tests__/hover.test.ts b/src/codeintel/__tests__/hover.test.ts index 1a44c7d..3f062e3 100644 --- a/src/codeintel/__tests__/hover.test.ts +++ b/src/codeintel/__tests__/hover.test.ts @@ -15,6 +15,7 @@ import * as path from "node:path" import type { ToolContext } from "../../engine/types.js" import { typeAtPositionHandler } from "../hover.js" import { resetCodeIntelCaches } from "../program.js" +import { runMain } from "../../test-runner.js" // Line numbers below are 1-based and VERIFIED against the fixture text: // lib.ts: doc=1-6, add=7, greeting=11. main.ts: import=1, sum=3, shout=5. @@ -151,37 +152,15 @@ async function testNoIdentifierGraceful(): Promise { } } -async function main(): Promise { - const tests: Array<[string, () => Promise]> = [ - ["inferred-type const shows its type text", testInferredConstType], - ["function use shows the full signature", testFunctionSignature], - [ - "import specifier resolves through the import", - testImportSpecifierResolves, - ], - ["docs and JSDoc tags follow the signature", testDocsAndTags], - ["blank spot yields the graceful message", testNoIdentifierGraceful], - ] - - let failed = 0 - for (const [name, fn] of tests) { - try { - await fn() - console.log(` ok ${name}`) - } catch (err) { - failed++ - console.error(` FAIL ${name}`) - console.error(err instanceof Error ? err.stack ?? err.message : String(err)) - } - } - if (failed > 0) { - console.error(`\n${failed} test(s) failed`) - process.exit(1) - } - console.log(`\nAll ${tests.length} type_at_position tests passed`) -} - -main().catch((err) => { - console.error("Test runner crashed:", err) - process.exit(1) -}) +const tests: Array<[string, () => Promise]> = [ + ["inferred-type const shows its type text", testInferredConstType], + ["function use shows the full signature", testFunctionSignature], + [ + "import specifier resolves through the import", + testImportSpecifierResolves, + ], + ["docs and JSDoc tags follow the signature", testDocsAndTags], + ["blank spot yields the graceful message", testNoIdentifierGraceful], +] + +runMain("type_at_position", tests) diff --git a/src/codeintel/__tests__/implementations.test.ts b/src/codeintel/__tests__/implementations.test.ts index 878e7ec..6c337eb 100644 --- a/src/codeintel/__tests__/implementations.test.ts +++ b/src/codeintel/__tests__/implementations.test.ts @@ -19,6 +19,7 @@ import * as path from "node:path" import type { ToolContext } from "../../engine/types.js" import { resetCodeIntelCaches } from "../program.js" import { findImplementationsHandler } from "../implementations.js" +import { runMain } from "../../test-runner.js" // Line numbers below are 1-based and VERIFIED against the fixture text: // Shape=1, area=2, Circle=5, Circle.area=6, Square=11, Square.area=12, @@ -198,35 +199,12 @@ async function testNonTsFile(): Promise { } } -async function main(): Promise { - const tests: Array<[string, () => Promise]> = [ - ["interface method finds class methods", testInterfaceMethodImplementations], - ["base method finds the subclass override", testSubclassOverride], - ["empty spot gets the graceful message", testNoImplementationsMessage], - ["symbol arg disambiguates one line", testSymbolArgDisambiguation], - ["non-TS file is refused, not guessed", testNonTsFile], - ] - - let failed = 0 - for (const [name, fn] of tests) { - try { - await fn() - console.log(` ok ${name}`) - } catch (err) { - failed++ - console.error(` FAIL ${name}`) - const detail = err instanceof Error ? err.stack ?? err.message : String(err) - console.error(detail) - } - } - if (failed > 0) { - console.error(`\n${failed} test(s) failed`) - process.exit(1) - } - console.log(`\nAll ${tests.length} find_implementations tests passed`) -} +const tests: Array<[string, () => Promise]> = [ + ["interface method finds class methods", testInterfaceMethodImplementations], + ["base method finds the subclass override", testSubclassOverride], + ["empty spot gets the graceful message", testNoImplementationsMessage], + ["symbol arg disambiguates one line", testSymbolArgDisambiguation], + ["non-TS file is refused, not guessed", testNonTsFile], +] -main().catch((err) => { - console.error("Test runner crashed:", err) - process.exit(1) -}) +runMain("find_implementations", tests) diff --git a/src/codeintel/__tests__/import-graph.test.ts b/src/codeintel/__tests__/import-graph.test.ts index 1709ff5..04c5d4c 100644 --- a/src/codeintel/__tests__/import-graph.test.ts +++ b/src/codeintel/__tests__/import-graph.test.ts @@ -19,6 +19,7 @@ import * as path from "node:path" import { createHeadlessExecutor } from "../../tools/executor.js" import { getCodeIntelCache, resetCodeIntelCaches } from "../program.js" +import { runMain } from "../../test-runner.js" const FIXTURE_A = `import { b1 } from "./b.js" import type { c1 } from "./c.js" @@ -146,33 +147,11 @@ async function testNonTsFile(): Promise { } } -async function main(): Promise { - const tests: Array<[string, () => Promise]> = [ - ["imports resolve to real file paths (incl. type-only and unresolved)", testImportsResolvedToRealPaths], - ["reverse lookup sees type imports and re-exports", testReverseLookupIncludingReExportAndTypeImport], - ["program cache is reused across calls and invalidated on file change", testProgramCacheReusedAcrossCalls], - ["non-TS file returns the not-supported result", testNonTsFile], - ] - - let failed = 0 - for (const [name, fn] of tests) { - try { - await fn() - console.log(` ok ${name}`) - } catch (err) { - failed++ - console.error(` FAIL ${name}`) - console.error(err instanceof Error ? err.stack ?? err.message : String(err)) - } - } - if (failed > 0) { - console.error(`\n${failed} test(s) failed`) - process.exit(1) - } - console.log(`\nAll ${tests.length} import_graph tests passed`) -} +const tests: Array<[string, () => Promise]> = [ + ["imports resolve to real file paths (incl. type-only and unresolved)", testImportsResolvedToRealPaths], + ["reverse lookup sees type imports and re-exports", testReverseLookupIncludingReExportAndTypeImport], + ["program cache is reused across calls and invalidated on file change", testProgramCacheReusedAcrossCalls], + ["non-TS file returns the not-supported result", testNonTsFile], +] -main().catch((err) => { - console.error("Test runner crashed:", err) - process.exit(1) -}) +runMain("import_graph", tests) diff --git a/src/codeintel/__tests__/outline.test.ts b/src/codeintel/__tests__/outline.test.ts index 6d378d3..808852f 100644 --- a/src/codeintel/__tests__/outline.test.ts +++ b/src/codeintel/__tests__/outline.test.ts @@ -19,6 +19,7 @@ import * as path from "node:path" import { createHeadlessExecutor } from "../../tools/executor.js" import { resetCodeIntelCaches } from "../program.js" +import { runMain } from "../../test-runner.js" const FIXTURE_MATH = `export interface Point { x: number @@ -155,34 +156,12 @@ async function testOutlinePathTraversalRejected(): Promise { } } -async function main(): Promise { - const tests: Array<[string, () => Promise]> = [ - ["outline returns the exact fixture structure", testOutlineExactStructure], - ["outline signatures exclude bodies and duplicate keywords", testOutlineSignatureHasNoBody], - ["outline of a missing file errors", testOutlineMissingFile], - ["outline of a non-TS file returns the not-supported error", testOutlineUnsupportedFileType], - ["outline rejects paths escaping the workspace", testOutlinePathTraversalRejected], - ] - - let failed = 0 - for (const [name, fn] of tests) { - try { - await fn() - console.log(` ok ${name}`) - } catch (err) { - failed++ - console.error(` FAIL ${name}`) - console.error(err instanceof Error ? err.stack ?? err.message : String(err)) - } - } - if (failed > 0) { - console.error(`\n${failed} test(s) failed`) - process.exit(1) - } - console.log(`\nAll ${tests.length} outline tests passed`) -} +const tests: Array<[string, () => Promise]> = [ + ["outline returns the exact fixture structure", testOutlineExactStructure], + ["outline signatures exclude bodies and duplicate keywords", testOutlineSignatureHasNoBody], + ["outline of a missing file errors", testOutlineMissingFile], + ["outline of a non-TS file returns the not-supported error", testOutlineUnsupportedFileType], + ["outline rejects paths escaping the workspace", testOutlinePathTraversalRejected], +] -main().catch((err) => { - console.error("Test runner crashed:", err) - process.exit(1) -}) +runMain("outline", tests) diff --git a/src/codeintel/__tests__/registration.test.ts b/src/codeintel/__tests__/registration.test.ts index 2b94874..afc3aa5 100644 --- a/src/codeintel/__tests__/registration.test.ts +++ b/src/codeintel/__tests__/registration.test.ts @@ -19,6 +19,7 @@ import * as path from "node:path" import { createHeadlessExecutor } from "../../tools/executor.js" import { resetCodeIntelCaches } from "../program.js" import { CODE_INTEL_TOOL_NAMES } from "../tools.js" +import { runMain } from "../../test-runner.js" // Line numbers below are 1-based and VERIFIED against the fixture text: // Shape=1, area=2, Circle=5, Circle.area=6, double=11, quadruple=15 @@ -185,45 +186,20 @@ async function testAdvertisedInToolList(): Promise { } } -async function main(): Promise { - const tests: Array<[string, () => Promise]> = [ - ["all five tools are registered", testAllRegistered], - ["get_diagnostics runs via executor", testDiagnosticsThroughExecutor], - ["type_at_position runs via executor", testHoverThroughExecutor], - [ - "workspace_symbol runs via executor", - testWorkspaceSymbolThroughExecutor, - ], - [ - "find_implementations runs via executor", - testImplementationsThroughExecutor, - ], - ["call_hierarchy runs via executor", testCallHierarchyThroughExecutor], - ["all five are in CODE_INTEL_TOOLS", testAdvertisedInToolList], - ] - - let failed = 0 - for (const [name, fn] of tests) { - try { - await fn() - console.log(` ok ${name}`) - } catch (err) { - failed++ - console.error(` FAIL ${name}`) - const detail = err instanceof Error - ? (err.stack ?? err.message) - : String(err) - console.error(detail) - } - } - if (failed > 0) { - console.error(`\n${failed} test(s) failed`) - process.exit(1) - } - console.log(`\nAll ${tests.length} registration tests passed`) -} +const tests: Array<[string, () => Promise]> = [ + ["all five tools are registered", testAllRegistered], + ["get_diagnostics runs via executor", testDiagnosticsThroughExecutor], + ["type_at_position runs via executor", testHoverThroughExecutor], + [ + "workspace_symbol runs via executor", + testWorkspaceSymbolThroughExecutor, + ], + [ + "find_implementations runs via executor", + testImplementationsThroughExecutor, + ], + ["call_hierarchy runs via executor", testCallHierarchyThroughExecutor], + ["all five are in CODE_INTEL_TOOLS", testAdvertisedInToolList], +] -main().catch((err) => { - console.error("Test runner crashed:", err) - process.exit(1) -}) +runMain("registration", tests) diff --git a/src/codeintel/__tests__/rename-symbol.test.ts b/src/codeintel/__tests__/rename-symbol.test.ts index 90e0e9e..a507615 100644 --- a/src/codeintel/__tests__/rename-symbol.test.ts +++ b/src/codeintel/__tests__/rename-symbol.test.ts @@ -21,6 +21,7 @@ import * as path from "node:path" import { createHeadlessExecutor } from "../../tools/executor.js" import { getCodeIntelCache, resetCodeIntelCaches } from "../program.js" import { renameSymbol } from "../rename-symbol.js" +import { runMain } from "../../test-runner.js" const FIXTURE_UTIL = `export function util(): number { return 1 @@ -189,33 +190,11 @@ async function testMidWriteFailureRollsBackWrittenFiles(): Promise { } } -async function main(): Promise { - const tests: Array<[string, () => Promise]> = [ - ["renames every real site across files and leaves lookalike identifiers untouched", testRenameAcrossAllSites], - ["invalid new name is refused with nothing changed", testInvalidNewNameLeavesEverythingUntouched], - ["stale snapshot aborts before any write (no partial edits)", testStaleSnapshotAbortsWithNoPartialWrites], - ["mid-write failure rolls back the files already written", testMidWriteFailureRollsBackWrittenFiles], - ] +const tests: Array<[string, () => Promise]> = [ + ["renames every real site across files and leaves lookalike identifiers untouched", testRenameAcrossAllSites], + ["invalid new name is refused with nothing changed", testInvalidNewNameLeavesEverythingUntouched], + ["stale snapshot aborts before any write (no partial edits)", testStaleSnapshotAbortsWithNoPartialWrites], + ["mid-write failure rolls back the files already written", testMidWriteFailureRollsBackWrittenFiles], +] - let failed = 0 - for (const [name, fn] of tests) { - try { - await fn() - console.log(` ok ${name}`) - } catch (err) { - failed++ - console.error(` FAIL ${name}`) - console.error(err instanceof Error ? err.stack ?? err.message : String(err)) - } - } - if (failed > 0) { - console.error(`\n${failed} test(s) failed`) - process.exit(1) - } - console.log(`\nAll ${tests.length} rename_symbol tests passed`) -} - -main().catch((err) => { - console.error("Test runner crashed:", err) - process.exit(1) -}) +runMain("rename_symbol", tests) diff --git a/src/codemap/__tests__/build.test.ts b/src/codemap/__tests__/build.test.ts index 7e62245..8528bca 100644 --- a/src/codemap/__tests__/build.test.ts +++ b/src/codemap/__tests__/build.test.ts @@ -30,6 +30,7 @@ import { execFileSync } from "node:child_process" import { buildCodemap } from "../build.js" import { codemapDir, codemapJsonPath, codemapLockPath, codemapHtmlPath, loadLock, loadCodemap } from "../lock.js" import { resetCodeIntelCaches } from "../../codeintel/program.js" +import { runMain } from "../../test-runner.js" const TSCONFIG = JSON.stringify( { @@ -273,35 +274,13 @@ async function testHtmlIsSelfContained(): Promise { } } -async function main(): Promise { - const tests: Array<[string, () => Promise]> = [ - ["TS/JS modules get resolved import edges + correct roles", testTypeScriptEdgesAndRoles], - ["Python modules get AST-subprocess import edges", testPythonEdges], - ["C/C++ modules get regex include edges", testCppIncludeEdges], - ["TS/JS call edges resolve cross-module calls + entrypoint flows", testCallEdgesAndEntrypointFlows], - ["regeneration is fingerprint-aware (no writes when unchanged)", testFingerprintAwareRegeneration], - ["html visualizer is self-contained and embeds the map", testHtmlIsSelfContained], - ] +const tests: Array<[string, () => Promise]> = [ + ["TS/JS modules get resolved import edges + correct roles", testTypeScriptEdgesAndRoles], + ["Python modules get AST-subprocess import edges", testPythonEdges], + ["C/C++ modules get regex include edges", testCppIncludeEdges], + ["TS/JS call edges resolve cross-module calls + entrypoint flows", testCallEdgesAndEntrypointFlows], + ["regeneration is fingerprint-aware (no writes when unchanged)", testFingerprintAwareRegeneration], + ["html visualizer is self-contained and embeds the map", testHtmlIsSelfContained], +] - let failed = 0 - for (const [name, fn] of tests) { - try { - await fn() - console.log(` ok ${name}`) - } catch (err) { - failed++ - console.error(` FAIL ${name}`) - console.error(err instanceof Error ? err.stack ?? err.message : String(err)) - } - } - if (failed > 0) { - console.error(`\n${failed} test(s) failed`) - process.exit(1) - } - console.log(`\nAll ${tests.length} codemap build tests passed`) -} - -main().catch((err) => { - console.error("Test runner crashed:", err) - process.exit(1) -}) +runMain("codemap build", tests) diff --git a/src/codemap/__tests__/cli.test.ts b/src/codemap/__tests__/cli.test.ts index af64c93..74861cf 100644 --- a/src/codemap/__tests__/cli.test.ts +++ b/src/codemap/__tests__/cli.test.ts @@ -23,6 +23,7 @@ import { execFileSync } from "node:child_process" import { codemapMain, parseCodemapArgs } from "../cli.js" import { codemapJsonPath } from "../lock.js" import { resetCodeIntelCaches } from "../../codeintel/program.js" +import { runMain } from "../../test-runner.js" function gitInit(root: string): void { execFileSync("git", ["init", "-q"], { cwd: root, stdio: "ignore" }) @@ -157,26 +158,4 @@ const tests: Array<[string, () => void | Promise]> = [ ["codemapMain: --force always regenerates", testMainForceAlwaysRegenerates], ] -async function main(): Promise { - let failed = 0 - for (const [name, fn] of tests) { - try { - await fn() - console.log(` ok ${name}`) - } catch (err) { - failed++ - console.error(` FAIL ${name}`) - console.error(err instanceof Error ? (err.stack ?? err.message) : String(err)) - } - } - if (failed > 0) { - console.error(`\n${failed} test(s) failed`) - process.exit(1) - } - console.log(`\nAll ${tests.length} codemap CLI tests passed`) -} - -main().catch((err) => { - console.error("Test runner crashed:", err) - process.exit(1) -}) +runMain("codemap CLI", tests) diff --git a/src/codemap/__tests__/units.test.ts b/src/codemap/__tests__/units.test.ts index 3f65a5f..4d5d2bb 100644 --- a/src/codemap/__tests__/units.test.ts +++ b/src/codemap/__tests__/units.test.ts @@ -21,6 +21,7 @@ import { codemapFingerprint, contentHash, fingerprintMap, lockMatches } from ".. import { computeEntrypointFlows } from "../flows.js" import { classifyRole, languageForExtension, statLines } from "../files.js" import type { Codemap, CodemapLock, ModuleEntry } from "../types.js" +import { runMain } from "../../test-runner.js" // ─── fingerprint.ts ────────────────────────────────────────────────────────── @@ -207,26 +208,4 @@ const tests: Array<[string, () => void | Promise]> = [ ["files: statLines counts bytes/lines and degrades to zeros on a missing file", testStatLinesCountsLinesAndBytes], ] -async function main(): Promise { - let failed = 0 - for (const [name, fn] of tests) { - try { - await fn() - console.log(` ok ${name}`) - } catch (err) { - failed++ - console.error(` FAIL ${name}`) - console.error(err instanceof Error ? (err.stack ?? err.message) : String(err)) - } - } - if (failed > 0) { - console.error(`\n${failed} test(s) failed`) - process.exit(1) - } - console.log(`\nAll ${tests.length} codemap unit tests passed`) -} - -main().catch((err) => { - console.error("Test runner crashed:", err) - process.exit(1) -}) +runMain("codemap unit", tests) diff --git a/src/codesearch/__tests__/airunner-embedder.test.ts b/src/codesearch/__tests__/airunner-embedder.test.ts index 333e420..da2df18 100644 --- a/src/codesearch/__tests__/airunner-embedder.test.ts +++ b/src/codesearch/__tests__/airunner-embedder.test.ts @@ -27,6 +27,7 @@ import { resolveAirunnerEmbeddingModel, resolveAirunnerUrl, } from "../airunner-embedder.js" +import { runMain } from "../../test-runner.js" /** Deterministic fake vector for a text (dim 8), so tests can assert content. */ function fakeVector(text: string): number[] { @@ -239,26 +240,4 @@ const tests: Array<[string, () => Promise]> = [ ["AIRunner URL/model env vars resolve with sensible defaults", testEnvResolution], ] -async function main(): Promise { - let failed = 0 - for (const [name, fn] of tests) { - try { - await fn() - console.log(` ok ${name}`) - } catch (err) { - failed++ - console.error(` FAIL ${name}`) - console.error(err instanceof Error ? err.stack ?? err.message : String(err)) - } - } - if (failed > 0) { - console.error(`\n${failed} test(s) failed`) - process.exit(1) - } - console.log(`\nAll ${tests.length} airunner-embedder tests passed`) -} - -main().catch((err) => { - console.error("Test runner crashed:", err) - process.exit(1) -}) +runMain("airunner-embedder", tests) diff --git a/src/codesearch/__tests__/backend-selection.test.ts b/src/codesearch/__tests__/backend-selection.test.ts index 520847b..be5a83e 100644 --- a/src/codesearch/__tests__/backend-selection.test.ts +++ b/src/codesearch/__tests__/backend-selection.test.ts @@ -33,6 +33,7 @@ import { AirunnerEmbedder } from "../airunner-embedder.js" import { OllamaEmbedder } from "../ollama-embedder.js" import { OpenRouterEmbedder } from "../embedder.js" import { createHeadlessExecutor } from "../../tools/executor.js" +import { runSuites } from "../../test-runner.js" async function mkTmpWorkspace(prefix: string): Promise { return fs.mkdtemp(path.join(os.tmpdir(), prefix)) @@ -250,16 +251,7 @@ async function main(): Promise { process.env.HEADLESSCODE_DATA_DIR = storeTmp let failed = 0 try { - for (const [name, fn] of tests) { - try { - await fn() - console.log(` ok ${name}`) - } catch (err) { - failed++ - console.error(` FAIL ${name}`) - console.error(err instanceof Error ? err.stack ?? err.message : String(err)) - } - } + failed = await runSuites("backend-selection", tests) } finally { delete process.env.HEADLESSCODE_DATA_DIR await fs.rm(storeTmp, { recursive: true, force: true }) @@ -268,7 +260,6 @@ async function main(): Promise { console.error(`\n${failed} test(s) failed`) process.exit(1) } - console.log(`\nAll ${tests.length} backend-selection tests passed`) } main().catch((err) => { diff --git a/src/codesearch/__tests__/chunk.test.ts b/src/codesearch/__tests__/chunk.test.ts index 047f293..98ccf62 100644 --- a/src/codesearch/__tests__/chunk.test.ts +++ b/src/codesearch/__tests__/chunk.test.ts @@ -12,6 +12,7 @@ import * as path from "node:path" import { MAX_CHUNK_CHARS } from "../embedder.js" import { chunkFile } from "../chunk.js" +import { runMain } from "../../test-runner.js" async function mkTmpWorkspace(files: Record): Promise { const dir = await fs.mkdtemp(path.join(os.tmpdir(), "hc-chunk-")) @@ -322,26 +323,4 @@ const tests: Array<[string, () => Promise]> = [ ["gitignored + binary files are skipped (walker integration)", testSkipsBinaryAndGitignoredFiles], ] -async function main(): Promise { - let failed = 0 - for (const [name, fn] of tests) { - try { - await fn() - console.log(` ok ${name}`) - } catch (err) { - failed++ - console.error(` FAIL ${name}`) - console.error(err instanceof Error ? err.stack ?? err.message : String(err)) - } - } - if (failed > 0) { - console.error(`\n${failed} test(s) failed`) - process.exit(1) - } - console.log(`\nAll ${tests.length} chunk tests passed`) -} - -main().catch((err) => { - console.error("Test runner crashed:", err) - process.exit(1) -}) +runMain("chunk", tests) diff --git a/src/codesearch/__tests__/embedder-types.test.ts b/src/codesearch/__tests__/embedder-types.test.ts index 149696a..89a5780 100644 --- a/src/codesearch/__tests__/embedder-types.test.ts +++ b/src/codesearch/__tests__/embedder-types.test.ts @@ -17,6 +17,7 @@ import { AirunnerEmbedder } from "../airunner-embedder.js" import { createEmbedder, OpenRouterEmbedder } from "../embedder.js" import type { Embedder, EmbedResult } from "../embedder-types.js" import { OllamaEmbedder } from "../ollama-embedder.js" +import { runMain } from "../../test-runner.js" async function testBackendsSatisfyTheSharedInterface(): Promise { const url = "http://localhost:1" @@ -50,37 +51,10 @@ async function testBackendsRejectEmptyInput(): Promise { await assert.rejects(() => airunner.embedBatch([""]), /empty/) } -async function runOne(name: string, fn: () => Promise): Promise { - try { - await fn() - console.log(` ok ${name}`) - return true - } catch (error) { - console.error(` FAIL ${name}`) - if (error instanceof Error) console.error(error.stack ?? error) - else console.error(String(error)) - return false - } -} - -async function main(): Promise { - const tests: Array<[string, () => Promise]> = [ - ["backends satisfy interface", testBackendsSatisfyTheSharedInterface], - ["factory selects backends", testFactorySelectsBackends], - ["backends reject empty input", testBackendsRejectEmptyInput], - ] - let failed = 0 - for (const [name, fn] of tests) { - if (!(await runOne(name, fn))) failed++ - } - if (failed > 0) { - console.error(`\n${failed} test(s) failed`) - process.exit(1) - } - console.log(`\nAll ${tests.length} embedder-types tests passed`) -} +const tests: Array<[string, () => Promise]> = [ + ["backends satisfy interface", testBackendsSatisfyTheSharedInterface], + ["factory selects backends", testFactorySelectsBackends], + ["backends reject empty input", testBackendsRejectEmptyInput], +] -main().catch((error) => { - console.error("Test runner crashed:", error) - process.exit(1) -}) +runMain("embedder-types", tests) diff --git a/src/codesearch/__tests__/files.test.ts b/src/codesearch/__tests__/files.test.ts index 2af5f21..cb9a59e 100644 --- a/src/codesearch/__tests__/files.test.ts +++ b/src/codesearch/__tests__/files.test.ts @@ -13,6 +13,7 @@ import * as path from "node:path" import { promisify } from "node:util" import { walkSourceFiles } from "../files.js" +import { runMain } from "../../test-runner.js" const execFileP = promisify(execFile) @@ -90,26 +91,4 @@ const tests: Array<[string, () => Promise]> = [ ["exclusion matches exact path segments only, not partial directory names", testDoesNotOverMatchPartialNames], ] -async function main(): Promise { - let failed = 0 - for (const [name, fn] of tests) { - try { - await fn() - console.log(` ok ${name}`) - } catch (err) { - failed++ - console.error(` FAIL ${name}`) - console.error(err instanceof Error ? err.stack ?? err.message : String(err)) - } - } - if (failed > 0) { - console.error(`\n${failed} test(s) failed`) - process.exit(1) - } - console.log(`\nAll ${tests.length} files tests passed`) -} - -main().catch((err) => { - console.error("Test runner crashed:", err) - process.exit(1) -}) +runMain("files", tests) diff --git a/src/codesearch/__tests__/index.test.ts b/src/codesearch/__tests__/index.test.ts index e11f27b..1a061ba 100644 --- a/src/codesearch/__tests__/index.test.ts +++ b/src/codesearch/__tests__/index.test.ts @@ -19,6 +19,7 @@ import { buildIndex, chunkHash, indexFilePath, loadIndex, readJsonlEntries, writ import type { Embedder } from "../embedder.js" import type { IndexEntry } from "../types.js" import { walkSourceFiles } from "../files.js" +import { runSuites } from "../../test-runner.js" /** Deterministic fake embedder with an embed-call counter. */ class FakeEmbedder implements Embedder { @@ -346,16 +347,7 @@ async function main(): Promise { process.env.HEADLESSCODE_DATA_DIR = storeTmp let failed = 0 try { - for (const [name, fn] of tests) { - try { - await fn() - console.log(` ok ${name}`) - } catch (err) { - failed++ - console.error(` FAIL ${name}`) - console.error(err instanceof Error ? err.stack ?? err.message : String(err)) - } - } + failed = await runSuites("index", tests) } finally { delete process.env.HEADLESSCODE_DATA_DIR await fs.rm(storeTmp, { recursive: true, force: true }) @@ -364,7 +356,6 @@ async function main(): Promise { console.error(`\n${failed} test(s) failed`) process.exit(1) } - console.log(`\nAll ${tests.length} index tests passed`) } main().catch((err) => { diff --git a/src/codesearch/__tests__/ollama-embedder.test.ts b/src/codesearch/__tests__/ollama-embedder.test.ts index e539c59..541d20b 100644 --- a/src/codesearch/__tests__/ollama-embedder.test.ts +++ b/src/codesearch/__tests__/ollama-embedder.test.ts @@ -27,6 +27,7 @@ import { resolveOllamaEmbeddingModel, resolveOllamaUrl, } from "../ollama-embedder.js" +import { runMain } from "../../test-runner.js" /** Deterministic fake vector for a text (dim 8), so tests can assert content. */ function fakeVector(text: string): number[] { @@ -220,26 +221,4 @@ const tests: Array<[string, () => Promise]> = [ ["Ollama URL/model env vars resolve with sensible defaults", testEnvResolution], ] -async function main(): Promise { - let failed = 0 - for (const [name, fn] of tests) { - try { - await fn() - console.log(` ok ${name}`) - } catch (err) { - failed++ - console.error(` FAIL ${name}`) - console.error(err instanceof Error ? err.stack ?? err.message : String(err)) - } - } - if (failed > 0) { - console.error(`\n${failed} test(s) failed`) - process.exit(1) - } - console.log(`\nAll ${tests.length} ollama-embedder tests passed`) -} - -main().catch((err) => { - console.error("Test runner crashed:", err) - process.exit(1) -}) +runMain("ollama-embedder", tests) diff --git a/src/codesearch/__tests__/search.test.ts b/src/codesearch/__tests__/search.test.ts index cb13505..bb77040 100644 --- a/src/codesearch/__tests__/search.test.ts +++ b/src/codesearch/__tests__/search.test.ts @@ -14,6 +14,7 @@ import * as path from "node:path" import { buildIndex } from "../index.js" import { formatSearchResults, searchIndex } from "../search.js" import type { Embedder } from "../embedder.js" +import { runSuites } from "../../test-runner.js" /** Deterministic fake embedder: vector[i] = count of the i-th letter. */ class FakeEmbedder implements Embedder { @@ -172,16 +173,7 @@ async function main(): Promise { process.env.HEADLESSCODE_DATA_DIR = storeTmp let failed = 0 try { - for (const [name, fn] of tests) { - try { - await fn() - console.log(` ok ${name}`) - } catch (err) { - failed++ - console.error(` FAIL ${name}`) - console.error(err instanceof Error ? err.stack ?? err.message : String(err)) - } - } + failed = await runSuites("search", tests) } finally { delete process.env.HEADLESSCODE_DATA_DIR await fs.rm(storeTmp, { recursive: true, force: true }) @@ -190,7 +182,6 @@ async function main(): Promise { console.error(`\n${failed} test(s) failed`) process.exit(1) } - console.log(`\nAll ${tests.length} search tests passed`) } main().catch((err) => { diff --git a/src/config/__tests__/mode-models.test.ts b/src/config/__tests__/mode-models.test.ts index d605337..c134254 100644 --- a/src/config/__tests__/mode-models.test.ts +++ b/src/config/__tests__/mode-models.test.ts @@ -20,6 +20,7 @@ import { validateModeModelsBody, DEFAULT_KEY, } from "../mode-models.js" +import { runSuites } from "../../test-runner.js" let storeTmp: string @@ -452,16 +453,7 @@ async function main(): Promise { process.env.HEADLESSCODE_DATA_DIR = storeTmp let failed = 0 try { - for (const [name, fn] of tests) { - try { - await fn() - console.log(` ok ${name}`) - } catch (err) { - failed++ - console.error(` FAIL ${name}`) - console.error(err instanceof Error ? err.stack ?? err.message : String(err)) - } - } + failed = await runSuites("mode-models", tests) } finally { delete process.env.HEADLESSCODE_DATA_DIR await fs.rm(storeTmp, { recursive: true, force: true }) @@ -470,7 +462,6 @@ async function main(): Promise { console.error(`\n${failed} test(s) failed`) process.exit(1) } - console.log(`\nAll ${tests.length} mode-models tests passed`) } main().catch((err) => { diff --git a/src/dashboard/__tests__/aggregate.test.ts b/src/dashboard/__tests__/aggregate.test.ts index f035e25..5eaa9e3 100644 --- a/src/dashboard/__tests__/aggregate.test.ts +++ b/src/dashboard/__tests__/aggregate.test.ts @@ -17,6 +17,7 @@ import * as path from "node:path" import { buildSummary, sumSessions } from "../aggregate.js" import type { LiveUsageRecord, UsageRecord } from "../../engine/usage.js" import { saveState, type OrchestratorState } from "../../orchestrator/state.js" +import { runMain } from "../../test-runner.js" async function tmpRepo(prefix = "hc-dashboard-"): Promise { return fs.mkdtemp(path.join(os.tmpdir(), prefix)) @@ -388,26 +389,4 @@ const tests: Array<[string, () => Promise]> = [ ["sumSessions: empty input -> zeroed totals", testSumSessionsEmpty], ] -async function main(): Promise { - let failed = 0 - for (const [name, fn] of tests) { - try { - await fn() - console.log(` ok ${name}`) - } catch (err) { - failed++ - console.error(` FAIL ${name}`) - console.error(err instanceof Error ? err.stack ?? err.message : String(err)) - } - } - if (failed > 0) { - console.error(`\n${failed} test(s) failed`) - process.exit(1) - } - console.log(`\nAll ${tests.length} dashboard aggregate tests passed`) -} - -main().catch((err) => { - console.error("Test runner crashed:", err) - process.exit(1) -}) +runMain("dashboard aggregate", tests) diff --git a/src/dashboard/__tests__/chat-thread.test.ts b/src/dashboard/__tests__/chat-thread.test.ts index 699a82f..c0ecb5a 100644 --- a/src/dashboard/__tests__/chat-thread.test.ts +++ b/src/dashboard/__tests__/chat-thread.test.ts @@ -13,6 +13,7 @@ import assert from "node:assert/strict" import { groupEventsIntoChat, summarizeArg } from "../chat-thread.js" import type { EventRecord } from "../../engine/events.js" +import { runMain } from "../../test-runner.js" function event(type: string, extra: Record = {}): EventRecord { return { ts: "2026-08-01T00:00:00.000Z", sessionId: "session-1", type, ...extra } @@ -278,26 +279,4 @@ const tests: Array<[string, () => Promise]> = [ ["chat-thread: summarizeArg shortens long args, keeps short ones", testSummarizeArg], ] -async function main(): Promise { - let failed = 0 - for (const [name, fn] of tests) { - try { - await fn() - console.log(` ok ${name}`) - } catch (err) { - failed++ - console.error(` FAIL ${name}`) - console.error(err instanceof Error ? err.stack ?? err.message : String(err)) - } - } - if (failed > 0) { - console.error(`\n${failed} test(s) failed`) - process.exit(1) - } - console.log(`\nAll ${tests.length} dashboard chat-thread tests passed`) -} - -main().catch((err) => { - console.error("Test runner crashed:", err) - process.exit(1) -}) +runMain("dashboard chat-thread", tests) diff --git a/src/dashboard/__tests__/checkpoints-routes.test.ts b/src/dashboard/__tests__/checkpoints-routes.test.ts index b4d2414..6ecc4c9 100644 --- a/src/dashboard/__tests__/checkpoints-routes.test.ts +++ b/src/dashboard/__tests__/checkpoints-routes.test.ts @@ -27,6 +27,7 @@ import * as path from "node:path" import { startDashboardServer } from "../server.js" import type { Server } from "node:http" import { createCheckpointService } from "../../checkpoints/service.js" +import { runMain } from "../../test-runner.js" async function tmpRepo(prefix = "hc-ckpt-routes-"): Promise { return fs.mkdtemp(path.join(os.tmpdir(), prefix)) @@ -341,26 +342,4 @@ const tests: Array<[string, () => Promise]> = [ ["checkpoints: POST restore validates body + method (400/405/500, never silent)", testRestoreValidatesBodyAndMethod], ] -async function main(): Promise { - let failed = 0 - for (const [name, fn] of tests) { - try { - await fn() - console.log(` ok ${name}`) - } catch (err) { - failed++ - console.error(` FAIL ${name}`) - console.error(err instanceof Error ? err.stack ?? err.message : String(err)) - } - } - if (failed > 0) { - console.error(`\n${failed} test(s) failed`) - process.exit(1) - } - console.log(`\nAll ${tests.length} dashboard checkpoints-routes tests passed`) -} - -main().catch((err) => { - console.error("Test runner crashed:", err) - process.exit(1) -}) +runMain("dashboard checkpoints-routes", tests) diff --git a/src/dashboard/__tests__/cli.test.ts b/src/dashboard/__tests__/cli.test.ts index 46c5868..edf4298 100644 --- a/src/dashboard/__tests__/cli.test.ts +++ b/src/dashboard/__tests__/cli.test.ts @@ -19,6 +19,7 @@ import assert from "node:assert/strict" import { dashboardMain, parseDashboardArgs } from "../cli.js" import { parseTrendArgs, trendMain } from "../trend-cli.js" +import { runMain } from "../../test-runner.js" /** Capture stdout+stderr written during `fn()`. */ async function captureOutput(fn: () => Promise): Promise<{ exit: number; stdout: string; stderr: string }> { @@ -134,26 +135,4 @@ const tests: Array<[string, () => void | Promise]> = [ ["trend: trendMain with a bad arg exits 2", testTrendMainBadArgExitsTwo], ] -async function main(): Promise { - let failed = 0 - for (const [name, fn] of tests) { - try { - await fn() - console.log(` ok ${name}`) - } catch (err) { - failed++ - console.error(` FAIL ${name}`) - console.error(err instanceof Error ? (err.stack ?? err.message) : String(err)) - } - } - if (failed > 0) { - console.error(`\n${failed} test(s) failed`) - process.exit(1) - } - console.log(`\nAll ${tests.length} dashboard/trend CLI tests passed`) -} - -main().catch((err) => { - console.error("Test runner crashed:", err) - process.exit(1) -}) +runMain("dashboard/trend CLI", tests) diff --git a/src/dashboard/__tests__/codemap-routes.test.ts b/src/dashboard/__tests__/codemap-routes.test.ts index eddc737..a80c601 100644 --- a/src/dashboard/__tests__/codemap-routes.test.ts +++ b/src/dashboard/__tests__/codemap-routes.test.ts @@ -17,6 +17,7 @@ import type { Server } from "node:http" import { startDashboardServer } from "../server.js" import { buildCodemap } from "../../codemap/build.js" import { resetCodeIntelCaches } from "../../codeintel/program.js" +import { runMain } from "../../test-runner.js" async function tmpRepo(prefix = "hc-codemap-route-"): Promise { const ws = await fsp.mkdtemp(path.join(os.tmpdir(), prefix)) @@ -119,33 +120,11 @@ async function testNoGeneratedMapIsClear404(): Promise { } } -async function main(): Promise { - const tests: Array<[string, () => Promise]> = [ - ["GET /api/codemap?repo= serves the stored codemap.json", testServesCodemapJson], - ["GET /api/codemap/html?repo= serves the visualizer", testServesCodemapHtml], - ["missing ?repo= is a 400", testMissingRepoParamIs400], - ["a repo with no generated map is a clear 404", testNoGeneratedMapIsClear404], - ] +const tests: Array<[string, () => Promise]> = [ + ["GET /api/codemap?repo= serves the stored codemap.json", testServesCodemapJson], + ["GET /api/codemap/html?repo= serves the visualizer", testServesCodemapHtml], + ["missing ?repo= is a 400", testMissingRepoParamIs400], + ["a repo with no generated map is a clear 404", testNoGeneratedMapIsClear404], +] - let failed = 0 - for (const [name, fn] of tests) { - try { - await fn() - console.log(` ok ${name}`) - } catch (err) { - failed++ - console.error(` FAIL ${name}`) - console.error(err instanceof Error ? err.stack ?? err.message : String(err)) - } - } - if (failed > 0) { - console.error(`\n${failed} test(s) failed`) - process.exit(1) - } - console.log(`\nAll ${tests.length} codemap dashboard-route tests passed`) -} - -main().catch((err) => { - console.error("Test runner crashed:", err) - process.exit(1) -}) +runMain("codemap dashboard-route", tests) diff --git a/src/dashboard/__tests__/cost-history-routes.test.ts b/src/dashboard/__tests__/cost-history-routes.test.ts index 31a6a3a..76e3d69 100644 --- a/src/dashboard/__tests__/cost-history-routes.test.ts +++ b/src/dashboard/__tests__/cost-history-routes.test.ts @@ -23,6 +23,7 @@ import { type CostHistoryRecord, type SessionCostRecord, } from "../../orchestrator/cost-history.js" +import { runMain } from "../../test-runner.js" async function tmpRepo(prefix = "hc-cost-history-route-"): Promise { return fsp.mkdtemp(path.join(os.tmpdir(), prefix)) @@ -237,39 +238,17 @@ async function testNoHistoryIsEmpty200(): Promise { } } -async function main(): Promise { - const tests: Array<[string, () => Promise]> = [ - ["GET /api/cost-history?repo= serves group + session records (newest first)", testServesGroupAndSessionRecords], - ["?since= windows by recordedAt", testSinceWindowsByRecordedAt], - [ - "?since= boundary: millisecond-precision recordedAt vs second-precision since includes the equal instant", - testSinceBoundaryMillisecondPrecisionIncludesEqualInstant, - ], - ["?limit=N keeps the most recent N", testLimitKeepsMostRecent], - ["missing ?repo= is a 400", testMissingRepoIs400], - ["invalid ?since= / ?limit= are 400s", testInvalidSinceAndLimitAre400], - ["a repo with no history is an empty 200", testNoHistoryIsEmpty200], - ] - - let failed = 0 - for (const [name, fn] of tests) { - try { - await fn() - console.log(` ok ${name}`) - } catch (err) { - failed++ - console.error(` FAIL ${name}`) - console.error(err instanceof Error ? err.stack ?? err.message : String(err)) - } - } - if (failed > 0) { - console.error(`\n${failed} test(s) failed`) - process.exit(1) - } - console.log(`\nAll ${tests.length} cost-history dashboard-route tests passed`) -} - -main().catch((err) => { - console.error("Test runner crashed:", err) - process.exit(1) -}) +const tests: Array<[string, () => Promise]> = [ + ["GET /api/cost-history?repo= serves group + session records (newest first)", testServesGroupAndSessionRecords], + ["?since= windows by recordedAt", testSinceWindowsByRecordedAt], + [ + "?since= boundary: millisecond-precision recordedAt vs second-precision since includes the equal instant", + testSinceBoundaryMillisecondPrecisionIncludesEqualInstant, + ], + ["?limit=N keeps the most recent N", testLimitKeepsMostRecent], + ["missing ?repo= is a 400", testMissingRepoIs400], + ["invalid ?since= / ?limit= are 400s", testInvalidSinceAndLimitAre400], + ["a repo with no history is an empty 200", testNoHistoryIsEmpty200], +] + +runMain("cost-history dashboard-route", tests) diff --git a/src/dashboard/__tests__/events.test.ts b/src/dashboard/__tests__/events.test.ts index c786537..b60ab22 100644 --- a/src/dashboard/__tests__/events.test.ts +++ b/src/dashboard/__tests__/events.test.ts @@ -15,6 +15,7 @@ import * as path from "node:path" import { readSessionEvents, findSessionEventsFile } from "../aggregate.js" import { eventsFilePath } from "../../engine/events.js" +import { runMain } from "../../test-runner.js" async function tmpRepo(prefix = "hc-dash-events-"): Promise { return fs.mkdtemp(path.join(os.tmpdir(), prefix)) @@ -190,26 +191,4 @@ const tests: Array<[string, () => Promise]> = [ ["readSessionEvents: offset beyond EOF clamps to the file size", testOffsetBeyondEOFClampsToFileEnd], ] -async function main(): Promise { - let failed = 0 - for (const [name, fn] of tests) { - try { - await fn() - console.log(` ok ${name}`) - } catch (err) { - failed++ - console.error(` FAIL ${name}`) - console.error(err instanceof Error ? err.stack ?? err.message : String(err)) - } - } - if (failed > 0) { - console.error(`\n${failed} test(s) failed`) - process.exit(1) - } - console.log(`\nAll ${tests.length} dashboard events tests passed`) -} - -main().catch((err) => { - console.error("Test runner crashed:", err) - process.exit(1) -}) +runMain("dashboard events", tests) diff --git a/src/dashboard/__tests__/file-browser-permissions.test.ts b/src/dashboard/__tests__/file-browser-permissions.test.ts index a5169d4..76f5dbf 100644 --- a/src/dashboard/__tests__/file-browser-permissions.test.ts +++ b/src/dashboard/__tests__/file-browser-permissions.test.ts @@ -29,6 +29,7 @@ import { startDashboardServer } from "../server.js" import type { Server } from "node:http" import { permissionsFilePath, resolvePermissions } from "../../permissions/config.js" import { DEFAULT_PROTECTED_FILES } from "../../permissions/protected-files.js" +import { runSuites } from "../../test-runner.js" async function tmpRepo(prefix = "hc-fileperm-"): Promise { return fs.mkdtemp(path.join(os.tmpdir(), prefix)) @@ -552,16 +553,10 @@ async function main(): Promise { process.env.HEADLESSCODE_DATA_DIR = storeTmp let failed = 0 try { - for (const [name, fn] of tests) { - try { - await fn() - console.log(` ok ${name}`) - } catch (err) { - failed++ - console.error(` FAIL ${name}`) - console.error(err instanceof Error ? err.stack ?? err.message : String(err)) - } - } + failed = await runSuites( + "dashboard file-browser + permissions settings", + tests, + ) } finally { delete process.env.HEADLESSCODE_DATA_DIR await fs.rm(storeTmp, { recursive: true, force: true }) @@ -570,7 +565,6 @@ async function main(): Promise { console.error(`\n${failed} test(s) failed`) process.exit(1) } - console.log(`\nAll ${tests.length} dashboard file-browser + permissions settings tests passed`) } main().catch((err) => { diff --git a/src/dashboard/__tests__/page-route.test.ts b/src/dashboard/__tests__/page-route.test.ts index 5559b6b..a339f8b 100644 --- a/src/dashboard/__tests__/page-route.test.ts +++ b/src/dashboard/__tests__/page-route.test.ts @@ -24,6 +24,7 @@ import * as path from "node:path" import { startDashboardServer } from "../server.js" import type { Server } from "node:http" +import { runMain } from "../../test-runner.js" async function tmpRepo(prefix = "hc-page-route-"): Promise { return fs.mkdtemp(path.join(os.tmpdir(), prefix)) @@ -139,26 +140,4 @@ const tests: Array<[string, () => Promise]> = [ ["GET / without --repo still renders, with an empty file-browser default", testGetRootWithoutRepoStillRendersWithEmptyDefault], ] -async function main(): Promise { - let failed = 0 - for (const [name, fn] of tests) { - try { - await fn() - console.log(` ok ${name}`) - } catch (err) { - failed++ - console.error(` FAIL ${name}`) - console.error(err instanceof Error ? (err.stack ?? err.message) : String(err)) - } - } - if (failed > 0) { - console.error(`\n${failed} test(s) failed`) - process.exit(1) - } - console.log(`\nAll ${tests.length} dashboard page-route tests passed`) -} - -main().catch((err) => { - console.error("Test runner crashed:", err) - process.exit(1) -}) +runMain("dashboard page-route", tests) diff --git a/src/dashboard/__tests__/projects-routes.test.ts b/src/dashboard/__tests__/projects-routes.test.ts index d0bdbef..84dcb1b 100644 --- a/src/dashboard/__tests__/projects-routes.test.ts +++ b/src/dashboard/__tests__/projects-routes.test.ts @@ -19,6 +19,7 @@ import * as path from "node:path" import type { Server } from "node:http" import { startDashboardServer } from "../server.js" +import { runMain } from "../../test-runner.js" async function mkTmp(prefix: string): Promise { return fsp.mkdtemp(path.join(os.tmpdir(), prefix)) @@ -202,26 +203,4 @@ const tests: Array<[string, () => Promise]> = [ ["GET /api/projects: gitOwnerName derived from origin remote", testGitOwnerNameDerivedFromOrigin], ] -async function main(): Promise { - let failed = 0 - for (const [name, fn] of tests) { - try { - await fn() - console.log(` ok ${name}`) - } catch (err) { - failed++ - console.error(` FAIL ${name}`) - console.error(err instanceof Error ? err.stack ?? err.message : String(err)) - } - } - if (failed > 0) { - console.error(`\n${failed} test(s) failed`) - process.exit(1) - } - console.log(`\nAll ${tests.length} projects-route tests passed`) -} - -main().catch((err) => { - console.error("Test runner crashed:", err) - process.exit(1) -}) +runMain("projects-route", tests) diff --git a/src/dashboard/__tests__/self-improvement-metrics.test.ts b/src/dashboard/__tests__/self-improvement-metrics.test.ts index dfa0f3e..3283522 100644 --- a/src/dashboard/__tests__/self-improvement-metrics.test.ts +++ b/src/dashboard/__tests__/self-improvement-metrics.test.ts @@ -30,6 +30,7 @@ import { extractIssueRefs, } from "../self-improvement-metrics.js" import type { SessionRow } from "../aggregate.js" +import { runMain } from "../../test-runner.js" const execFileP = promisify(execFile) @@ -199,26 +200,4 @@ const tests: Array<[string, () => Promise]> = [ ["computeIssueLifecycle: a repo with no GitHub remote degrades to undefined, never throws", testIssueLifecycleDegradesGracefullyWithoutGhRemote], ] -async function main(): Promise { - let failed = 0 - for (const [name, fn] of tests) { - try { - await fn() - console.log(` ok ${name}`) - } catch (err) { - failed++ - console.error(` FAIL ${name}`) - console.error(err instanceof Error ? (err.stack ?? err.message) : String(err)) - } - } - if (failed > 0) { - console.error(`\n${failed} test(s) failed`) - process.exit(1) - } - console.log(`\nAll ${tests.length} self-improvement-metrics tests passed`) -} - -main().catch((err) => { - console.error("Test runner crashed:", err) - process.exit(1) -}) +runMain("self-improvement-metrics", tests) diff --git a/src/dashboard/__tests__/server.test.ts b/src/dashboard/__tests__/server.test.ts index 8a7eab3..e59e2e5 100644 --- a/src/dashboard/__tests__/server.test.ts +++ b/src/dashboard/__tests__/server.test.ts @@ -17,6 +17,7 @@ import { setTimeout as sleep } from "node:timers/promises" import { startDashboardServer } from "../server.js" import type { Server } from "node:http" import { eventsFilePath } from "../../engine/events.js" +import { runMain } from "../../test-runner.js" async function tmpRepo(prefix = "hc-server-"): Promise { return fs.mkdtemp(path.join(os.tmpdir(), prefix)) @@ -359,26 +360,4 @@ const tests: Array<[string, () => Promise]> = [ ["server: non-GET/non-POST methods to unrelated routes are still rejected (405)", testNonGetPostMethodsStillRejected], ] -async function main(): Promise { - let failed = 0 - for (const [name, fn] of tests) { - try { - await fn() - console.log(` ok ${name}`) - } catch (err) { - failed++ - console.error(` FAIL ${name}`) - console.error(err instanceof Error ? err.stack ?? err.message : String(err)) - } - } - if (failed > 0) { - console.error(`\n${failed} test(s) failed`) - process.exit(1) - } - console.log(`\nAll ${tests.length} dashboard server tests passed`) -} - -main().catch((err) => { - console.error("Test runner crashed:", err) - process.exit(1) -}) +runMain("dashboard server", tests) diff --git a/src/dashboard/__tests__/session-launch.test.ts b/src/dashboard/__tests__/session-launch.test.ts index d600c13..66f0e4e 100644 --- a/src/dashboard/__tests__/session-launch.test.ts +++ b/src/dashboard/__tests__/session-launch.test.ts @@ -28,6 +28,7 @@ import type { Server } from "node:http" import { createHeadlessExecutor } from "../../tools/executor.js" import { eventsFilePath } from "../../engine/events.js" import { DECISION_MARKERS, validateSessionStartBody } from "../session-launch.js" +import { runMain } from "../../test-runner.js" async function tmpRepo(prefix = "hc-launch-"): Promise { return fs.mkdtemp(path.join(os.tmpdir(), prefix)) @@ -431,26 +432,4 @@ const tests: Array<[string, () => Promise]> = [ ["session-launch: optional bearer token gates control-plane POSTs, off by default", testTokenGatesControlPlanePosts], ] -async function main(): Promise { - let failed = 0 - for (const [name, fn] of tests) { - try { - await fn() - console.log(` ok ${name}`) - } catch (err) { - failed++ - console.error(` FAIL ${name}`) - console.error(err instanceof Error ? err.stack ?? err.message : String(err)) - } - } - if (failed > 0) { - console.error(`\n${failed} test(s) failed`) - process.exit(1) - } - console.log(`\nAll ${tests.length} session-launch tests passed`) -} - -main().catch((err) => { - console.error("Test runner crashed:", err) - process.exit(1) -}) +runMain("session-launch", tests) diff --git a/src/dashboard/__tests__/settings.test.ts b/src/dashboard/__tests__/settings.test.ts index 1ac27ec..509a923 100644 --- a/src/dashboard/__tests__/settings.test.ts +++ b/src/dashboard/__tests__/settings.test.ts @@ -15,6 +15,7 @@ import * as path from "node:path" import { startDashboardServer } from "../server.js" import type { Server } from "node:http" import { modeModelsFilePath } from "../../config/mode-models.js" +import { runSuites } from "../../test-runner.js" async function tmpRepo(prefix = "hc-settings-"): Promise { return fs.mkdtemp(path.join(os.tmpdir(), prefix)) @@ -240,16 +241,7 @@ async function main(): Promise { process.env.HEADLESSCODE_DATA_DIR = storeTmp let failed = 0 try { - for (const [name, fn] of tests) { - try { - await fn() - console.log(` ok ${name}`) - } catch (err) { - failed++ - console.error(` FAIL ${name}`) - console.error(err instanceof Error ? err.stack ?? err.message : String(err)) - } - } + failed = await runSuites("dashboard settings", tests) } finally { delete process.env.HEADLESSCODE_DATA_DIR await fs.rm(storeTmp, { recursive: true, force: true }) @@ -258,7 +250,6 @@ async function main(): Promise { console.error(`\n${failed} test(s) failed`) process.exit(1) } - console.log(`\nAll ${tests.length} dashboard settings tests passed`) } main().catch((err) => { diff --git a/src/dashboard/__tests__/sigint-shutdown.test.ts b/src/dashboard/__tests__/sigint-shutdown.test.ts index e19c7ae..4583aa3 100644 --- a/src/dashboard/__tests__/sigint-shutdown.test.ts +++ b/src/dashboard/__tests__/sigint-shutdown.test.ts @@ -35,6 +35,7 @@ import * as net from "node:net" import * as os from "node:os" import * as path from "node:path" import { fileURLToPath } from "node:url" +import { runMain } from "../../test-runner.js" const REPO_ROOT = path.resolve(fileURLToPath(new URL("../../../", import.meta.url))) @@ -207,26 +208,4 @@ const tests: Array<[string, () => Promise]> = [ ["dashboard CLI: real SIGINT closes the HTTP server and releases the port", testDashboardCliSigintClosesServer], ] -async function main(): Promise { - let failed = 0 - for (const [name, fn] of tests) { - try { - await fn() - console.log(` ok ${name}`) - } catch (err) { - failed++ - console.error(` FAIL ${name}`) - console.error(err instanceof Error ? (err.stack ?? err.message) : String(err)) - } - } - if (failed > 0) { - console.error(`\n${failed} test(s) failed`) - process.exit(1) - } - console.log(`\nAll ${tests.length} sigint-shutdown tests passed`) -} - -main().catch((err) => { - console.error("Test runner crashed:", err) - process.exit(1) -}) +runMain("sigint-shutdown", tests) diff --git a/src/dashboard/__tests__/timeline.test.ts b/src/dashboard/__tests__/timeline.test.ts index c1d0edd..1b06d13 100644 --- a/src/dashboard/__tests__/timeline.test.ts +++ b/src/dashboard/__tests__/timeline.test.ts @@ -18,6 +18,7 @@ import { fileURLToPath } from "node:url" import { buildTimeline, classifyTool } from "../timeline.js" import type { EventRecord } from "../../engine/events.js" +import { runMain } from "../../test-runner.js" const __dirname = path.dirname(fileURLToPath(import.meta.url)) @@ -325,26 +326,4 @@ const tests: Array<[string, () => Promise]> = [ ["timeline: page.ts's TL_READ/WRITE/EXEC/SEARCH sets stay in sync with timeline.ts's", testPageTsWriteToolsStayInSyncWithTimelineTs], ] -async function main(): Promise { - let failed = 0 - for (const [name, fn] of tests) { - try { - await fn() - console.log(` ok ${name}`) - } catch (err) { - failed++ - console.error(` FAIL ${name}`) - console.error(err instanceof Error ? err.stack ?? err.message : String(err)) - } - } - if (failed > 0) { - console.error(`\n${failed} test(s) failed`) - process.exit(1) - } - console.log(`\nAll ${tests.length} dashboard timeline tests passed`) -} - -main().catch((err) => { - console.error("Test runner crashed:", err) - process.exit(1) -}) +runMain("dashboard timeline", tests) diff --git a/src/dashboard/__tests__/tool-exec.test.ts b/src/dashboard/__tests__/tool-exec.test.ts index da74e55..52a066f 100644 --- a/src/dashboard/__tests__/tool-exec.test.ts +++ b/src/dashboard/__tests__/tool-exec.test.ts @@ -17,6 +17,7 @@ import * as path from "node:path" import { executeTool } from "../tool-exec.js" import { startDashboardServer } from "../server.js" import type { Server } from "node:http" +import { runMain } from "../../test-runner.js" async function mkTmpWorkspace(prefix: string): Promise { return fs.mkdtemp(path.join(os.tmpdir(), prefix)) @@ -194,23 +195,4 @@ const tests: Array<[string, () => Promise]> = [ ["HTTP POST /api/tool/execute rejects bad body", testHttpEndpointRejectsBadBody], ] -async function main(): Promise { - let failed = 0 - for (const [name, fn] of tests) { - try { - await fn() - console.log(`ok - ${name}`) - } catch (error) { - failed++ - console.error(`FAIL - ${name}`) - console.error(error) - } - } - if (failed > 0) { - console.error(`\n${failed}/${tests.length} tool-exec tests failed`) - process.exit(1) - } - console.log(`\nAll ${tests.length} tool-exec tests passed`) -} - -main() +runMain("tool-exec", tests) diff --git a/src/deploy/__tests__/gate.test.ts b/src/deploy/__tests__/gate.test.ts index 78ef445..8862c81 100644 --- a/src/deploy/__tests__/gate.test.ts +++ b/src/deploy/__tests__/gate.test.ts @@ -7,6 +7,7 @@ import assert from "node:assert/strict" import { decideApproval, type ApprovalInputs } from "../gate.js" +import { runMain } from "../../test-runner.js" function inputs(over: Partial): ApprovalInputs { return { interactive: false, approvalFileExists: false, ...over } @@ -108,26 +109,4 @@ const tests: Array<[string, () => Promise]> = [ ["all inputs empty denies", testNonInteractiveEverythingEmptyDenies], ] -async function main(): Promise { - let failed = 0 - for (const [name, fn] of tests) { - try { - await fn() - console.log(` ok ${name}`) - } catch (err) { - failed++ - console.error(` FAIL ${name}`) - console.error(err instanceof Error ? err.stack ?? err.message : String(err)) - } - } - if (failed > 0) { - console.error(`\n${failed} test(s) failed`) - process.exit(1) - } - console.log(`\nAll ${tests.length} deploy-gate decision tests passed`) -} - -main().catch((err) => { - console.error("Test runner crashed:", err) - process.exit(1) -}) +runMain("deploy-gate decision", tests) diff --git a/src/engine/__tests__/claims.test.ts b/src/engine/__tests__/claims.test.ts index 61dbecf..3d129de 100644 --- a/src/engine/__tests__/claims.test.ts +++ b/src/engine/__tests__/claims.test.ts @@ -13,6 +13,7 @@ import { type ClaimVerification, } from "../claims.js" import { resolvePermissions } from "../../permissions/config.js" +import { runMain } from "../../test-runner.js" /** Deterministic tmp workspace per test. */ async function makeTmpWorkspace(files: Record = {}): Promise { @@ -337,51 +338,73 @@ function testAllClaimsVerifiedHelpers(): void { // --- runner ---------------------------------------------------------------- -const tests: Array<{ name: string; fn: () => void | Promise }> = [ - { name: "extract: file-existence claims", fn: testExtractsFileExistenceClaims }, - { name: "extract: file claim behind a preposition (verb … to )", fn: testExtractsFileClaimBehindPreposition }, - { name: "extract: prepositional phrase without a change verb is not a claim", fn: testPrepositionPatternStillNeedsAVerb }, - { name: "extract: bare path mentions without verbs ignored", fn: testIgnoresBarePathMentionsWithoutVerbs }, - { name: "extract: command-passed claims", fn: testExtractsCommandPassedClaims }, - { name: "extract: serial markers only when serial mentioned", fn: testExtractsSerialMarkersOnlyWhenSerialMentioned }, - { name: "extract: PR URL claims", fn: testExtractsPrUrlClaims }, - { name: "extract: dedupe identical claims", fn: testDeduplicatesClaims }, - { name: "verify: file exists against disk", fn: testFileExistsVerifiedAgainstDisk }, - { name: "verify: empty file fails", fn: testEmptyFileClaimFails }, - { name: "verify: command re-run passes", fn: testCommandClaimRerunPasses }, - { name: "verify: command re-run fails on non-zero exit", fn: testCommandClaimRerunFails }, - { name: "extract: quoted token in pass phrase becomes expectedMarker", fn: testExtractsExpectedMarkerFromQuotedToken }, - { name: "extract: all-caps word in pass phrase becomes expectedMarker", fn: testExtractsExpectedMarkerFromAllCapsWord }, - { name: "extract: lowercase adjectives are not output markers", fn: testLowercasePassPhraseDoesNotBecomeMarker }, - { name: "verify: exit 0 but missing claimed marker FAILS", fn: testCommandClaimExitZeroButMissingMarkerFails }, - { name: "verify: exit 0 with the claimed marker passes", fn: testCommandClaimExitZeroWithMarkerPasses }, - { name: "resolveClaimPath: ../ traversal and absolute paths rejected", fn: testResolveClaimPathRejectsEscapes }, - { name: "verify: denied command refused, never re-run", fn: testCommandClaimRefusedByPermissionGate }, - { name: "verify: nonexistent command fails", fn: testCommandClaimNonexistentCommandFails }, - { name: "verify: serial markers against newest serial log", fn: testSerialMarkerVerifiedAgainstLog }, - { name: "verify: no serial log fails closed", fn: testSerialMarkerNoLogFails }, - { name: "verify: PR number against real git history", fn: testPrUrlVerifiedAgainstGitHistory }, - { name: "helpers: allClaimsVerified / firstUnverifiedDetail", fn: testAllClaimsVerifiedHelpers }, +const tests: Array<[string, () => void | Promise]> = [ + ["extract: file-existence claims", testExtractsFileExistenceClaims], + [ + "extract: file claim behind a preposition (verb … to )", + testExtractsFileClaimBehindPreposition, + ], + [ + "extract: prepositional phrase without a change verb is not a claim", + testPrepositionPatternStillNeedsAVerb, + ], + [ + "extract: bare path mentions without verbs ignored", + testIgnoresBarePathMentionsWithoutVerbs, + ], + ["extract: command-passed claims", testExtractsCommandPassedClaims], + [ + "extract: serial markers only when serial mentioned", + testExtractsSerialMarkersOnlyWhenSerialMentioned, + ], + ["extract: PR URL claims", testExtractsPrUrlClaims], + ["extract: dedupe identical claims", testDeduplicatesClaims], + ["verify: file exists against disk", testFileExistsVerifiedAgainstDisk], + ["verify: empty file fails", testEmptyFileClaimFails], + ["verify: command re-run passes", testCommandClaimRerunPasses], + ["verify: command re-run fails on non-zero exit", testCommandClaimRerunFails], + [ + "extract: quoted token in pass phrase becomes expectedMarker", + testExtractsExpectedMarkerFromQuotedToken, + ], + [ + "extract: all-caps word in pass phrase becomes expectedMarker", + testExtractsExpectedMarkerFromAllCapsWord, + ], + [ + "extract: lowercase adjectives are not output markers", + testLowercasePassPhraseDoesNotBecomeMarker, + ], + [ + "verify: exit 0 but missing claimed marker FAILS", + testCommandClaimExitZeroButMissingMarkerFails, + ], + [ + "verify: exit 0 with the claimed marker passes", + testCommandClaimExitZeroWithMarkerPasses, + ], + [ + "resolveClaimPath: ../ traversal and absolute paths rejected", + testResolveClaimPathRejectsEscapes, + ], + [ + "verify: denied command refused, never re-run", + testCommandClaimRefusedByPermissionGate, + ], + ["verify: nonexistent command fails", testCommandClaimNonexistentCommandFails], + [ + "verify: serial markers against newest serial log", + testSerialMarkerVerifiedAgainstLog, + ], + ["verify: no serial log fails closed", testSerialMarkerNoLogFails], + [ + "verify: PR number against real git history", + testPrUrlVerifiedAgainstGitHistory, + ], + [ + "helpers: allClaimsVerified / firstUnverifiedDetail", + testAllClaimsVerifiedHelpers, + ], ] -async function run(): Promise { - let failed = 0 - for (const t of tests) { - try { - await t.fn() - console.log(`PASS ${t.name}`) - } catch (err) { - failed++ - console.error(`FAIL ${t.name}`) - console.error(err instanceof Error ? err.message : String(err)) - } - } - if (failed > 0) { - process.exitCode = 1 - console.error(`\n${failed}/${tests.length} tests failed`) - } else { - console.log(`\nAll ${tests.length} claims tests passed`) - } -} - -void run() +runMain("claims", tests) diff --git a/src/engine/__tests__/condense-async.test.ts b/src/engine/__tests__/condense-async.test.ts index f10c15a..e92106c 100644 --- a/src/engine/__tests__/condense-async.test.ts +++ b/src/engine/__tests__/condense-async.test.ts @@ -23,6 +23,7 @@ import { HeadlessSession } from "../loop.js" import { DEFAULT_WINDOW_SIZE } from "../loop.js" import type { ChatMessage, ChatToolCall, LlmClient, LlmRequest, LlmResponse } from "../types.js" import { BudgetTracker } from "../../budget/budget.js" +import { runMain } from "../../test-runner.js" // ─── Fakes ─────────────────────────────────────────────────────────────────── @@ -379,26 +380,4 @@ const tests: Array<[string, () => Promise]> = [ ["async condense: session-end before resolution discards the result but keeps its usage in the BudgetTracker", testSessionEndBeforeBackgroundResolveDiscardsButAccounts], ] -async function main(): Promise { - let failed = 0 - for (const [name, fn] of tests) { - try { - await fn() - console.log(` ok ${name}`) - } catch (err) { - failed++ - console.error(` FAIL ${name}`) - console.error(err instanceof Error ? err.stack ?? err.message : String(err)) - } - } - if (failed > 0) { - console.error(`\n${failed} test(s) failed`) - process.exit(1) - } - console.log(`\nAll ${tests.length} condense-async tests passed`) -} - -main().catch((err) => { - console.error("Test runner crashed:", err) - process.exit(1) -}) +runMain("condense-async", tests) diff --git a/src/engine/__tests__/condense.test.ts b/src/engine/__tests__/condense.test.ts index 31ee911..c06a797 100644 --- a/src/engine/__tests__/condense.test.ts +++ b/src/engine/__tests__/condense.test.ts @@ -45,6 +45,7 @@ import { } from "../condense.js" import type { ChatMessage, ChatToolCall, LlmClient, LlmRequest, LlmResponse } from "../types.js" import { BudgetTracker } from "../../budget/budget.js" +import { runMain } from "../../test-runner.js" // ─── Fakes ─────────────────────────────────────────────────────────────────── @@ -1338,26 +1339,4 @@ const tests: Array<[string, () => Promise | void]> = [ ["session: condensation call times out instead of hanging forever (non-fatal)", testSessionCondenseCallTimesOutNonFatally], ] -async function main(): Promise { - let failed = 0 - for (const [name, fn] of tests) { - try { - await fn() - console.log(` ok ${name}`) - } catch (err) { - failed++ - console.error(` FAIL ${name}`) - console.error(err instanceof Error ? err.stack ?? err.message : String(err)) - } - } - if (failed > 0) { - console.error(`\n${failed} test(s) failed`) - process.exit(1) - } - console.log(`\nAll ${tests.length} condense tests passed`) -} - -main().catch((err) => { - console.error("Test runner crashed:", err) - process.exit(1) -}) +runMain("condense", tests) diff --git a/src/engine/__tests__/events.test.ts b/src/engine/__tests__/events.test.ts index 02c9e1e..b07d189 100644 --- a/src/engine/__tests__/events.test.ts +++ b/src/engine/__tests__/events.test.ts @@ -21,6 +21,7 @@ import { HeadlessSession } from "../loop.js" import { eventsDir, eventsFilePath, readEventsFile, EVENT_TRUNCATE_CHARS, truncateField } from "../events.js" import { readLimitFromEnv } from "../../tools/executor.js" import type { ChatMessage, ChatToolCall, LlmClient, LlmRequest, LlmResponse } from "../types.js" +import { runMain } from "../../test-runner.js" // ─── Fake LLM client (mirrors src/engine/__tests__/loop.test.ts) ──────────── @@ -761,26 +762,4 @@ const tests: Array<[string, () => Promise]> = [ ["events: streamed response usage feeds the budget tracker exactly once (no double-count)", testStreamedResponseUsageFeedsBudgetOnce], ] -async function main(): Promise { - let failed = 0 - for (const [name, fn] of tests) { - try { - await fn() - console.log(` ok ${name}`) - } catch (err) { - failed++ - console.error(` FAIL ${name}`) - console.error(err instanceof Error ? err.stack ?? err.message : String(err)) - } - } - if (failed > 0) { - console.error(`\n${failed} test(s) failed`) - process.exit(1) - } - console.log(`\nAll ${tests.length} events tests passed`) -} - -main().catch((err) => { - console.error("Test runner crashed:", err) - process.exit(1) -}) +runMain("events", tests) diff --git a/src/engine/__tests__/global-modes.test.ts b/src/engine/__tests__/global-modes.test.ts index 3612733..9c43b0e 100644 --- a/src/engine/__tests__/global-modes.test.ts +++ b/src/engine/__tests__/global-modes.test.ts @@ -29,6 +29,7 @@ const ROOT_SANDBOX = path.join(os.tmpdir(), `hc-global-modes-root-${process.pid} import assert from "node:assert/strict" import { buildSystemPrompt, loadCustomModes } from "../prompt.js" +import { runSuites } from "../../test-runner.js" // ─── Per-test HOME sandboxing ─────────────────────────────────────────────── @@ -232,22 +233,11 @@ async function main(): Promise { ) assert.equal(process.env.HOME, path.join(ROOT_SANDBOX, "home-0")) - let failed = 0 - for (const [name, fn] of tests) { - try { - await fn() - console.log(` ok ${name}`) - } catch (err) { - failed++ - console.error(` FAIL ${name}`) - console.error(err instanceof Error ? err.stack : err) - } - } + const failed = await runSuites("global-modes", tests) if (failed > 0) { console.error(`\n${failed} test(s) failed`) process.exit(1) } - console.log(`\nAll ${tests.length} global-modes tests passed`) } main().finally(async () => { diff --git a/src/engine/__tests__/inject-message.test.ts b/src/engine/__tests__/inject-message.test.ts index ed48c1c..8111668 100644 --- a/src/engine/__tests__/inject-message.test.ts +++ b/src/engine/__tests__/inject-message.test.ts @@ -21,6 +21,7 @@ import * as path from "node:path" import { HeadlessSession } from "../loop.js" import { eventsDir, eventsFilePath, readEventsFile } from "../events.js" import type { ChatMessage, ChatToolCall, LlmClient, LlmRequest, LlmResponse } from "../types.js" +import { runMain } from "../../test-runner.js" const INJECT_MESSAGE = ".harness.inject-message" const INJECTED_TEXT = "Wait — switch to the other approach first, then continue." @@ -310,26 +311,4 @@ const tests: Array<[string, () => Promise]> = [ ["inject: malformed marker is removed and ignored, never wedges the loop", testMalformedMarkerIsRemovedAndIgnored], ] -async function main(): Promise { - let failed = 0 - for (const [name, fn] of tests) { - try { - await fn() - console.log(` ok ${name}`) - } catch (err) { - failed++ - console.error(` FAIL ${name}`) - console.error(err instanceof Error ? err.stack ?? err.message : String(err)) - } - } - if (failed > 0) { - console.error(`\n${failed} test(s) failed`) - process.exit(1) - } - console.log(`\nAll ${tests.length} inject-message tests passed`) -} - -main().catch((err) => { - console.error("Test runner crashed:", err) - process.exit(1) -}) +runMain("inject-message", tests) diff --git a/src/engine/__tests__/lazy-tools.test.ts b/src/engine/__tests__/lazy-tools.test.ts index 65ede1b..12768d9 100644 --- a/src/engine/__tests__/lazy-tools.test.ts +++ b/src/engine/__tests__/lazy-tools.test.ts @@ -18,6 +18,7 @@ import { splitCoreAndLazyTools, } from "../lazy-tools.js" import type { ChatTool } from "../types.js" +import { runMain } from "../../test-runner.js" function fnTool(name: string, description = `${name} description`): ChatTool { return { type: "function", function: { name, description, parameters: { type: "object", properties: {} } } } @@ -133,22 +134,4 @@ const tests: Array<[string, () => void]> = [ ["renderToolIndex: empty case", testRenderToolIndexEmptyCase], ] -function main(): void { - let failed = 0 - for (const [name, fn] of tests) { - try { - fn() - console.log(` ok ${name}`) - } catch (error) { - failed++ - console.error(` FAIL ${name}`) - console.error(error) - } - } - console.log(`\n${failed === 0 ? "All" : `${tests.length - failed}/${tests.length}`} ${tests.length} tests passed`) - if (failed > 0) { - process.exit(1) - } -} - -main() +runMain("", tests) diff --git a/src/engine/__tests__/local-explore.test.ts b/src/engine/__tests__/local-explore.test.ts index acba3e3..a35bdd4 100644 --- a/src/engine/__tests__/local-explore.test.ts +++ b/src/engine/__tests__/local-explore.test.ts @@ -55,6 +55,7 @@ import { } from "../local-explore.js" import type { LocalChatClient, LocalChatResponse, LocalExploreOptions, LocalExploreRequest } from "../local-explore.js" import type { ChatMessage, ChatToolCall, LlmClient, LlmRequest, LlmResponse } from "../types.js" +import { runMain } from "../../test-runner.js" // ─── Fakes ─────────────────────────────────────────────────────────────────── @@ -178,10 +179,10 @@ async function makeSession(options: { // ─── Tests ─────────────────────────────────────────────────────────────────── -const tests: Array<{ name: string; fn: () => Promise | void }> = [] +const tests: Array<[string, () => Promise | void]> = [] function test(name: string, fn: () => Promise | void) { - tests.push({ name, fn }) + tests.push([name, fn]) } // 1. Iteration cap enforcement: a local model that never stops must be cut @@ -631,26 +632,4 @@ test("Ollama client wraps HTTP and network failures in LocalExploreError", async // ─── Runner ────────────────────────────────────────────────────────────────── -async function main() { - let failed = 0 - for (const { name, fn } of tests) { - try { - await fn() - console.log(` ok ${name}`) - } catch (err) { - failed++ - console.error(` FAIL ${name}`) - console.error(err instanceof Error ? err.stack ?? err.message : String(err)) - } - } - if (failed > 0) { - console.error(`\n${failed} test(s) failed`) - process.exit(1) - } - console.log(`\nAll ${tests.length} local-explore tests passed`) -} - -main().catch((err) => { - console.error("Test runner crashed:", err) - process.exit(1) -}) +runMain("local-explore", tests) diff --git a/src/engine/__tests__/loop-helpers-history.test.ts b/src/engine/__tests__/loop-helpers-history.test.ts index 5006527..7fc61b1 100644 --- a/src/engine/__tests__/loop-helpers-history.test.ts +++ b/src/engine/__tests__/loop-helpers-history.test.ts @@ -14,6 +14,7 @@ import { truncateHistory, } from "../loop.js" import type { ChatMessage, ChatTool } from "../types.js" +import { runMain } from "../../test-runner.js" function msg(role: ChatMessage["role"], content: string | null): ChatMessage { return { role, content } @@ -94,23 +95,4 @@ const tests: Array<[string, () => void]> = [ ["messagesPrefixEquals empty snapshot", testPrefixEqualsEmptySnapshot], ] -function main(): void { - let failed = 0 - for (const [name, fn] of tests) { - try { - fn() - console.log(` ok ${name}`) - } catch (err) { - failed++ - console.error(` FAIL ${name}`) - console.error(err instanceof Error ? err.stack ?? err.message : String(err)) - } - } - if (failed > 0) { - console.error(`\n${failed} test(s) failed`) - process.exit(1) - } - console.log(`\nAll ${tests.length} tests passed`) -} - -main() +runMain("", tests) diff --git a/src/engine/__tests__/loop-helpers-readonly.test.ts b/src/engine/__tests__/loop-helpers-readonly.test.ts index 9ac65bd..4829dd9 100644 --- a/src/engine/__tests__/loop-helpers-readonly.test.ts +++ b/src/engine/__tests__/loop-helpers-readonly.test.ts @@ -13,6 +13,7 @@ import { isReadOnlyDirectCall, isReadOnlyTurnCall, } from "../loop.js" +import { runMain } from "../../test-runner.js" function testCombineAbortedWhenInputAborted(): void { const c = new AbortController() @@ -147,23 +148,4 @@ const tests: Array<[string, () => void]> = [ ["artifact nudge names streak+path", testArtifactNudgeNamesStreakAndPath], ] -function main(): void { - let failed = 0 - for (const [name, fn] of tests) { - try { - fn() - console.log(` ok ${name}`) - } catch (err) { - failed++ - console.error(` FAIL ${name}`) - console.error(err instanceof Error ? err.stack ?? err.message : String(err)) - } - } - if (failed > 0) { - console.error(`\n${failed} test(s) failed`) - process.exit(1) - } - console.log(`\nAll ${tests.length} tests passed`) -} - -main() +runMain("", tests) diff --git a/src/engine/__tests__/loop.test.ts b/src/engine/__tests__/loop.test.ts index 1d25350..d87f50d 100644 --- a/src/engine/__tests__/loop.test.ts +++ b/src/engine/__tests__/loop.test.ts @@ -38,6 +38,7 @@ import type { EvidenceInput, EvidenceJournal, EvidenceReceipt } from "../../evid import type { ToolExecutor } from "../../tools/executor.js" import type { SessionBudget } from "../../budget/budget.js" import type { ChatMessage, ChatToolCall, LlmClient, LlmRequest, LlmResponse } from "../types.js" +import { runSuites } from "../../test-runner.js" // ─── Fake LLM client ───────────────────────────────────────────────────────── @@ -4375,22 +4376,11 @@ process.on("unhandledRejection", (reason) => { }) async function main(): Promise { - let failed = 0 - for (const [name, fn] of tests) { - try { - await fn() - console.log(` ok ${name}`) - } catch (err) { - failed++ - console.error(` FAIL ${name}`) - console.error(err instanceof Error ? err.stack ?? err.message : String(err)) - } - } + const failed = await runSuites("", tests) if (failed > 0) { console.error(`\n${failed} test(s) failed`) process.exit(1) } - console.log(`\nAll ${tests.length} tests passed`) testsFinished = true process.exitCode = 0 } diff --git a/src/engine/__tests__/new-task.test.ts b/src/engine/__tests__/new-task.test.ts index 58fac88..f927d33 100644 --- a/src/engine/__tests__/new-task.test.ts +++ b/src/engine/__tests__/new-task.test.ts @@ -25,6 +25,7 @@ import { eventsDir, readEventsFile, type EventRecord } from "../events.js" import { createCheckpointService } from "../../checkpoints/service.js" import type { SessionBudget } from "../../budget/budget.js" import type { ChatMessage, ChatToolCall, LlmClient, LlmRequest, LlmResponse } from "../types.js" +import { runMain } from "../../test-runner.js" // ─── Scripted fake LLM client (parent + child share it) ────────────────────── @@ -431,26 +432,4 @@ const tests: Array<[string, () => Promise]> = [ ["new_task: malformed child completion fails closed", testMalformedChildCompletionFailsClosed], ] -async function main(): Promise { - let failed = 0 - for (const [name, fn] of tests) { - try { - await fn() - console.log(` ok ${name}`) - } catch (err) { - failed++ - console.error(` FAIL ${name}`) - console.error(err instanceof Error ? err.stack ?? err.message : String(err)) - } - } - if (failed > 0) { - console.error(`\n${failed} test(s) failed`) - process.exit(1) - } - console.log(`\nAll ${tests.length} tests passed`) -} - -main().catch((err) => { - console.error("Test runner crashed:", err) - process.exit(1) -}) +runMain("", tests) diff --git a/src/engine/__tests__/parser.test.ts b/src/engine/__tests__/parser.test.ts index 802e2b6..a07cc7b 100644 --- a/src/engine/__tests__/parser.test.ts +++ b/src/engine/__tests__/parser.test.ts @@ -14,6 +14,7 @@ import assert from "node:assert/strict" import { bestEffortPartialJson, extractEmbeddedToolCall, parseToolCall, parseToolCalls } from "../parser.js" import type { ChatMessage, ChatToolCall } from "../types.js" +import { runMain } from "../../test-runner.js" function call(overrides: Partial = {}, id = "call_1"): ChatToolCall { return { @@ -256,23 +257,4 @@ const tests: Array<[string, () => void]> = [ ], ] -function main(): void { - let failed = 0 - for (const [name, fn] of tests) { - try { - fn() - console.log(` ok ${name}`) - } catch (err) { - failed++ - console.error(` FAIL ${name}`) - console.error(err instanceof Error ? err.stack ?? err.message : String(err)) - } - } - if (failed > 0) { - console.error(`\n${failed} test(s) failed`) - process.exit(1) - } - console.log(`\nAll ${tests.length} tests passed`) -} - -main() +runMain("", tests) diff --git a/src/engine/__tests__/pause.test.ts b/src/engine/__tests__/pause.test.ts index e771794..6156cb1 100644 --- a/src/engine/__tests__/pause.test.ts +++ b/src/engine/__tests__/pause.test.ts @@ -20,6 +20,7 @@ import { HeadlessSession } from "../loop.js" import { BudgetTracker } from "../../budget/budget.js" import { eventsDir, eventsFilePath, readEventsFile } from "../events.js" import type { ChatMessage, ChatToolCall, LlmClient, LlmRequest, LlmResponse } from "../types.js" +import { runMain } from "../../test-runner.js" const PAUSE_REQUESTED = ".harness.pause-requested" const PAUSED = ".harness.paused" @@ -299,26 +300,4 @@ const tests: Array<[string, () => Promise]> = [ ["pause: no marker -> zero behavior change", testNoMarkerMeansNoBlocking], ] -async function main(): Promise { - let failed = 0 - for (const [name, fn] of tests) { - try { - await fn() - console.log(` ok ${name}`) - } catch (err) { - failed++ - console.error(` FAIL ${name}`) - console.error(err instanceof Error ? err.stack ?? err.message : String(err)) - } - } - if (failed > 0) { - console.error(`\n${failed} test(s) failed`) - process.exit(1) - } - console.log(`\nAll ${tests.length} pause tests passed`) -} - -main().catch((err) => { - console.error("Test runner crashed:", err) - process.exit(1) -}) +runMain("pause", tests) diff --git a/src/engine/__tests__/prompt.test.ts b/src/engine/__tests__/prompt.test.ts index 41f418b..c67274b 100644 --- a/src/engine/__tests__/prompt.test.ts +++ b/src/engine/__tests__/prompt.test.ts @@ -31,6 +31,7 @@ import { selectToolsForMode, } from "../prompt.js" import type { ChatTool } from "../types.js" +import { runMain } from "../../test-runner.js" function findStrictModeViolations(tools: ChatTool[]): string[] { const violations: string[] = [] @@ -143,23 +144,4 @@ const tests: Array<[string, () => Promise]> = [ ["headless conventions are not shown to read-only modes", testHeadlessConventionsNotShownToReadOnlyModes], ] -async function main(): Promise { - let failed = 0 - for (const [name, fn] of tests) { - try { - await fn() - console.log(` ok ${name}`) - } catch (err) { - failed++ - console.error(` FAIL ${name}`) - console.error(err instanceof Error ? err.stack : err) - } - } - if (failed > 0) { - console.error(`\n${failed} test(s) failed`) - process.exit(1) - } - console.log(`\nAll ${tests.length} prompt/tool-schema tests passed`) -} - -main() +runMain("prompt/tool-schema", tests) diff --git a/src/engine/__tests__/stack-rules-content.test.ts b/src/engine/__tests__/stack-rules-content.test.ts index 9cd4f30..86107c2 100644 --- a/src/engine/__tests__/stack-rules-content.test.ts +++ b/src/engine/__tests__/stack-rules-content.test.ts @@ -26,6 +26,7 @@ const ROOT_SANDBOX = path.join(os.tmpdir(), `hc-stack-content-${process.pid}-${D import { buildSystemPrompt } from "../prompt.js" import { STACK_RULES_HEADER, getCentralStackRulesDir, loadStackRules } from "../stacks.js" +import { runSuites } from "../../test-runner.js" /** Repo root = 4 levels up from src/engine/__tests__/. */ const REPO_ROOT = path.resolve(fileURLToPath(import.meta.url), "..", "..", "..", "..") @@ -157,22 +158,11 @@ async function main(): Promise { ) assert.equal(process.env.HOME, path.join(ROOT_SANDBOX, "home-0")) - let failed = 0 - for (const [name, fn] of tests) { - try { - await fn() - console.log(` ok ${name}`) - } catch (err) { - failed++ - console.error(` FAIL ${name}`) - console.error(err instanceof Error ? err.stack : err) - } - } + const failed = await runSuites("stack-rules-content", tests) if (failed > 0) { console.error(`\n${failed} test(s) failed`) process.exit(1) } - console.log(`\nAll ${tests.length} stack-rules-content tests passed`) } main().finally(async () => { diff --git a/src/engine/__tests__/stacks.test.ts b/src/engine/__tests__/stacks.test.ts index b450f3d..dc3b237 100644 --- a/src/engine/__tests__/stacks.test.ts +++ b/src/engine/__tests__/stacks.test.ts @@ -43,6 +43,7 @@ import { getCentralStackRulesDir, loadStackRules, } from "../stacks.js" +import { runSuites } from "../../test-runner.js" // ─── Per-test HOME sandboxing (mirrors global-modes.test.ts) ──────────────── @@ -580,22 +581,11 @@ async function main(): Promise { "getCentralStackRulesDir() must resolve under the per-test data dir", ) - let failed = 0 - for (const [name, fn] of tests) { - try { - await fn() - console.log(` ok ${name}`) - } catch (err) { - failed++ - console.error(` FAIL ${name}`) - console.error(err instanceof Error ? err.stack : err) - } - } + const failed = await runSuites("stack-rules", tests) if (failed > 0) { console.error(`\n${failed} test(s) failed`) process.exit(1) } - console.log(`\nAll ${tests.length} stack-rules tests passed`) } main().finally(async () => { diff --git a/src/engine/__tests__/summarize-tool-arg.test.ts b/src/engine/__tests__/summarize-tool-arg.test.ts index e9e3161..4ae22c8 100644 --- a/src/engine/__tests__/summarize-tool-arg.test.ts +++ b/src/engine/__tests__/summarize-tool-arg.test.ts @@ -14,6 +14,7 @@ import assert from "node:assert/strict" import { readLimitFromEnv } from "../../tools/executor.js" import { summarizeToolArg } from "../loop.js" +import { runMain } from "../../test-runner.js" function testSliceModeWithExplicitOffsetLimit(): void { assert.equal( @@ -104,23 +105,4 @@ const tests: Array<[string, () => void]> = [ ["edit_file/search_replace use file_path", testEditFileAndSearchReplaceUseFilePath], ] -function main(): void { - let failed = 0 - for (const [name, fn] of tests) { - try { - fn() - console.log(` ok ${name}`) - } catch (err) { - failed++ - console.error(` FAIL ${name}`) - console.error(err instanceof Error ? err.stack ?? err.message : String(err)) - } - } - if (failed > 0) { - console.error(`\n${failed} test(s) failed`) - process.exit(1) - } - console.log(`\nAll ${tests.length} tests passed`) -} - -main() +runMain("", tests) diff --git a/src/engine/__tests__/switch-mode.test.ts b/src/engine/__tests__/switch-mode.test.ts index 02c8254..df3dc5c 100644 --- a/src/engine/__tests__/switch-mode.test.ts +++ b/src/engine/__tests__/switch-mode.test.ts @@ -29,6 +29,7 @@ import { appendExtensionTools } from "../../extensions/tools.js" import type { SkillRegistry } from "../../skills/registry.js" import { createReadOnlyHeadlessExecutor } from "../../tools/executor.js" import type { ChatMessage, ChatTool, ChatToolCall, LlmClient, LlmRequest, LlmResponse } from "../types.js" +import { runMain } from "../../test-runner.js" const NEEDS_DECISION = ".harness.needs-decision" const DECISION_ANSWER = ".harness.decision-answer" @@ -690,26 +691,4 @@ const tests: Array<[string, () => Promise]> = [ ["switch_mode: differing target tool set rebuilds the prompt (messages[0] replaced)", testToolSetChangeRebuildsPrompt], ] -async function main(): Promise { - let failed = 0 - for (const [name, fn] of tests) { - try { - await fn() - console.log(` ok ${name}`) - } catch (err) { - failed++ - console.error(` FAIL ${name}`) - console.error(err instanceof Error ? err.stack ?? err.message : String(err)) - } - } - if (failed > 0) { - console.error(`\n${failed} test(s) failed`) - process.exit(1) - } - console.log(`\nAll ${tests.length} tests passed`) -} - -main().catch((err) => { - console.error("Test runner crashed:", err) - process.exit(1) -}) +runMain("", tests) diff --git a/src/evidence/__tests__/verify-files.test.ts b/src/evidence/__tests__/verify-files.test.ts index 08e59b4..d14a3fc 100644 --- a/src/evidence/__tests__/verify-files.test.ts +++ b/src/evidence/__tests__/verify-files.test.ts @@ -20,6 +20,7 @@ import * as path from "node:path" import { FileEvidenceCollector, UnixEvidenceJournal } from "../collector.js" import { verifyEvidenceFiles } from "../verify.js" import { verifyEvidenceFiles as indexVerifyEvidenceFiles } from "../index.js" +import { runMain } from "../../test-runner.js" interface SignedBundlePaths { bundlePath: string @@ -103,37 +104,10 @@ async function testIndexReexportsTheLeaf(): Promise { assert.equal(indexVerifyEvidenceFiles, verifyEvidenceFiles) } -async function runOne(name: string, fn: () => Promise): Promise { - try { - await fn() - console.log(` ok ${name}`) - return true - } catch (error) { - console.error(` FAIL ${name}`) - if (error instanceof Error) console.error(error.stack ?? error) - else console.error(String(error)) - return false - } -} - -async function main(): Promise { - const tests: Array<[string, () => Promise]> = [ - ["accepts a signed bundle", testVerifyFilesAcceptsSignedBundle], - ["rejects a tampered bundle", testVerifyFilesRejectsTamperedBundle], - ["index re-exports the leaf", testIndexReexportsTheLeaf], - ] - let failed = 0 - for (const [name, fn] of tests) { - if (!(await runOne(name, fn))) failed++ - } - if (failed > 0) { - console.error(`\n${failed} test(s) failed`) - process.exit(1) - } - console.log(`\nAll ${tests.length} verify-files tests passed`) -} +const tests: Array<[string, () => Promise]> = [ + ["accepts a signed bundle", testVerifyFilesAcceptsSignedBundle], + ["rejects a tampered bundle", testVerifyFilesRejectsTamperedBundle], + ["index re-exports the leaf", testIndexReexportsTheLeaf], +] -main().catch((error) => { - console.error("Test runner crashed:", error) - process.exit(1) -}) +runMain("verify-files", tests) diff --git a/src/github/__tests__/app-auth.test.ts b/src/github/__tests__/app-auth.test.ts index 9370015..6b6cb12 100644 --- a/src/github/__tests__/app-auth.test.ts +++ b/src/github/__tests__/app-auth.test.ts @@ -18,6 +18,7 @@ import assert from "node:assert/strict" import * as crypto from "node:crypto" import { createAppAuthClient, type TokenCache } from "../app-auth.js" +import { runMain } from "../../test-runner.js" /** A real RSA key (PKCS#1 PEM, like GitHub provides) so JWT signing works. */ function fakePrivateKey(): string { @@ -217,26 +218,4 @@ const tests: Array<[string, () => Promise]> = [ ["exchange failure surfaces as typed AppAuthError", testExchangeFailureThrowsTypedError], ] -async function main(): Promise { - let failed = 0 - for (const [name, fn] of tests) { - try { - await fn() - console.log(` ok ${name}`) - } catch (err) { - failed++ - console.error(` FAIL ${name}`) - console.error(err instanceof Error ? err.stack ?? err.message : String(err)) - } - } - if (failed > 0) { - console.error(`\n${failed} test(s) failed`) - process.exit(1) - } - console.log(`\nAll ${tests.length} app-auth tests passed`) -} - -main().catch((err) => { - console.error("Test runner crashed:", err) - process.exit(1) -}) +runMain("app-auth", tests) diff --git a/src/github/__tests__/cli.test.ts b/src/github/__tests__/cli.test.ts index 16eb368..1fb543c 100644 --- a/src/github/__tests__/cli.test.ts +++ b/src/github/__tests__/cli.test.ts @@ -11,6 +11,7 @@ import assert from "node:assert/strict" import type { createAppAuthClient } from "../app-auth.js" import { parsePushPrArgs, pushPrMain, type PushPrDeps } from "../cli.js" +import { runMain } from "../../test-runner.js" /** The token used for API calls in these tests (fake — never a real one). */ const FAKE_TOKEN = "ghs_TESTTOKEN1234567890" @@ -227,26 +228,4 @@ const tests: Array<[string, () => Promise]> = [ ["src/cli.ts main() dispatches push-pr to pushPrMain", testCliMainDispatchesPushPr], ] -async function main(): Promise { - let failed = 0 - for (const [name, fn] of tests) { - try { - await fn() - console.log(` ok ${name}`) - } catch (err) { - failed++ - console.error(` FAIL ${name}`) - console.error(err instanceof Error ? err.stack ?? err.message : String(err)) - } - } - if (failed > 0) { - console.error(`\n${failed} test(s) failed`) - process.exit(1) - } - console.log(`\nAll ${tests.length} push-pr CLI tests passed`) -} - -main().catch((err) => { - console.error("Test runner crashed:", err) - process.exit(1) -}) +runMain("push-pr CLI", tests) diff --git a/src/github/__tests__/installations.test.ts b/src/github/__tests__/installations.test.ts index e70eec4..04365fa 100644 --- a/src/github/__tests__/installations.test.ts +++ b/src/github/__tests__/installations.test.ts @@ -10,6 +10,7 @@ import assert from "node:assert/strict" import { listInstallationRepos, toInstallationRepo } from "../installations.js" +import { runMain } from "../../test-runner.js" const FAKE_TOKEN = "ghs_TESTTOKEN1234567890" const API_BASE = "https://api.github.com" @@ -197,26 +198,4 @@ const tests: Array<[string, () => Promise]> = [ ["listInstallationRepos: a missing repositories field degrades to []", testListInstallationReposHandlesEmptyRepositoriesField], ] -async function main(): Promise { - let failed = 0 - for (const [name, fn] of tests) { - try { - await fn() - console.log(` ok ${name}`) - } catch (err) { - failed++ - console.error(` FAIL ${name}`) - console.error(err instanceof Error ? (err.stack ?? err.message) : String(err)) - } - } - if (failed > 0) { - console.error(`\n${failed} test(s) failed`) - process.exit(1) - } - console.log(`\nAll ${tests.length} github/installations tests passed`) -} - -main().catch((err) => { - console.error("Test runner crashed:", err) - process.exit(1) -}) +runMain("github/installations", tests) diff --git a/src/github/__tests__/pr.test.ts b/src/github/__tests__/pr.test.ts index 2acf015..f7c4c47 100644 --- a/src/github/__tests__/pr.test.ts +++ b/src/github/__tests__/pr.test.ts @@ -9,6 +9,7 @@ import assert from "node:assert/strict" import { NoDiffError, OpenPrError, getPullRequestStatus, getRepoDefaultBranch, openPullRequest } from "../pr.js" +import { runMain } from "../../test-runner.js" /** The token used for API calls in these tests (fake — never a real one). */ const FAKE_TOKEN = "ghs_TESTTOKEN1234567890" @@ -384,26 +385,4 @@ const tests: Array<[string, () => Promise]> = [ ["getPullRequestStatus rejects a malformed 2xx body", testGetPullRequestStatusMalformedBodyThrows], ] -async function main(): Promise { - let failed = 0 - for (const [name, fn] of tests) { - try { - await fn() - console.log(` ok ${name}`) - } catch (err) { - failed++ - console.error(` FAIL ${name}`) - console.error(err instanceof Error ? err.stack ?? err.message : String(err)) - } - } - if (failed > 0) { - console.error(`\n${failed} test(s) failed`) - process.exit(1) - } - console.log(`\nAll ${tests.length} PR tests passed`) -} - -main().catch((err) => { - console.error("Test runner crashed:", err) - process.exit(1) -}) +runMain("PR", tests) diff --git a/src/github/__tests__/provision.test.ts b/src/github/__tests__/provision.test.ts index 8719995..a38b990 100644 --- a/src/github/__tests__/provision.test.ts +++ b/src/github/__tests__/provision.test.ts @@ -17,6 +17,7 @@ import * as os from "node:os" import * as path from "node:path" import { provisionRepo, readOriginUrl } from "../provision.js" +import { runMain } from "../../test-runner.js" /** The token used for clones in these tests (fake — never a real one). */ const FAKE_TOKEN = "ghs_TESTTOKEN1234567890" @@ -175,26 +176,4 @@ const tests: Array<[string, () => Promise]> = [ ["refuses when target exists as a file", testRefusesExistingFileTarget], ] -async function main(): Promise { - let failed = 0 - for (const [name, fn] of tests) { - try { - await fn() - console.log(` ok ${name}`) - } catch (err) { - failed++ - console.error(` FAIL ${name}`) - console.error(err instanceof Error ? err.stack ?? err.message : String(err)) - } - } - if (failed > 0) { - console.error(`\n${failed} test(s) failed`) - process.exit(1) - } - console.log(`\nAll ${tests.length} provision tests passed`) -} - -main().catch((err) => { - console.error("Test runner crashed:", err) - process.exit(1) -}) +runMain("provision", tests) diff --git a/src/github/__tests__/push.test.ts b/src/github/__tests__/push.test.ts index 1a7207c..f2f1594 100644 --- a/src/github/__tests__/push.test.ts +++ b/src/github/__tests__/push.test.ts @@ -27,6 +27,7 @@ import * as os from "node:os" import * as path from "node:path" import { PushError, pushBranch } from "../push.js" +import { runMain } from "../../test-runner.js" /** The token used for pushes in these tests (fake — never a real one). */ const FAKE_TOKEN = "ghs_TESTTOKEN1234567890" @@ -207,26 +208,4 @@ const tests: Array<[string, () => Promise]> = [ ["refuses when the token supplier returns nothing", testRefusesMissingToken], ] -async function main(): Promise { - let failed = 0 - for (const [name, fn] of tests) { - try { - await fn() - console.log(` ok ${name}`) - } catch (err) { - failed++ - console.error(` FAIL ${name}`) - console.error(err instanceof Error ? err.stack ?? err.message : String(err)) - } - } - if (failed > 0) { - console.error(`\n${failed} test(s) failed`) - process.exit(1) - } - console.log(`\nAll ${tests.length} push tests passed`) -} - -main().catch((err) => { - console.error("Test runner crashed:", err) - process.exit(1) -}) +runMain("push", tests) diff --git a/src/index-util.test.ts b/src/index-util.test.ts index a03114a..26adc4d 100644 --- a/src/index-util.test.ts +++ b/src/index-util.test.ts @@ -18,6 +18,7 @@ import * as path from "node:path" import { hasCodebaseIndex, indexFilePathFor } from "./index-util.js" import { migrateLegacyProjectData, projectStoreRoot, resolveProjectDataDir, resolveProjectIdentity } from "./project-store.js" +import { runMain } from "./test-runner.js" async function mkTmp(prefix: string): Promise { return fsp.mkdtemp(path.join(os.tmpdir(), prefix)) @@ -122,26 +123,4 @@ const tests: Array<[string, () => Promise]> = [ ["legacy workspace .headlesscode index migrates on first resolution", testLegacyWorkspaceIndexIsMigratedOnFirstResolution], ] -async function main(): Promise { - let failed = 0 - for (const [name, fn] of tests) { - try { - await fn() - console.log(` ok ${name}`) - } catch (err) { - failed++ - console.error(` FAIL ${name}`) - console.error(err instanceof Error ? err.stack ?? err.message : String(err)) - } - } - if (failed > 0) { - console.error(`\n${failed} test(s) failed`) - process.exit(1) - } - console.log(`\nAll ${tests.length} index-util smoke tests passed`) -} - -main().catch((err) => { - console.error("Test runner crashed:", err) - process.exit(1) -}) +runMain("index-util smoke", tests) diff --git a/src/init/__tests__/cli.test.ts b/src/init/__tests__/cli.test.ts index 762a3eb..5fe2061 100644 --- a/src/init/__tests__/cli.test.ts +++ b/src/init/__tests__/cli.test.ts @@ -19,6 +19,7 @@ import * as path from "node:path" import { initMain, parseInitArgs } from "../cli.js" import { projectKeyFor, resolveProjectIdentity } from "../../project-store.js" +import { runMain } from "../../test-runner.js" async function mkWs(files: Record = {}): Promise { const ws = await fs.mkdtemp(path.join(os.tmpdir(), "hc-init-cli-")) @@ -226,26 +227,4 @@ const tests: Array<[string, () => Promise]> = [ ["existing .gitignore coverage reported as unchanged, file byte-identical", testExistingGitignoreCoverageReportedAsUnchanged], ] -async function main(): Promise { - let failed = 0 - for (const [name, fn] of tests) { - try { - await fn() - console.log(` ok ${name}`) - } catch (err) { - failed++ - console.error(` FAIL ${name}`) - console.error(err instanceof Error ? err.stack ?? err.message : String(err)) - } - } - if (failed > 0) { - console.error(`\n${failed} test(s) failed`) - process.exit(1) - } - console.log(`\nAll ${tests.length} init cli tests passed`) -} - -main().catch((err) => { - console.error("Test runner crashed:", err) - process.exit(1) -}) +runMain("init cli", tests) diff --git a/src/init/__tests__/gitignore.test.ts b/src/init/__tests__/gitignore.test.ts index ac35edd..229c601 100644 --- a/src/init/__tests__/gitignore.test.ts +++ b/src/init/__tests__/gitignore.test.ts @@ -12,6 +12,7 @@ import * as os from "node:os" import * as path from "node:path" import { ensureWorkspaceGitignore } from "../gitignore.js" +import { runMain } from "../../test-runner.js" const EXPECTED_ENTRY = "# headlesscode session artifacts (events, reports, usage, scratch, memory)\n/.headlesscode/\n" @@ -141,26 +142,4 @@ const tests: Array<[string, () => Promise]> = [ ["missing / non-directory workspace is a silent no-op (no .gitignore created)", testNonDirectoryWorkspaceIsSkipped], ] -async function main(): Promise { - let failed = 0 - for (const [name, fn] of tests) { - try { - await fn() - console.log(` ok ${name}`) - } catch (err) { - failed++ - console.error(` FAIL ${name}`) - console.error(err instanceof Error ? err.stack ?? err.message : String(err)) - } - } - if (failed > 0) { - console.error(`\n${failed} test(s) failed`) - process.exit(1) - } - console.log(`\nAll ${tests.length} gitignore tests passed`) -} - -main().catch((err) => { - console.error("Test runner crashed:", err) - process.exit(1) -}) +runMain("gitignore", tests) diff --git a/src/llm/__tests__/muse.test.ts b/src/llm/__tests__/muse.test.ts index 5113497..ec0be43 100644 --- a/src/llm/__tests__/muse.test.ts +++ b/src/llm/__tests__/muse.test.ts @@ -20,6 +20,7 @@ import { resolveLlmProviderOrReport, } from "../muse.js" import type { LlmRequest } from "../../engine/types.js" +import { runMain } from "../../test-runner.js" const baseRequest: LlmRequest = { model: "muse-spark-1.3", @@ -452,23 +453,4 @@ const tests: Array<[string, () => Promise]> = [ ["MUSE_BASE_URL is the documented Meta endpoint", testBaseUrlDefault], ] -async function main(): Promise { - let failed = 0 - for (const [name, fn] of tests) { - try { - await fn() - console.log(` ok ${name}`) - } catch (err) { - failed++ - console.error(` FAIL ${name}`) - console.error(err instanceof Error ? err.stack : err) - } - } - if (failed > 0) { - console.error(`\n${failed} test(s) failed`) - process.exit(1) - } - console.log(`\nAll ${tests.length} muse client tests passed`) -} - -main() +runMain("muse client", tests) diff --git a/src/llm/__tests__/openrouter.test.ts b/src/llm/__tests__/openrouter.test.ts index e10aca2..75f8a79 100644 --- a/src/llm/__tests__/openrouter.test.ts +++ b/src/llm/__tests__/openrouter.test.ts @@ -13,6 +13,7 @@ import { estimateCost } from "../../budget/cost.js" import { buildRequestBody, isRetryableOpenRouterError, OpenRouterClient, OpenRouterError, parseReasoningEffort } from "../openrouter.js" import { PromptByteBudgetError, TOOL_RESULT_BUDGET_NOTICE } from "../request-budget.js" import type { LlmRequest } from "../../engine/types.js" +import { runMain } from "../../test-runner.js" const baseRequest: LlmRequest = { model: "irrelevant-here", @@ -1109,23 +1110,4 @@ const tests: Array<[string, () => Promise]> = [ ["fetchModelInfo: one fetch resolves window AND price together", testFetchModelInfoSharesOneFetch], ] -async function main(): Promise { - let failed = 0 - for (const [name, fn] of tests) { - try { - await fn() - console.log(` ok ${name}`) - } catch (err) { - failed++ - console.error(` FAIL ${name}`) - console.error(err instanceof Error ? err.stack : err) - } - } - if (failed > 0) { - console.error(`\n${failed} test(s) failed`) - process.exit(1) - } - console.log(`\nAll ${tests.length} openrouter request-building tests passed`) -} - -main() +runMain("openrouter request-building", tests) diff --git a/src/memory/__tests__/embed.test.ts b/src/memory/__tests__/embed.test.ts index 36e19c7..59d8539 100644 --- a/src/memory/__tests__/embed.test.ts +++ b/src/memory/__tests__/embed.test.ts @@ -7,6 +7,7 @@ import assert from "node:assert/strict" import { cosine, createLocalEmbedder, similarity } from "../embed.js" +import { runMain } from "../../test-runner.js" // ─── Tests ─────────────────────────────────────────────────────────────────── @@ -72,23 +73,4 @@ const tests: Array<[string, () => void]> = [ ["createLocalEmbedder validates dims", testDimsValidation], ] -function main(): void { - let failed = 0 - for (const [name, fn] of tests) { - try { - fn() - console.log(` ok ${name}`) - } catch (err) { - failed++ - console.error(` FAIL ${name}`) - console.error(err instanceof Error ? err.stack ?? err.message : String(err)) - } - } - if (failed > 0) { - console.error(`\n${failed} test(s) failed`) - process.exit(1) - } - console.log(`\nAll ${tests.length} embed tests passed`) -} - -main() +runMain("embed", tests) diff --git a/src/memory/__tests__/local.test.ts b/src/memory/__tests__/local.test.ts index 3bd0523..3f4b2b3 100644 --- a/src/memory/__tests__/local.test.ts +++ b/src/memory/__tests__/local.test.ts @@ -11,6 +11,7 @@ import * as path from "node:path" import { LocalMemoryStore, sanitizeProject } from "../local.js" import type { SessionSummary } from "../types.js" +import { runMain } from "../../test-runner.js" async function tmpDir(prefix: string): Promise { return fs.mkdtemp(path.join(os.tmpdir(), prefix)) @@ -198,26 +199,4 @@ const tests: Array<[string, () => Promise]> = [ ["sanitizeProject produces filesystem-safe slugs", testSanitizeProject], ] -async function main(): Promise { - let failed = 0 - for (const [name, fn] of tests) { - try { - await fn() - console.log(` ok ${name}`) - } catch (err) { - failed++ - console.error(` FAIL ${name}`) - console.error(err instanceof Error ? err.stack ?? err.message : String(err)) - } - } - if (failed > 0) { - console.error(`\n${failed} test(s) failed`) - process.exit(1) - } - console.log(`\nAll ${tests.length} local store tests passed`) -} - -main().catch((err) => { - console.error("Test runner crashed:", err) - process.exit(1) -}) +runMain("local store", tests) diff --git a/src/memory/__tests__/memory-wiring.test.ts b/src/memory/__tests__/memory-wiring.test.ts index 0a561cb..ab0391e 100644 --- a/src/memory/__tests__/memory-wiring.test.ts +++ b/src/memory/__tests__/memory-wiring.test.ts @@ -21,6 +21,7 @@ import { HeadlessSession } from "../../engine/loop.js" import type { ChatMessage, LlmClient, LlmRequest, LlmResponse } from "../../engine/types.js" import { LocalMemoryStore } from "../local.js" import type { MemoryStore } from "../types.js" +import { runMain } from "../../test-runner.js" const PROJECT = "wiring-project" @@ -204,26 +205,4 @@ const tests: Array<[string, () => Promise]> = [ ["memory failures are non-fatal to the session", testMemoryFailureDoesNotFailSession], ] -async function main(): Promise { - let failed = 0 - for (const [name, fn] of tests) { - try { - await fn() - console.log(` ok ${name}`) - } catch (err) { - failed++ - console.error(` FAIL ${name}`) - console.error(err instanceof Error ? err.stack ?? err.message : String(err)) - } - } - if (failed > 0) { - console.error(`\n${failed} test(s) failed`) - process.exit(1) - } - console.log(`\nAll ${tests.length} memory-wiring tests passed`) -} - -main().catch((err) => { - console.error("Test runner crashed:", err) - process.exit(1) -}) +runMain("memory-wiring", tests) diff --git a/src/memory/__tests__/summarizer.test.ts b/src/memory/__tests__/summarizer.test.ts index 18ffff9..cf1a7f5 100644 --- a/src/memory/__tests__/summarizer.test.ts +++ b/src/memory/__tests__/summarizer.test.ts @@ -9,6 +9,7 @@ import assert from "node:assert/strict" import { buildRollingSummary, extractFacts, extractSessionSummary } from "../summarizer.js" import type { ChatMessage, SessionResult } from "../../engine/types.js" import type { SessionSummary } from "../types.js" +import { runMain } from "../../test-runner.js" function toolMessage(name: string, content: string): ChatMessage { return { role: "tool", tool_call_id: `c_${Math.random()}`, name, content } @@ -150,23 +151,4 @@ const tests: Array<[string, () => void]> = [ ["rolling summary renders key facts", testRollingSummaryIncludesKeyFacts], ] -function main(): void { - let failed = 0 - for (const [name, fn] of tests) { - try { - fn() - console.log(` ok ${name}`) - } catch (err) { - failed++ - console.error(` FAIL ${name}`) - console.error(err instanceof Error ? err.stack ?? err.message : String(err)) - } - } - if (failed > 0) { - console.error(`\n${failed} test(s) failed`) - process.exit(1) - } - console.log(`\nAll ${tests.length} summarizer tests passed`) -} - -main() +runMain("summarizer", tests) diff --git a/src/memory/__tests__/uwuchat.test.ts b/src/memory/__tests__/uwuchat.test.ts index 683a27a..87401cf 100644 --- a/src/memory/__tests__/uwuchat.test.ts +++ b/src/memory/__tests__/uwuchat.test.ts @@ -8,6 +8,7 @@ import assert from "node:assert/strict" import { API_PREFIX, UwUChatMemoryError, UwUChatMemoryStore } from "../uwuchat.js" +import { runMain } from "../../test-runner.js" const BASE = "https://uwu.example" @@ -163,26 +164,4 @@ const tests: Array<[string, () => Promise | void]> = [ ["typed UwUChatMemoryError on non-2xx", testTypedErrorOnNon2xx], ] -async function main(): Promise { - let failed = 0 - for (const [name, fn] of tests) { - try { - await fn() - console.log(` ok ${name}`) - } catch (err) { - failed++ - console.error(` FAIL ${name}`) - console.error(err instanceof Error ? err.stack ?? err.message : String(err)) - } - } - if (failed > 0) { - console.error(`\n${failed} test(s) failed`) - process.exit(1) - } - console.log(`\nAll ${tests.length} uwuchat tests passed`) -} - -main().catch((err) => { - console.error("Test runner crashed:", err) - process.exit(1) -}) +runMain("uwuchat", tests) diff --git a/src/migrate/cli.test.ts b/src/migrate/cli.test.ts index ee9f7db..708a4f8 100644 --- a/src/migrate/cli.test.ts +++ b/src/migrate/cli.test.ts @@ -12,6 +12,7 @@ import * as path from "node:path" import { migrateMain } from "./cli.js" import { projectStoreRoot, resolveProjectDataDir } from "../project-store.js" +import { runMain } from "../test-runner.js" async function mkTmp(prefix: string): Promise { return fs.mkdtemp(path.join(os.tmpdir(), prefix)) @@ -86,26 +87,4 @@ const tests: Array<[string, () => Promise]> = [ ["migrate respects the store-root override", testMigrateUsesStoreRoot], ] -async function main(): Promise { - let failed = 0 - for (const [name, fn] of tests) { - try { - await fn() - console.log(` ok ${name}`) - } catch (err) { - failed++ - console.error(` FAIL ${name}`) - console.error(err instanceof Error ? err.stack ?? err.message : String(err)) - } - } - if (failed > 0) { - console.error(`\n${failed} test(s) failed`) - process.exit(1) - } - console.log(`\nAll ${tests.length} migrate CLI tests passed`) -} - -main().catch((err) => { - console.error("Test runner crashed:", err) - process.exit(1) -}) +runMain("migrate CLI", tests) diff --git a/src/orchestrator/__tests__/analyze-cli.test.ts b/src/orchestrator/__tests__/analyze-cli.test.ts index f8a133a..f1955a3 100644 --- a/src/orchestrator/__tests__/analyze-cli.test.ts +++ b/src/orchestrator/__tests__/analyze-cli.test.ts @@ -11,6 +11,7 @@ import * as path from "node:path" import { appendEvent, type EventRecord } from "../../engine/events.js" import { analyzeCliMain, parseAnalyzeArgs } from "../analyze-cli.js" +import { runMain } from "../../test-runner.js" // ─── Helpers ───────────────────────────────────────────────────────────────── @@ -148,26 +149,4 @@ const tests: [string, () => Promise][] = [ ["main --json prints raw analysis", testMainJsonOutput], ] -async function main(): Promise { - let failed = 0 - for (const [name, fn] of tests) { - try { - await fn() - console.log(` ok ${name}`) - } catch (err) { - failed++ - console.error(` FAIL ${name}`) - console.error(err instanceof Error ? (err.stack ?? err.message) : String(err)) - } - } - if (failed > 0) { - console.error(`\n${failed} test(s) failed`) - process.exit(1) - } - console.log(`\nAll ${tests.length} analyze-cli tests passed`) -} - -main().catch((err) => { - console.error("Test runner crashed:", err) - process.exit(1) -}) +runMain("analyze-cli", tests) diff --git a/src/orchestrator/__tests__/arg-cursor.test.ts b/src/orchestrator/__tests__/arg-cursor.test.ts index 57d4f5b..422993e 100644 --- a/src/orchestrator/__tests__/arg-cursor.test.ts +++ b/src/orchestrator/__tests__/arg-cursor.test.ts @@ -9,6 +9,7 @@ import assert from "node:assert/strict" import { createArgCursor } from "../arg-cursor.js" +import { runMain } from "../../test-runner.js" function cursor(argv: string[], i: number): { flag: string @@ -83,26 +84,4 @@ const tests: Array<[string, () => void | Promise]> = [ ["nextRaw accepts dashed values", testNextRawAcceptsDashedValue], ] -async function main(): Promise { - let failed = 0 - for (const [name, fn] of tests) { - try { - await fn() - console.log(` ok ${name}`) - } catch (err) { - failed++ - console.error(` FAIL ${name}`) - console.error(err instanceof Error ? err.stack ?? err.message : String(err)) - } - } - if (failed > 0) { - console.error(`\n${failed} test(s) failed`) - process.exit(1) - } - console.log(`\nAll ${tests.length} arg-cursor tests passed`) -} - -main().catch((err) => { - console.error("Test runner crashed:", err) - process.exit(1) -}) +runMain("arg-cursor", tests) diff --git a/src/orchestrator/__tests__/auto-split.test.ts b/src/orchestrator/__tests__/auto-split.test.ts index 5f8a6d7..2bfb74e 100644 --- a/src/orchestrator/__tests__/auto-split.test.ts +++ b/src/orchestrator/__tests__/auto-split.test.ts @@ -12,6 +12,7 @@ import assert from "node:assert/strict" import { autoSplitOversizedIssues, proposeSemanticSplit } from "../auto-split.js" import type { IssueSizeWarning, SplitIssue } from "../split.js" import type { LlmClient, LlmRequest, LlmResponse } from "../../engine/types.js" +import { runMain } from "../../test-runner.js" function issue(number: number, title: string, body?: string): SplitIssue { return { number, title, body } @@ -241,26 +242,4 @@ const tests: Array<[string, () => Promise]> = [ ["autoSplitOversizedIssues: preserves the relative order of unflagged issues", testAutoSplitPreservesOrderOfUnflaggedIssues], ] -async function main(): Promise { - let failed = 0 - for (const [name, fn] of tests) { - try { - await fn() - console.log(` ok ${name}`) - } catch (err) { - failed++ - console.error(` FAIL ${name}`) - console.error(err instanceof Error ? err.stack ?? err.message : String(err)) - } - } - if (failed > 0) { - console.error(`\n${failed} test(s) failed`) - process.exit(1) - } - console.log(`\nAll ${tests.length} auto-split tests passed`) -} - -main().catch((err) => { - console.error("Test runner crashed:", err) - process.exit(1) -}) +runMain("auto-split", tests) diff --git a/src/orchestrator/__tests__/cleanup.test.ts b/src/orchestrator/__tests__/cleanup.test.ts index 2658f0f..a72664e 100644 --- a/src/orchestrator/__tests__/cleanup.test.ts +++ b/src/orchestrator/__tests__/cleanup.test.ts @@ -35,6 +35,7 @@ import { type OrchestratorGroup, type OrchestratorState, } from "../state.js" +import { runMain } from "../../test-runner.js" const ownedFixtureDirectories = new Set() const FIXTURE_REMOVE_ATTEMPTS = 5 @@ -771,26 +772,4 @@ const tests: Array<[string, () => Promise]> = [ ["orchestrate status shows the cleanup column (eligible) for a merged terminal group", testStatusShowsCleanupColumn], ] -async function main(): Promise { - let failed = 0 - for (const [name, fn] of tests) { - try { - await fn() - console.log(` ok ${name}`) - } catch (err) { - failed++ - console.error(` FAIL ${name}`) - console.error(err instanceof Error ? err.stack ?? err.message : String(err)) - } - } - if (failed > 0) { - console.error(`\n${failed} test(s) failed`) - process.exit(1) - } - console.log(`\nAll ${tests.length} cleanup tests passed`) -} - -main().catch((err) => { - console.error("Test runner crashed:", err) - process.exit(1) -}) +runMain("cleanup", tests) diff --git a/src/orchestrator/__tests__/cli.test.ts b/src/orchestrator/__tests__/cli.test.ts index 0567844..d431a3f 100644 --- a/src/orchestrator/__tests__/cli.test.ts +++ b/src/orchestrator/__tests__/cli.test.ts @@ -34,6 +34,7 @@ import { watchGroups } from "../watch.js" import { defaultState, loadStateSync, saveStateSync, updateGroup, type OrchestratorGroup, type OrchestratorState } from "../state.js" import type { ReviewResult } from "../reviewer.js" import type { QaResult } from "../../qa/qa.js" +import { runSuites } from "../../test-runner.js" async function tmpRepo(): Promise { return fs.mkdtemp(path.join(os.tmpdir(), "headlesscode-cli-rework-")) @@ -1839,16 +1840,7 @@ async function main(): Promise { process.env.HEADLESSCODE_DATA_DIR = storeTmp let failed = 0 try { - for (const [name, fn] of tests) { - try { - await fn() - console.log(` ok ${name}`) - } catch (err) { - failed++ - console.error(` FAIL ${name}`) - console.error(err instanceof Error ? err.stack ?? err.message : String(err)) - } - } + failed = await runSuites("cli", tests) } finally { delete process.env.HEADLESSCODE_DATA_DIR await fs.rm(storeTmp, { recursive: true, force: true }) @@ -1857,7 +1849,6 @@ async function main(): Promise { console.error(`\n${failed} test(s) failed`) process.exit(1) } - console.log(`\nAll ${tests.length} cli tests passed`) } main().catch((err) => { diff --git a/src/orchestrator/__tests__/cost-estimate.test.ts b/src/orchestrator/__tests__/cost-estimate.test.ts index 0701b63..776933f 100644 --- a/src/orchestrator/__tests__/cost-estimate.test.ts +++ b/src/orchestrator/__tests__/cost-estimate.test.ts @@ -23,6 +23,7 @@ import { VERIFICATION_MULTIPLIER_FULL_SAMPLES, verificationMultiplier, } from "../cost-estimate.js" +import { runMain } from "../../test-runner.js" // ─── Helpers ───────────────────────────────────────────────────────────────── @@ -538,26 +539,4 @@ const tests: Array<[string, () => Promise]> = [ ["LOW-CONFIDENCE flagged on an under-sampled direct match (#126)", testLowConfidenceFlaggedOnUnderSampledDirectMatch], ] -async function main(): Promise { - let failed = 0 - for (const [name, fn] of tests) { - try { - await fn() - console.log(` ok ${name}`) - } catch (err) { - failed++ - console.error(` FAIL ${name}`) - console.error(err instanceof Error ? (err.stack ?? err.message) : String(err)) - } - } - if (failed > 0) { - console.error(`\n${failed} test(s) failed`) - process.exit(1) - } - console.log(`\nAll ${tests.length} cost-estimate tests passed`) -} - -main().catch((err) => { - console.error("Test runner crashed:", err) - process.exit(1) -}) +runMain("cost-estimate", tests) diff --git a/src/orchestrator/__tests__/cost-history-cli.test.ts b/src/orchestrator/__tests__/cost-history-cli.test.ts index b1ac56c..3a92be7 100644 --- a/src/orchestrator/__tests__/cost-history-cli.test.ts +++ b/src/orchestrator/__tests__/cost-history-cli.test.ts @@ -12,6 +12,7 @@ import { execFileSync } from "node:child_process" import { appendCostHistoryRecord, appendSessionCostRecord, type CostHistoryRecord, type SessionCostRecord } from "../cost-history.js" import { costHistoryCliMain, parseCostHistoryArgs } from "../cost-history-cli.js" +import { runSuites } from "../../test-runner.js" // ─── Helpers ───────────────────────────────────────────────────────────────── @@ -327,16 +328,7 @@ async function main(): Promise { process.env.HEADLESSCODE_DATA_DIR = storeTmp let failed = 0 try { - for (const [name, fn] of tests) { - try { - await fn() - console.log(` ok ${name}`) - } catch (err) { - failed++ - console.error(` FAIL ${name}`) - console.error(err instanceof Error ? (err.stack ?? err.message) : String(err)) - } - } + failed = await runSuites("cost-history-cli", tests) } finally { delete process.env.HEADLESSCODE_DATA_DIR await fs.rm(storeTmp, { recursive: true, force: true }) @@ -345,7 +337,6 @@ async function main(): Promise { console.error(`\n${failed} test(s) failed`) process.exit(1) } - console.log(`\nAll ${tests.length} cost-history-cli tests passed`) } main().catch((err) => { diff --git a/src/orchestrator/__tests__/cost-history.test.ts b/src/orchestrator/__tests__/cost-history.test.ts index da58658..a789354 100644 --- a/src/orchestrator/__tests__/cost-history.test.ts +++ b/src/orchestrator/__tests__/cost-history.test.ts @@ -21,6 +21,7 @@ import { type CostHistoryRecord, } from "../cost-history.js" import type { OrchestratorGroup } from "../state.js" +import { runSuites } from "../../test-runner.js" // ─── Helpers ───────────────────────────────────────────────────────────────── @@ -393,16 +394,7 @@ async function main(): Promise { process.env.HEADLESSCODE_DATA_DIR = storeTmp let failed = 0 try { - for (const [name, fn] of tests) { - try { - await fn() - console.log(` ok ${name}`) - } catch (err) { - failed++ - console.error(` FAIL ${name}`) - console.error(err instanceof Error ? (err.stack ?? err.message) : String(err)) - } - } + failed = await runSuites("cost-history", tests) } finally { delete process.env.HEADLESSCODE_DATA_DIR await fs.rm(storeTmp, { recursive: true, force: true }) @@ -411,7 +403,6 @@ async function main(): Promise { console.error(`\n${failed} test(s) failed`) process.exit(1) } - console.log(`\nAll ${tests.length} cost-history tests passed`) } main().catch((err) => { diff --git a/src/orchestrator/__tests__/git-sync.test.ts b/src/orchestrator/__tests__/git-sync.test.ts index 0537b76..f34479c 100644 --- a/src/orchestrator/__tests__/git-sync.test.ts +++ b/src/orchestrator/__tests__/git-sync.test.ts @@ -21,6 +21,7 @@ import { syncWarningLines, TRIVIAL_DRIFT_AHEAD, } from "../git-sync.js" +import { runMain } from "../../test-runner.js" function git(repo: string, args: string[]): string { return execFileSync("git", ["-C", repo, ...args], { encoding: "utf-8", stdio: ["ignore", "pipe", "ignore"] }).trim() @@ -274,26 +275,4 @@ const tests: Array<[string, () => Promise]> = [ ["TRIVIAL_DRIFT_AHEAD threshold is exported", testTrivialDriftThresholdIsExported], ] -async function main(): Promise { - let failed = 0 - for (const [name, fn] of tests) { - try { - await fn() - console.log(` ok ${name}`) - } catch (err) { - failed++ - console.error(` FAIL ${name}`) - console.error(err instanceof Error ? err.stack ?? err.message : String(err)) - } - } - if (failed > 0) { - console.error(`\n${failed} test(s) failed`) - process.exit(1) - } - console.log(`\nAll ${tests.length} git-sync tests passed`) -} - -main().catch((err) => { - console.error("Test runner crashed:", err) - process.exit(1) -}) +runMain("git-sync", tests) diff --git a/src/orchestrator/__tests__/log-analysis.test.ts b/src/orchestrator/__tests__/log-analysis.test.ts index 0f2e4d8..66e0948 100644 --- a/src/orchestrator/__tests__/log-analysis.test.ts +++ b/src/orchestrator/__tests__/log-analysis.test.ts @@ -12,6 +12,7 @@ import * as path from "node:path" import { appendEvent, type EventRecord } from "../../engine/events.js" import { analyzeWorktreeSessions, formatAnalysisReport, listSessionIds } from "../log-analysis.js" +import { runMain } from "../../test-runner.js" // ─── Helpers ───────────────────────────────────────────────────────────────── @@ -175,26 +176,4 @@ const tests: [string, () => Promise][] = [ ["formatAnalysisReport renders readable text", testFormatAnalysisReportIsReadable], ] -async function main(): Promise { - let failed = 0 - for (const [name, fn] of tests) { - try { - await fn() - console.log(` ok ${name}`) - } catch (err) { - failed++ - console.error(` FAIL ${name}`) - console.error(err instanceof Error ? (err.stack ?? err.message) : String(err)) - } - } - if (failed > 0) { - console.error(`\n${failed} test(s) failed`) - process.exit(1) - } - console.log(`\nAll ${tests.length} log-analysis tests passed`) -} - -main().catch((err) => { - console.error("Test runner crashed:", err) - process.exit(1) -}) +runMain("log-analysis", tests) diff --git a/src/orchestrator/__tests__/main-tree-tripwire.test.ts b/src/orchestrator/__tests__/main-tree-tripwire.test.ts index a9aad06..82c4499 100644 --- a/src/orchestrator/__tests__/main-tree-tripwire.test.ts +++ b/src/orchestrator/__tests__/main-tree-tripwire.test.ts @@ -9,6 +9,7 @@ import { fileURLToPath } from "node:url" import { removeHarnessArtifacts } from "../cleanup.js" import { buildStatusSummary, formatStatusText } from "../status.js" import { reconcileGroups } from "../status.js" +import { runMain } from "../../test-runner.js" import { DEFAULT_STALL_TIMEOUT_MS, inspectGroup, @@ -202,14 +203,14 @@ async function cleanupRemovesMarkers(dir: string): Promise { } } -const tests: Array<[string, (fx: Fixture) => Promise]> = [ - ["clean run leaves no marker", cleanRunLeavesNoMarker], - ["escaped write is reported", escapedWriteIsReported], - ["orchestrator dirs excluded", orchestratorDirsExcluded], - ["missing baseline reports all", missingBaselineReportsAll], - ["main checkout refuses loudly", mainCheckoutRefusesLoudly], - ["qa role uses qa markers", qaRoleUsesQaMarkers], - ["script and reader agree", scriptAndReaderAgree], +const tests: Array<[string, () => Promise]> = [ + ["clean run leaves no marker", () => withFixture(cleanRunLeavesNoMarker)], + ["escaped write is reported", () => withFixture(escapedWriteIsReported)], + ["orchestrator dirs excluded", () => withFixture(orchestratorDirsExcluded)], + ["missing baseline reports all", () => withFixture(missingBaselineReportsAll)], + ["main checkout refuses loudly", () => withFixture(mainCheckoutRefusesLoudly)], + ["qa role uses qa markers", () => withFixture(qaRoleUsesQaMarkers)], + ["script and reader agree", () => withFixture(scriptAndReaderAgree)], ["reader reads marker lines", () => withTmpDir(readerReadsMarkerLines)], ["inspect persists dirty", () => withTmpDir(inspectPersistsDirty)], ["reconcile persists dirty", () => withTmpDir(reconcilePersistsDirty)], @@ -217,27 +218,4 @@ const tests: Array<[string, (fx: Fixture) => Promise]> = [ ["cleanup removes markers", () => withTmpDir(cleanupRemovesMarkers)], ] -async function main(): Promise { - let failed = 0 - for (const [name, fn] of tests) { - try { - await withFixture(fn) - console.log(` ok ${name}`) - } catch (err) { - failed++ - console.error(` FAIL ${name}`) - const detail = err instanceof Error ? err.stack ?? err.message : String(err) - console.error(detail) - } - } - if (failed > 0) { - console.error(`\n${failed} test(s) failed`) - process.exit(1) - } - console.log(`\nAll ${tests.length} main-tree-tripwire tests passed`) -} - -main().catch((err) => { - console.error("Test runner crashed:", err) - process.exit(1) -}) +runMain("main-tree-tripwire", tests) diff --git a/src/orchestrator/__tests__/merge-check.test.ts b/src/orchestrator/__tests__/merge-check.test.ts index 66fa679..898c1be 100644 --- a/src/orchestrator/__tests__/merge-check.test.ts +++ b/src/orchestrator/__tests__/merge-check.test.ts @@ -18,6 +18,7 @@ import * as os from "node:os" import * as path from "node:path" import { checkMergedByAncestor, checkMergedByGitHubPr } from "../merge-check.js" +import { runMain } from "../../test-runner.js" function git(repo: string, args: string[]): string { return execFileSync("git", ["-C", repo, ...args], { encoding: "utf-8", stdio: ["ignore", "pipe", "ignore"] }).trim() @@ -246,26 +247,4 @@ const tests: Array<[string, () => Promise]> = [ ["github-pr: network error fails closed as unknown", testGitHubNetworkErrorFailsClosed], ] -async function main(): Promise { - let failed = 0 - for (const [name, fn] of tests) { - try { - await fn() - console.log(` ok ${name}`) - } catch (err) { - failed++ - console.error(` FAIL ${name}`) - console.error(err instanceof Error ? err.stack ?? err.message : String(err)) - } - } - if (failed > 0) { - console.error(`\n${failed} test(s) failed`) - process.exit(1) - } - console.log(`\nAll ${tests.length} merge-check tests passed`) -} - -main().catch((err) => { - console.error("Test runner crashed:", err) - process.exit(1) -}) +runMain("merge-check", tests) diff --git a/src/orchestrator/__tests__/model-resolution.test.ts b/src/orchestrator/__tests__/model-resolution.test.ts index 22d0549..0bacf9b 100644 --- a/src/orchestrator/__tests__/model-resolution.test.ts +++ b/src/orchestrator/__tests__/model-resolution.test.ts @@ -21,6 +21,7 @@ import { resolveModelForMode } from "../../config/mode-models.js" import { handleReviewVerdict, parseOrchestrateArgs } from "../cli.js" import type { OrchestratorGroup } from "../state.js" import type { ReviewResult } from "../reviewer.js" +import { runSuites } from "../../test-runner.js" async function tmpRepo(): Promise { return fs.mkdtemp(path.join(os.tmpdir(), "headlesscode-model-res-")) @@ -157,16 +158,7 @@ async function main(): Promise { process.env.HEADLESSCODE_DATA_DIR = storeTmp let failed = 0 try { - for (const [name, fn] of tests) { - try { - await fn() - console.log(` ok ${name}`) - } catch (err) { - failed++ - console.error(` FAIL ${name}`) - console.error(err instanceof Error ? err.stack ?? err.message : String(err)) - } - } + failed = await runSuites("model-resolution", tests) } finally { delete process.env.HEADLESSCODE_DATA_DIR await fs.rm(storeTmp, { recursive: true, force: true }) @@ -175,7 +167,6 @@ async function main(): Promise { console.error(`\n${failed} test(s) failed`) process.exit(1) } - console.log(`\nAll ${tests.length} model-resolution tests passed`) } main().catch((err) => { diff --git a/src/orchestrator/__tests__/pipeline.test.ts b/src/orchestrator/__tests__/pipeline.test.ts index 30211ec..14dc1ef 100644 --- a/src/orchestrator/__tests__/pipeline.test.ts +++ b/src/orchestrator/__tests__/pipeline.test.ts @@ -25,6 +25,7 @@ import { defaultResearchTaskText, parseFilingResult, } from "../pipeline.js" +import { runMain } from "../../test-runner.js" function testParseFilingResultStructuredLine(): void { // The required `ISSUES:` line is authoritative. @@ -92,26 +93,4 @@ const tests: Array<[string, () => void | Promise]> = [ ["mode constants match .roomodes slugs", testModeConstants], ] -async function main(): Promise { - let failed = 0 - for (const [name, fn] of tests) { - try { - await fn() - console.log(` ok ${name}`) - } catch (err) { - failed++ - console.error(` FAIL ${name}`) - console.error(err instanceof Error ? err.stack ?? err.message : String(err)) - } - } - if (failed > 0) { - console.error(`\n${failed} test(s) failed`) - process.exit(1) - } - console.log(`\nAll ${tests.length} pipeline tests passed`) -} - -main().catch((err) => { - console.error("Test runner crashed:", err) - process.exit(1) -}) +runMain("pipeline", tests) diff --git a/src/orchestrator/__tests__/resume.test.ts b/src/orchestrator/__tests__/resume.test.ts index c882bac..6574479 100644 --- a/src/orchestrator/__tests__/resume.test.ts +++ b/src/orchestrator/__tests__/resume.test.ts @@ -39,6 +39,7 @@ import { } from "../resume.js" import { defaultState, loadStateSync, saveStateSync, type OrchestratorGroup, type OrchestratorState } from "../state.js" import { assessVerificationActivity, commandIdentity, parseRequiredCommandIdentities } from "../verification-gate.js" +import { runSuites } from "../../test-runner.js" async function tmpRepo(): Promise { return fs.mkdtemp(path.join(os.tmpdir(), "headlesscode-resume-")) @@ -1355,16 +1356,7 @@ async function main(): Promise { process.env.HEADLESSCODE_DATA_DIR = storeTmp let failed = 0 try { - for (const [name, fn] of tests) { - try { - await fn() - console.log(` ok ${name}`) - } catch (err) { - failed++ - console.error(` FAIL ${name}`) - console.error(err instanceof Error ? err.stack ?? err.message : String(err)) - } - } + failed = await runSuites("resume", tests) } finally { delete process.env.HEADLESSCODE_DATA_DIR await fs.rm(storeTmp, { recursive: true, force: true }) @@ -1373,7 +1365,6 @@ async function main(): Promise { console.error(`\n${failed} test(s) failed`) process.exit(1) } - console.log(`\nAll ${tests.length} resume tests passed`) } main().catch((err) => { diff --git a/src/orchestrator/__tests__/review-gates.test.ts b/src/orchestrator/__tests__/review-gates.test.ts index a1faddf..27f0efe 100644 --- a/src/orchestrator/__tests__/review-gates.test.ts +++ b/src/orchestrator/__tests__/review-gates.test.ts @@ -8,6 +8,7 @@ import type { CodeIntel } from "../../codeintel/program.js" import type { GateRunner } from "../review-gates.js" import { runReviewGates } from "../review-gates.js" import { hasRealWorktreeChanges } from "../verification-gate.js" +import { runMain } from "../../test-runner.js" function git(repo: string, args: string[]): string { return execFileSync("git", ["-C", repo, ...args], { @@ -162,27 +163,4 @@ const tests: Array<[string, () => Promise]> = [ ["uncovered sources flagged", () => withFixture(uncoveredSourceFlagged)], ] -async function main(): Promise { - let failed = 0 - for (const [name, fn] of tests) { - try { - await fn() - console.log(` ok ${name}`) - } catch (err) { - failed++ - console.error(` FAIL ${name}`) - const detail = err instanceof Error ? err.stack ?? err.message : String(err) - console.error(detail) - } - } - if (failed > 0) { - console.error(`\n${failed} test(s) failed`) - process.exit(1) - } - console.log(`\nAll ${tests.length} review-gates tests passed`) -} - -main().catch((err) => { - console.error("Test runner crashed:", err) - process.exit(1) -}) +runMain("review-gates", tests) diff --git a/src/orchestrator/__tests__/reviewer.test.ts b/src/orchestrator/__tests__/reviewer.test.ts index ff9155c..96d2437 100644 --- a/src/orchestrator/__tests__/reviewer.test.ts +++ b/src/orchestrator/__tests__/reviewer.test.ts @@ -10,6 +10,7 @@ import * as path from "node:path" import { parseReviewResult, runReview, runReviewWithRetries } from "../reviewer.js" import type { ChatMessage, ChatToolCall, LlmClient, LlmRequest, LlmResponse } from "../../engine/types.js" +import { runMain } from "../../test-runner.js" // ─── Fake LLM client (same pattern as engine/__tests__/loop.test.ts) ───────── @@ -446,26 +447,4 @@ const tests: Array<[string, () => Promise]> = [ ["runReviewWithRetries: exhausts retries and returns the final error verdict", testRunReviewWithRetriesExhaustsRetries], ] -async function main(): Promise { - let failed = 0 - for (const [name, fn] of tests) { - try { - await fn() - console.log(` ok ${name}`) - } catch (err) { - failed++ - console.error(` FAIL ${name}`) - console.error(err instanceof Error ? err.stack ?? err.message : String(err)) - } - } - if (failed > 0) { - console.error(`\n${failed} test(s) failed`) - process.exit(1) - } - console.log(`\nAll ${tests.length} reviewer tests passed`) -} - -main().catch((err) => { - console.error("Test runner crashed:", err) - process.exit(1) -}) +runMain("reviewer", tests) diff --git a/src/orchestrator/__tests__/spawn-model.test.ts b/src/orchestrator/__tests__/spawn-model.test.ts index 10b8a71..762b019 100644 --- a/src/orchestrator/__tests__/spawn-model.test.ts +++ b/src/orchestrator/__tests__/spawn-model.test.ts @@ -23,6 +23,7 @@ import * as fsPromises from "node:fs/promises" import * as os from "node:os" import * as path from "node:path" import { fileURLToPath } from "node:url" +import { runSuites } from "../../test-runner.js" /** This repo root (parent of src/orchestrator/__tests__/). */ const REPO_ROOT = fileURLToPath(new URL("../../..", import.meta.url)) @@ -256,16 +257,7 @@ async function main(): Promise { process.env.HEADLESSCODE_DATA_DIR = storeTmp let failed = 0 try { - for (const [name, fn] of tests) { - try { - await fn() - console.log(` ok ${name}`) - } catch (err) { - failed++ - console.error(` FAIL ${name}`) - console.error(err instanceof Error ? err.stack ?? err.message : String(err)) - } - } + failed = await runSuites("spawn-model", tests) } finally { delete process.env.HEADLESSCODE_DATA_DIR await fsPromises.rm(storeTmp, { recursive: true, force: true }) @@ -274,7 +266,6 @@ async function main(): Promise { console.error(`\n${failed} test(s) failed`) process.exit(1) } - console.log(`\nAll ${tests.length} spawn-model tests passed`) } main().catch((err) => { diff --git a/src/orchestrator/__tests__/spawn-state-reset.test.ts b/src/orchestrator/__tests__/spawn-state-reset.test.ts index 4f351b5..e669b85 100644 --- a/src/orchestrator/__tests__/spawn-state-reset.test.ts +++ b/src/orchestrator/__tests__/spawn-state-reset.test.ts @@ -23,6 +23,7 @@ import * as fsPromises from "node:fs/promises" import * as os from "node:os" import * as path from "node:path" import { fileURLToPath } from "node:url" +import { runMain } from "../../test-runner.js" const REPO_ROOT = fileURLToPath(new URL("../../..", import.meta.url)) const SPAWN_SCRIPT = path.join(REPO_ROOT, "scripts", "spawn-parallel-worktrees.sh") @@ -164,26 +165,4 @@ const tests: Array<[string, () => Promise]> = [ ["reused worktree slot clears stale review_verdict/qa/cost_recorded (issue #28)", testReusedSlotClearsStaleReviewQaCostFields], ] -async function main(): Promise { - let failed = 0 - for (const [name, fn] of tests) { - try { - await fn() - console.log(` ok ${name}`) - } catch (err) { - failed++ - console.error(` FAIL ${name}`) - console.error(err instanceof Error ? (err.stack ?? err.message) : String(err)) - } - } - if (failed > 0) { - console.error(`\n${failed} test(s) failed`) - process.exit(1) - } - console.log(`\nAll ${tests.length} spawn-state-reset tests passed`) -} - -main().catch((err) => { - console.error("Test runner crashed:", err) - process.exit(1) -}) +runMain("spawn-state-reset", tests) diff --git a/src/orchestrator/__tests__/split.test.ts b/src/orchestrator/__tests__/split.test.ts index b9d3c8b..032d8e0 100644 --- a/src/orchestrator/__tests__/split.test.ts +++ b/src/orchestrator/__tests__/split.test.ts @@ -20,6 +20,7 @@ import { ISSUE_SIZE_WARN_THRESHOLD, } from "../split.js" import type { SplitIssue, WorktreeSpec } from "../split.js" +import { runMain } from "../../test-runner.js" function issue(number: number, title: string, body?: string): SplitIssue { return { number, title, body } @@ -278,26 +279,4 @@ const tests: Array<[string, () => Promise]> = [ ["issueSizeWarnings flags only 3+-section issues (issue #53)", testIssueSizeWarningsOnlyFlagsThreePlusSections], ] -async function main(): Promise { - let failed = 0 - for (const [name, fn] of tests) { - try { - await fn() - console.log(` ok ${name}`) - } catch (err) { - failed++ - console.error(` FAIL ${name}`) - console.error(err instanceof Error ? err.stack ?? err.message : String(err)) - } - } - if (failed > 0) { - console.error(`\n${failed} test(s) failed`) - process.exit(1) - } - console.log(`\nAll ${tests.length} split tests passed`) -} - -main().catch((err) => { - console.error("Test runner crashed:", err) - process.exit(1) -}) +runMain("split", tests) diff --git a/src/orchestrator/__tests__/state.test.ts b/src/orchestrator/__tests__/state.test.ts index 562c1fe..fc07d92 100644 --- a/src/orchestrator/__tests__/state.test.ts +++ b/src/orchestrator/__tests__/state.test.ts @@ -21,6 +21,7 @@ import { updateGroup, type OrchestratorState, } from "../state.js" +import { runMain } from "../../test-runner.js" async function tmpStatePath(): Promise { const dir = await fs.mkdtemp(path.join(os.tmpdir(), "headlesscode-state-")) @@ -209,26 +210,4 @@ const tests: Array<[string, () => Promise]> = [ ["patchGroup survives two concurrent OS processes racing the same state file (issue #43)", testPatchGroupSurvivesTwoConcurrentProcesses], ] -async function main(): Promise { - let failed = 0 - for (const [name, fn] of tests) { - try { - await fn() - console.log(` ok ${name}`) - } catch (err) { - failed++ - console.error(` FAIL ${name}`) - console.error(err instanceof Error ? err.stack ?? err.message : String(err)) - } - } - if (failed > 0) { - console.error(`\n${failed} test(s) failed`) - process.exit(1) - } - console.log(`\nAll ${tests.length} state tests passed`) -} - -main().catch((err) => { - console.error("Test runner crashed:", err) - process.exit(1) -}) +runMain("state", tests) diff --git a/src/orchestrator/__tests__/status-cli.test.ts b/src/orchestrator/__tests__/status-cli.test.ts index 89817b4..76278fd 100644 --- a/src/orchestrator/__tests__/status-cli.test.ts +++ b/src/orchestrator/__tests__/status-cli.test.ts @@ -22,6 +22,7 @@ import { type OrchestratorGroup, type OrchestratorState, } from "../state.js" +import { runMain } from "../../test-runner.js" async function tmpRepo(): Promise { return fs.mkdtemp(path.join(os.tmpdir(), "headlesscode-status-")) @@ -1037,26 +1038,4 @@ const tests: Array<[string, () => Promise]> = [ ["wait reports the reconciled group", testWaitReportsReconciled], ] -async function main(): Promise { - let failed = 0 - for (const [name, fn] of tests) { - try { - await fn() - console.log(` ok ${name}`) - } catch (err) { - failed++ - console.error(` FAIL ${name}`) - console.error(err instanceof Error ? err.stack ?? err.message : String(err)) - } - } - if (failed > 0) { - console.error(`\n${failed} test(s) failed`) - process.exit(1) - } - console.log(`\nAll ${tests.length} status-cli tests passed`) -} - -main().catch((err) => { - console.error("Test runner crashed:", err) - process.exit(1) -}) +runMain("status-cli", tests) diff --git a/src/orchestrator/__tests__/status.test.ts b/src/orchestrator/__tests__/status.test.ts index d498c95..b1c3fc9 100644 --- a/src/orchestrator/__tests__/status.test.ts +++ b/src/orchestrator/__tests__/status.test.ts @@ -15,6 +15,7 @@ import assert from "node:assert/strict" import { buildStatusSummary, formatStatusText } from "../status.js" import type { OrchestratorGroup, OrchestratorState } from "../state.js" +import { runMain } from "../../test-runner.js" // ─── Helpers ───────────────────────────────────────────────────────────────── @@ -149,26 +150,4 @@ const tests: [string, () => Promise][] = [ ["formatStatusText omits batch usage when absent", testFormattedTextOmitsBatchUsageWhenAbsent], ] -async function main(): Promise { - let failed = 0 - for (const [name, fn] of tests) { - try { - await fn() - console.log(` ok ${name}`) - } catch (err) { - failed++ - console.error(` FAIL ${name}`) - console.error(err instanceof Error ? (err.stack ?? err.message) : String(err)) - } - } - if (failed > 0) { - console.error(`\n${failed} test(s) failed`) - process.exit(1) - } - console.log(`\nAll ${tests.length} status tests passed`) -} - -main().catch((err) => { - console.error("Test runner crashed:", err) - process.exit(1) -}) +runMain("status", tests) diff --git a/src/orchestrator/__tests__/stop-cli.test.ts b/src/orchestrator/__tests__/stop-cli.test.ts index 22c923d..055d226 100644 --- a/src/orchestrator/__tests__/stop-cli.test.ts +++ b/src/orchestrator/__tests__/stop-cli.test.ts @@ -17,6 +17,7 @@ import * as path from "node:path" import { parseStopArgs, stopMain } from "../cli.js" import { defaultState, saveStateSync, updateGroup, type OrchestratorGroup, type OrchestratorState } from "../state.js" +import { runMain } from "../../test-runner.js" async function tmpRepo(): Promise { return fsp.mkdtemp(path.join(os.tmpdir(), "headlesscode-stop-")) @@ -235,26 +236,4 @@ const tests: Array<[string, () => Promise]> = [ ["stop: usage errors exit 2, --help exits 0", testStopUsageErrors], ] -async function main(): Promise { - let failed = 0 - for (const [name, fn] of tests) { - try { - await fn() - console.log(` ok ${name}`) - } catch (err) { - failed++ - console.error(` FAIL ${name}`) - console.error(err instanceof Error ? err.stack ?? err.message : String(err)) - } - } - if (failed > 0) { - console.error(`\n${failed} test(s) failed`) - process.exit(1) - } - console.log(`\nAll ${tests.length} stop-cli tests passed`) -} - -main().catch((err) => { - console.error("Test runner crashed:", err) - process.exit(1) -}) +runMain("stop-cli", tests) diff --git a/src/orchestrator/__tests__/task-files.test.ts b/src/orchestrator/__tests__/task-files.test.ts index ed8fedc..a93831a 100644 --- a/src/orchestrator/__tests__/task-files.test.ts +++ b/src/orchestrator/__tests__/task-files.test.ts @@ -29,6 +29,7 @@ import { buildTaskFileContent as cliBuildTaskFileContent, writeTaskFiles as cliWriteTaskFiles, } from "../cli.js" +import { runMain } from "../../test-runner.js" function renderSameNumberPair(): [string, string] { const slices = [ @@ -121,41 +122,14 @@ async function testOrchestratorCliReexportsTheLeaf(): Promise { assert.equal(cliWriteTaskFiles, writeTaskFiles) } -async function runOne(name: string, fn: () => Promise): Promise { - try { - await fn() - console.log(` ok ${name}`) - return true - } catch (error) { - console.error(` FAIL ${name}`) - if (error instanceof Error) console.error(error.stack ?? error) - else console.error(String(error)) - return false - } -} - -async function main(): Promise { - const tests: Array<[string, () => Promise]> = [ - ["inlines bodies", testBuildTaskFileContentInlineBodies], - ["falls back", testBuildTaskFileContentFallback], - ["plan-first mentions planning", testPlanFirstMentionsPlanning], - ["rework embeds findings", testReworkEmbedsFindings], - ["qa-rework and continuation", testQaReworkAndContinuation], - ["writeTaskFiles writes both", testWriteTaskFilesWritesBoth], - ["cli re-exports the leaf", testOrchestratorCliReexportsTheLeaf], - ] - let failed = 0 - for (const [name, fn] of tests) { - if (!(await runOne(name, fn))) failed++ - } - if (failed > 0) { - console.error(`\n${failed} test(s) failed`) - process.exit(1) - } - console.log(`\nAll ${tests.length} task-files tests passed`) -} +const tests: Array<[string, () => Promise]> = [ + ["inlines bodies", testBuildTaskFileContentInlineBodies], + ["falls back", testBuildTaskFileContentFallback], + ["plan-first mentions planning", testPlanFirstMentionsPlanning], + ["rework embeds findings", testReworkEmbedsFindings], + ["qa-rework and continuation", testQaReworkAndContinuation], + ["writeTaskFiles writes both", testWriteTaskFilesWritesBoth], + ["cli re-exports the leaf", testOrchestratorCliReexportsTheLeaf], +] -main().catch((error) => { - console.error("Test runner crashed:", error) - process.exit(1) -}) +runMain("task-files", tests) diff --git a/src/orchestrator/__tests__/terminal-status.test.ts b/src/orchestrator/__tests__/terminal-status.test.ts index 936d250..3a1a6f8 100644 --- a/src/orchestrator/__tests__/terminal-status.test.ts +++ b/src/orchestrator/__tests__/terminal-status.test.ts @@ -18,6 +18,7 @@ import { TERMINAL_STATUSES as STATUS_LIST, } from "../status.js" import { isTerminalStatus, TERMINAL_STATUSES } from "../terminal-status.js" +import { runMain } from "../../test-runner.js" async function testTerminalStatusesAreTerminal(): Promise { for (const status of ["done", "failed", "needs-human", "orphaned"]) { @@ -43,38 +44,11 @@ async function testStatusReexportsTheLeaf(): Promise { assert.equal(statusIsTerminal("running"), isTerminalStatus("running")) } -async function runOne(name: string, fn: () => Promise): Promise { - try { - await fn() - console.log(` ok ${name}`) - return true - } catch (error) { - console.error(` FAIL ${name}`) - if (error instanceof Error) console.error(error.stack ?? error) - else console.error(String(error)) - return false - } -} - -async function main(): Promise { - const tests: Array<[string, () => Promise]> = [ - ["terminal statuses are terminal", testTerminalStatusesAreTerminal], - ["live statuses are not terminal", testLiveStatusesAreNotTerminal], - ["blocked stays non-terminal", testBlockedStaysNonTerminal], - ["status re-exports the leaf", testStatusReexportsTheLeaf], - ] - let failed = 0 - for (const [name, fn] of tests) { - if (!(await runOne(name, fn))) failed++ - } - if (failed > 0) { - console.error(`\n${failed} test(s) failed`) - process.exit(1) - } - console.log(`\nAll ${tests.length} terminal-status tests passed`) -} +const tests: Array<[string, () => Promise]> = [ + ["terminal statuses are terminal", testTerminalStatusesAreTerminal], + ["live statuses are not terminal", testLiveStatusesAreNotTerminal], + ["blocked stays non-terminal", testBlockedStaysNonTerminal], + ["status re-exports the leaf", testStatusReexportsTheLeaf], +] -main().catch((error) => { - console.error("Test runner crashed:", error) - process.exit(1) -}) +runMain("terminal-status", tests) diff --git a/src/orchestrator/__tests__/watch.test.ts b/src/orchestrator/__tests__/watch.test.ts index cf33ae7..ee265a0 100644 --- a/src/orchestrator/__tests__/watch.test.ts +++ b/src/orchestrator/__tests__/watch.test.ts @@ -32,6 +32,7 @@ import { type OrchestratorGroup, type OrchestratorState, } from "../state.js" +import { runSuites } from "../../test-runner.js" async function tmpRepo(): Promise { return fs.mkdtemp(path.join(os.tmpdir(), "headlesscode-watch-blocked-")) @@ -1045,16 +1046,7 @@ async function main(): Promise { process.env.HEADLESSCODE_DATA_DIR = storeTmp let failed = 0 try { - for (const [name, fn] of tests) { - try { - await fn() - console.log(` ok ${name}`) - } catch (err) { - failed++ - console.error(` FAIL ${name}`) - console.error(err instanceof Error ? err.stack ?? err.message : String(err)) - } - } + failed = await runSuites("watch (blocked-status)", tests) } finally { delete process.env.HEADLESSCODE_DATA_DIR await fs.rm(storeTmp, { recursive: true, force: true }) @@ -1063,7 +1055,6 @@ async function main(): Promise { console.error(`\n${failed} test(s) failed`) process.exit(1) } - console.log(`\nAll ${tests.length} watch (blocked-status) tests passed`) } main().catch((err) => { diff --git a/src/permissions/__tests__/commands.test.ts b/src/permissions/__tests__/commands.test.ts index 1335c74..f7aee57 100644 --- a/src/permissions/__tests__/commands.test.ts +++ b/src/permissions/__tests__/commands.test.ts @@ -31,6 +31,7 @@ import { redirectTargets, resolveRedirectTarget, } from "../commands.js" +import { runMain } from "../../test-runner.js" // ─── (a) dangerous substitutions are always refused, regardless of config ─── @@ -570,26 +571,4 @@ const tests: Array<[string, () => Promise]> = [ ["cd refusal precedes redirect; describes", testCdRefusalPrecedesRedirect], ] -async function main(): Promise { - let failed = 0 - for (const [name, fn] of tests) { - try { - await fn() - console.log(` ok ${name}`) - } catch (err) { - failed++ - console.error(` FAIL ${name}`) - console.error(err instanceof Error ? err.stack ?? err.message : String(err)) - } - } - if (failed > 0) { - console.error(`\n${failed} test(s) failed`) - process.exit(1) - } - console.log(`\nAll ${tests.length} command-permissions tests passed`) -} - -main().catch((err) => { - console.error("Test runner crashed:", err) - process.exit(1) -}) +runMain("command-permissions", tests) diff --git a/src/permissions/__tests__/protected-files.test.ts b/src/permissions/__tests__/protected-files.test.ts index 35be5cf..8d7aecd 100644 --- a/src/permissions/__tests__/protected-files.test.ts +++ b/src/permissions/__tests__/protected-files.test.ts @@ -22,6 +22,7 @@ import { resolvePermissions, type PermissionsOverrides, } from "../config.js" +import { runSuites } from "../../test-runner.js" async function mkTmpWorkspace(prefix: string): Promise { return fs.mkdtemp(path.join(os.tmpdir(), prefix)) @@ -218,16 +219,7 @@ async function main(): Promise { process.env.HEADLESSCODE_DATA_DIR = storeTmp let failed = 0 try { - for (const [name, fn] of tests) { - try { - await fn() - console.log(` ok ${name}`) - } catch (err) { - failed++ - console.error(` FAIL ${name}`) - console.error(err instanceof Error ? err.stack ?? err.message : String(err)) - } - } + failed = await runSuites("protected-file + config", tests) } finally { delete process.env.HEADLESSCODE_DATA_DIR await fs.rm(storeTmp, { recursive: true, force: true }) @@ -236,7 +228,6 @@ async function main(): Promise { console.error(`\n${failed} test(s) failed`) process.exit(1) } - console.log(`\nAll ${tests.length} protected-file + config tests passed`) } main().catch((err) => { diff --git a/src/permissions/__tests__/store-protection.test.ts b/src/permissions/__tests__/store-protection.test.ts index 24ae19b..1454e99 100644 --- a/src/permissions/__tests__/store-protection.test.ts +++ b/src/permissions/__tests__/store-protection.test.ts @@ -39,6 +39,7 @@ import { resolveProjectIdentity, writeProjectMetadata, } from "../../project-store.js" +import { runMain } from "../../test-runner.js" async function mkTmp(prefix: string): Promise { return fsp.mkdtemp(path.join(os.tmpdir(), prefix)) @@ -373,26 +374,4 @@ const tests: Array<[string, () => Promise]> = [ ["word splitting / tilde + env expansion / containment edges", testWordSplittingAndPathResolution], ] -async function main(): Promise { - let failed = 0 - for (const [name, fn] of tests) { - try { - await fn() - console.log(` ok ${name}`) - } catch (err) { - failed++ - console.error(` FAIL ${name}`) - console.error(err instanceof Error ? err.stack ?? err.message : String(err)) - } - } - if (failed > 0) { - console.error(`\n${failed} test(s) failed`) - process.exit(1) - } - console.log(`\nAll ${tests.length} store-protection tests passed`) -} - -main().catch((err) => { - console.error("Test runner crashed:", err) - process.exit(1) -}) +runMain("store-protection", tests) diff --git a/src/project-store.test.ts b/src/project-store.test.ts index d460346..502fcbc 100644 --- a/src/project-store.test.ts +++ b/src/project-store.test.ts @@ -34,6 +34,7 @@ import { resolveProjectIdentity, writeProjectMetadata, } from "./project-store.js" +import { runMain } from "./test-runner.js" async function mkTmp(prefix: string): Promise { return fsp.mkdtemp(path.join(os.tmpdir(), prefix)) @@ -420,26 +421,4 @@ const tests: Array<[string, () => Promise]> = [ ["OpenShell identity override applies only to TARGET_REPO", testOpenShellIdentityOverrideIsWorkspaceScoped], ] -async function main(): Promise { - let failed = 0 - for (const [name, fn] of tests) { - try { - await fn() - console.log(` ok ${name}`) - } catch (err) { - failed++ - console.error(` FAIL ${name}`) - console.error(err instanceof Error ? err.stack ?? err.message : String(err)) - } - } - if (failed > 0) { - console.error(`\n${failed} test(s) failed`) - process.exit(1) - } - console.log(`\nAll ${tests.length} project-store tests passed`) -} - -main().catch((err) => { - console.error("Test runner crashed:", err) - process.exit(1) -}) +runMain("project-store", tests) diff --git a/src/projects/__tests__/cli.test.ts b/src/projects/__tests__/cli.test.ts index 18c8796..9590a60 100644 --- a/src/projects/__tests__/cli.test.ts +++ b/src/projects/__tests__/cli.test.ts @@ -18,6 +18,7 @@ import * as os from "node:os" import * as path from "node:path" import { projectsMain } from "../cli.js" +import { runMain } from "../../test-runner.js" async function mkTmp(prefix: string): Promise { return fsp.mkdtemp(path.join(os.tmpdir(), prefix)) @@ -330,26 +331,4 @@ const tests: Array<[string, () => Promise]> = [ ["projects: --help and argument errors", testHelpAndArgErrors], ] -async function main(): Promise { - let failed = 0 - for (const [name, fn] of tests) { - try { - await fn() - console.log(` ok ${name}`) - } catch (err) { - failed++ - console.error(` FAIL ${name}`) - console.error(err instanceof Error ? err.stack ?? err.message : String(err)) - } - } - if (failed > 0) { - console.error(`\n${failed} test(s) failed`) - process.exit(1) - } - console.log(`\nAll ${tests.length} projects CLI tests passed`) -} - -main().catch((err) => { - console.error("Test runner crashed:", err) - process.exit(1) -}) +runMain("projects CLI", tests) diff --git a/src/qa/__tests__/qa.test.ts b/src/qa/__tests__/qa.test.ts index 4aa01d8..14c1471 100644 --- a/src/qa/__tests__/qa.test.ts +++ b/src/qa/__tests__/qa.test.ts @@ -10,6 +10,7 @@ import * as path from "node:path" import { parseQaResult, runQa, runQaWithRetries } from "../qa.js" import type { ChatMessage, ChatToolCall, LlmClient, LlmRequest, LlmResponse } from "../../engine/types.js" +import { runMain } from "../../test-runner.js" // ─── Fake LLM client (same pattern as engine/__tests__/loop.test.ts) ───────── @@ -349,26 +350,4 @@ const tests: Array<[string, () => Promise]> = [ ["runQaWithRetries: exhausts retries and returns the final error verdict", testRunQaWithRetriesExhaustsRetries], ] -async function main(): Promise { - let failed = 0 - for (const [name, fn] of tests) { - try { - await fn() - console.log(` ok ${name}`) - } catch (err) { - failed++ - console.error(` FAIL ${name}`) - console.error(err instanceof Error ? err.stack ?? err.message : String(err)) - } - } - if (failed > 0) { - console.error(`\n${failed} test(s) failed`) - process.exit(1) - } - console.log(`\nAll ${tests.length} QA parsing tests passed`) -} - -main().catch((err) => { - console.error("Test runner crashed:", err) - process.exit(1) -}) +runMain("QA parsing", tests) diff --git a/src/run-tests-script.test.ts b/src/run-tests-script.test.ts index ecf8250..80c6f60 100644 --- a/src/run-tests-script.test.ts +++ b/src/run-tests-script.test.ts @@ -14,6 +14,7 @@ import * as fs from "node:fs" import * as os from "node:os" import * as path from "node:path" import { fileURLToPath } from "node:url" +import { runMain } from "./test-runner.js" const REPO_ROOT = fileURLToPath(new URL("..", import.meta.url)) const SCRIPT = path.join(REPO_ROOT, "scripts", "run-tests.mjs") @@ -135,26 +136,4 @@ const tests: Array<[string, () => Promise]> = [ ["--help / -h print usage and exit 0 without running the suite (BUG-14)", testHelpFlagPrintsUsageAndExitsWithoutRunningTests], ] -async function main(): Promise { - let failed = 0 - for (const [name, fn] of tests) { - try { - await fn() - console.log(` ok ${name}`) - } catch (err) { - failed++ - console.error(` FAIL ${name}`) - console.error(err instanceof Error ? (err.stack ?? err.message) : String(err)) - } - } - if (failed > 0) { - console.error(`\n${failed} test(s) failed`) - process.exit(1) - } - console.log(`\nAll ${tests.length} run-tests-script tests passed`) -} - -main().catch((err) => { - console.error("Test runner crashed:", err) - process.exit(1) -}) +runMain("run-tests-script", tests) diff --git a/src/stack-rules-content.test.ts b/src/stack-rules-content.test.ts index bef6124..3d66ff4 100644 --- a/src/stack-rules-content.test.ts +++ b/src/stack-rules-content.test.ts @@ -20,6 +20,7 @@ import * as path from "node:path" import { fileURLToPath } from "node:url" import { STACK_NAMES } from "./engine/stacks.js" +import { runMain } from "./test-runner.js" const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..") const SHIPPED_DIR = path.join(repoRoot, "shared", "stacks") @@ -65,23 +66,4 @@ const tests: Array<[string, () => void]> = [ ["javascript + python content shipped", testRoundStacksShipped], ] -function main(): void { - let failed = 0 - for (const [name, fn] of tests) { - try { - fn() - console.log(` ok ${name}`) - } catch (err) { - failed++ - console.error(` FAIL ${name}`) - console.error(err instanceof Error ? err.stack : err) - } - } - if (failed > 0) { - console.error(`\n${failed} test(s) failed`) - process.exit(1) - } - console.log(`\nAll ${tests.length} stack-rules-content tests passed`) -} - -main() +runMain("stack-rules-content", tests) diff --git a/src/test-runner.test.ts b/src/test-runner.test.ts new file mode 100644 index 0000000..615558b --- /dev/null +++ b/src/test-runner.test.ts @@ -0,0 +1,111 @@ +import assert from "node:assert/strict" + +import { runMain, runSuites } from "./test-runner.js" + +function capture(stream: "log" | "error"): { + lines: string[] + restore: () => void +} { + const lines: string[] = [] + const orig = console[stream] + console[stream] = (...args: unknown[]): void => { + lines.push(args.map(String).join(" ")) + } + return { lines, restore: () => void (console[stream] = orig) } +} + +function stubExit(): { codes: number[]; restore: () => void } { + const codes: number[] = [] + const orig = process.exit + process.exit = ((code?: number) => { + codes.push(code ?? 0) + }) as never + return { codes, restore: () => void (process.exit = orig) } +} + +async function tick(): Promise { + await new Promise((resolve) => setImmediate(() => resolve())) +} + +async function boom(): Promise { + throw new Error("boom-detail") +} + +async function passingSuiteLogsOkSummaryAndZero(): Promise { + const out = capture("log") + try { + const failed = await runSuites("demo", [ + ["first", async () => {}], + ["second", () => {}], + ]) + assert.equal(failed, 0) + const want = [" ok first", " ok second", "\nAll 2 demo tests passed"] + assert.deepEqual(out.lines, want) + } finally { + out.restore() + } +} + +async function failingSuiteLogsFailStackAndCount(): Promise { + const errOut = capture("error") + const out = capture("log") + try { + const failed = await runSuites("demo", [["bad", boom]]) + assert.equal(failed, 1) + } finally { + errOut.restore() + out.restore() + } + assert.ok(errOut.lines.some((l) => l.includes("FAIL bad"))) + assert.ok(errOut.lines.some((l) => l.includes("boom-detail"))) + assert.ok(errOut.lines.some((l) => l.includes("1 test(s) failed"))) +} + +async function emptyLabelOmitsLabel(): Promise { + const out = capture("log") + try { + await runSuites("", [["only", async () => {}]]) + assert.deepEqual(out.lines, [" ok only", "\nAll 1 tests passed"]) + } finally { + out.restore() + } +} + +async function mainExitsOneOnFailure(): Promise { + const errOut = capture("error") + const out = capture("log") + const exited = stubExit() + try { + runMain("demo", [["bad", boom]]) + await tick() + } finally { + errOut.restore() + out.restore() + exited.restore() + } + assert.deepEqual(exited.codes, [1]) +} + +async function crashHandlerExitsOne(): Promise { + const errOut = capture("error") + const exited = stubExit() + try { + runMain("demo", null as unknown as []) + await tick() + } finally { + errOut.restore() + exited.restore() + } + assert.deepEqual(exited.codes, [1]) + assert.ok(errOut.lines.some((l) => l.includes("Test runner crashed:"))) +} + +const tests: Array<[string, () => Promise]> = [ + ["passing suite logs ok summary and zero", passingSuiteLogsOkSummaryAndZero], + ["failing suite logs fail stack and count", failingSuiteLogsFailStackAndCount], + ["empty label omits label", emptyLabelOmitsLabel], + ["main exits one on failure", mainExitsOneOnFailure], + ["crash handler exits one", crashHandlerExitsOne], +] + +runMain("test-runner", tests) diff --git a/src/test-runner.ts b/src/test-runner.ts new file mode 100644 index 0000000..3389bbc --- /dev/null +++ b/src/test-runner.ts @@ -0,0 +1,78 @@ +/** + * Shared assert-style test runner (issue #138). Every `*.test.ts` file + * used to inline the same ~20-line main() loop; the copies were flagged + * as clone groups by the architecture gate. New and migrated files end + * with a single line instead: + * + * import { runMain } from "../../test-runner.js" + * runMain("my-module", tests) + * + * Semantics are the established house pattern: per-test try/catch, + * ` ok ` / ` FAIL ` + stack lines, and an + * `All N