mixdog 0.9.172 → 0.9.173
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +3 -3
- package/scripts/code-graph-description-contract.mjs +1 -2
- package/scripts/context-renewal-live-check.mjs +1 -1
- package/scripts/coverage-query.mjs +27 -0
- package/scripts/generate-runtime-manifest.mjs +12 -16
- package/scripts/graph-parity.mjs +108 -75
- package/scripts/lib/coverage.mjs +309 -0
- package/scripts/lib/embedding-model-bench-core.mjs +4 -2
- package/scripts/lib/recall-bench-eval.mjs +4 -4
- package/scripts/lib/run-node-tests.mjs +137 -13
- package/scripts/lib/test-failure-records.mjs +46 -0
- package/scripts/lib/test-failure-summary.mjs +59 -0
- package/scripts/lib/test-summary-reporter.mjs +7 -3
- package/scripts/mouse-probe.mjs +1 -3
- package/scripts/office/build-pptx-icons.mjs +0 -1
- package/scripts/perf/transcript-runtime-probe.mjs +20 -4
- package/scripts/pg-memory-diet.mjs +2 -4
- package/scripts/routing-corpus.mjs +0 -2
- package/scripts/session-diag.mjs +0 -7
- package/scripts/session-sweep.mjs +102 -56
- package/scripts/test.mjs +113 -37
- package/scripts/tui-frame-harness.mjs +1 -1
- package/src/app.mjs +79 -74
- package/src/cli.mjs +0 -2
- package/src/defaults/skills/browser-use/SKILL.md +8 -6
- package/src/defaults/skills/code-tidy/SKILL.md +67 -17
- package/src/defaults/skills/code-tidy/references/agent-cleanup.md +26 -3
- package/src/defaults/skills/computer-use/SKILL.md +12 -5
- package/src/defaults/skills/goal-management/SKILL.md +16 -9
- package/src/defaults/skills/pdf/SKILL.md +2 -1
- package/src/defaults/skills/xlsx/SKILL.md +3 -3
- package/src/headless-exec.mjs +1 -2
- package/src/help.mjs +2 -2
- package/src/lib/rules-builder.cjs +0 -23
- package/src/repl/slash-commands.mjs +1 -5
- package/src/runtime/agent/orchestrator/agent-runtime/agent-dispatch/turn.mjs +4 -4
- package/src/runtime/agent/orchestrator/agent-runtime/cache-strategy.mjs +25 -41
- package/src/runtime/agent/orchestrator/agent-trace-format.mjs +25 -24
- package/src/runtime/agent/orchestrator/agent-trace-io.mjs +15 -22
- package/src/runtime/agent/orchestrator/agent-trace.mjs +0 -22
- package/src/runtime/agent/orchestrator/config-providers-merge.mjs +56 -0
- package/src/runtime/agent/orchestrator/config.mjs +34 -76
- package/src/runtime/agent/orchestrator/context/role-instructions.mjs +1 -1
- package/src/runtime/agent/orchestrator/providers/admission-scheduler.mjs +40 -39
- package/src/runtime/agent/orchestrator/providers/anthropic-api-recovery.mjs +75 -0
- package/src/runtime/agent/orchestrator/providers/anthropic-api-request.mjs +152 -0
- package/src/runtime/agent/orchestrator/providers/anthropic-api-transport.mjs +168 -0
- package/src/runtime/agent/orchestrator/providers/anthropic-messages.mjs +2 -1
- package/src/runtime/agent/orchestrator/providers/anthropic-midstream-recovery.mjs +248 -0
- package/src/runtime/agent/orchestrator/providers/anthropic-oauth-midstream.mjs +16 -206
- package/src/runtime/agent/orchestrator/providers/anthropic-oauth-recovery.mjs +11 -63
- package/src/runtime/agent/orchestrator/providers/anthropic-recovery-guards.mjs +86 -0
- package/src/runtime/agent/orchestrator/providers/anthropic-turn-result.mjs +88 -0
- package/src/runtime/agent/orchestrator/providers/anthropic.mjs +87 -591
- package/src/runtime/agent/orchestrator/providers/antigravity-oauth.mjs +32 -288
- package/src/runtime/agent/orchestrator/providers/antigravity-response.mjs +101 -0
- package/src/runtime/agent/orchestrator/providers/antigravity-stream.mjs +161 -0
- package/src/runtime/agent/orchestrator/providers/antigravity-transport.mjs +114 -0
- package/src/runtime/agent/orchestrator/providers/gemini-cache-policy.mjs +266 -0
- package/src/runtime/agent/orchestrator/providers/gemini-request-body.mjs +54 -0
- package/src/runtime/agent/orchestrator/providers/gemini-response.mjs +145 -0
- package/src/runtime/agent/orchestrator/providers/gemini-schema.mjs +56 -39
- package/src/runtime/agent/orchestrator/providers/gemini-sdk-request.mjs +107 -0
- package/src/runtime/agent/orchestrator/providers/gemini.mjs +23 -533
- package/src/runtime/agent/orchestrator/providers/lib/anthropic-request-utils.mjs +10 -4
- package/src/runtime/agent/orchestrator/providers/media-normalization.mjs +83 -32
- package/src/runtime/agent/orchestrator/providers/openai-compat-responses.mjs +41 -25
- package/src/runtime/agent/orchestrator/providers/openai-http-sse-response-state/response-outcome.mjs +4 -98
- package/src/runtime/agent/orchestrator/providers/openai-http-sse-response-state/response-stamps.mjs +114 -0
- package/src/runtime/agent/orchestrator/providers/openai-oauth-dispatch.mjs +258 -0
- package/src/runtime/agent/orchestrator/providers/openai-oauth.mjs +48 -214
- package/src/runtime/agent/orchestrator/providers/openai-responses-input.mjs +80 -60
- package/src/runtime/agent/orchestrator/providers/openai-ws-handshake.mjs +108 -0
- package/src/runtime/agent/orchestrator/providers/openai-ws-pool.mjs +8 -87
- package/src/runtime/agent/orchestrator/providers/openai-ws-send-outcome.mjs +52 -293
- package/src/runtime/agent/orchestrator/providers/openai-ws-send-trace.mjs +316 -0
- package/src/runtime/agent/orchestrator/providers/opencode-go-usage.mjs +76 -253
- package/src/runtime/agent/orchestrator/session/cache/scoped-cache.mjs +1 -7
- package/src/runtime/agent/orchestrator/session/compact/budget.mjs +17 -16
- package/src/runtime/agent/orchestrator/session/compact/runner.mjs +6 -4
- package/src/runtime/agent/orchestrator/session/compact/summary-schema.mjs +1 -1
- package/src/runtime/agent/orchestrator/session/context-compaction-policy.mjs +9 -13
- package/src/runtime/agent/orchestrator/session/context-utils.mjs +1 -2
- package/src/runtime/agent/orchestrator/session/evidence-union.mjs +6 -9
- package/src/runtime/agent/orchestrator/session/loop/compact-policy.mjs +2 -3
- package/src/runtime/agent/orchestrator/session/loop/diagnostics.mjs +1 -8
- package/src/runtime/agent/orchestrator/session/loop/tool-classify.mjs +1 -3
- package/src/runtime/agent/orchestrator/session/loop/tool-exec/call-prep.mjs +1 -5
- package/src/runtime/agent/orchestrator/session/manager/ask-turn-execute.mjs +0 -1
- package/src/runtime/agent/orchestrator/session/manager/ask-turn-loop-options.mjs +4 -55
- package/src/runtime/agent/orchestrator/session/manager/ask-turn-steering-drain.mjs +63 -0
- package/src/runtime/agent/orchestrator/session/manager/compaction-runner.mjs +1 -1
- package/src/runtime/agent/orchestrator/session/manager/foreign-pending-messages.mjs +80 -65
- package/src/runtime/agent/orchestrator/session/manager/pending-claim-ledger.mjs +68 -0
- package/src/runtime/agent/orchestrator/session/manager/pending-delivered-ledger.mjs +56 -0
- package/src/runtime/agent/orchestrator/session/manager/pending-lifecycle-epoch.mjs +84 -0
- package/src/runtime/agent/orchestrator/session/manager/pending-messages.mjs +51 -651
- package/src/runtime/agent/orchestrator/session/manager/pending-persist-queue.mjs +264 -0
- package/src/runtime/agent/orchestrator/session/manager/pending-spool-file.mjs +131 -0
- package/src/runtime/agent/orchestrator/session/manager/pending-spool-sweep.mjs +73 -0
- package/src/runtime/agent/orchestrator/session/manager/prepared-resume-cache.mjs +122 -0
- package/src/runtime/agent/orchestrator/session/manager/prompt-utils.mjs +2 -1
- package/src/runtime/agent/orchestrator/session/manager/runtime-liveness.mjs +5 -4
- package/src/runtime/agent/orchestrator/session/manager/session-close.mjs +60 -35
- package/src/runtime/agent/orchestrator/session/manager/session-crud.mjs +3 -2
- package/src/runtime/agent/orchestrator/session/manager/session-lifecycle.mjs +22 -646
- package/src/runtime/agent/orchestrator/session/manager/session-owner-liveness.mjs +135 -0
- package/src/runtime/agent/orchestrator/session/manager/session-prompt-composition.mjs +150 -0
- package/src/runtime/agent/orchestrator/session/manager/session-record.mjs +154 -0
- package/src/runtime/agent/orchestrator/session/manager/session-tool-surface.mjs +135 -0
- package/src/runtime/agent/orchestrator/session/manager/tool-resolution.mjs +4 -11
- package/src/runtime/agent/orchestrator/session/manager/turn-checkpoint-journal.mjs +0 -6
- package/src/runtime/agent/orchestrator/session/manager/usage-metrics.mjs +1 -3
- package/src/runtime/agent/orchestrator/session/pre-send-compact.mjs +11 -12
- package/src/runtime/agent/orchestrator/session/provider-prefix-guard.mjs +23 -32
- package/src/runtime/agent/orchestrator/session/store/lifecycle-barriers.mjs +275 -0
- package/src/runtime/agent/orchestrator/session/store/lifecycle-read.mjs +67 -0
- package/src/runtime/agent/orchestrator/session/store/listing.mjs +1 -1
- package/src/runtime/agent/orchestrator/session/store/live-cache-eviction.mjs +72 -0
- package/src/runtime/agent/orchestrator/session/store/liveness-veto.mjs +53 -0
- package/src/runtime/agent/orchestrator/session/store/load-session.mjs +60 -0
- package/src/runtime/agent/orchestrator/session/store/pending-saves.mjs +55 -0
- package/src/runtime/agent/orchestrator/session/store/serialize.mjs +7 -15
- package/src/runtime/agent/orchestrator/session/store/session-delete.mjs +149 -0
- package/src/runtime/agent/orchestrator/session/store/session-observers.mjs +55 -0
- package/src/runtime/agent/orchestrator/session/store/write-admission.mjs +108 -0
- package/src/runtime/agent/orchestrator/session/store-agent-worker-rows.mjs +531 -0
- package/src/runtime/agent/orchestrator/session/store-summary-archived-agent.mjs +126 -0
- package/src/runtime/agent/orchestrator/session/store-summary-fields.mjs +24 -0
- package/src/runtime/agent/orchestrator/session/store-summary-index.mjs +2 -10
- package/src/runtime/agent/orchestrator/session/store-summary-locations.mjs +46 -0
- package/src/runtime/agent/orchestrator/session/store-summary-reader.mjs +11 -791
- package/src/runtime/agent/orchestrator/session/store-transcript-projection.mjs +126 -0
- package/src/runtime/agent/orchestrator/session/store.mjs +33 -771
- package/src/runtime/agent/orchestrator/session/tool-batch/finalize.mjs +1 -1
- package/src/runtime/agent/orchestrator/session/tool-result-offload.mjs +13 -7
- package/src/runtime/agent/orchestrator/stable-hash-stringify.mjs +10 -0
- package/src/runtime/agent/orchestrator/tools/builtin/arg-guard.mjs +50 -75
- package/src/runtime/agent/orchestrator/tools/builtin/builtin-tools.mjs +2 -1
- package/src/runtime/agent/orchestrator/tools/builtin/cache-layers.mjs +22 -21
- package/src/runtime/agent/orchestrator/tools/builtin/external-tool-adapters.mjs +0 -2
- package/src/runtime/agent/orchestrator/tools/builtin/git-command-policy.mjs +44 -27
- package/src/runtime/agent/orchestrator/tools/builtin/git-command-tool.mjs +36 -54
- package/src/runtime/agent/orchestrator/tools/builtin/git-partial-stage.mjs +8 -5
- package/src/runtime/agent/orchestrator/tools/builtin/lib/grep-context-expander.mjs +8 -5
- package/src/runtime/agent/orchestrator/tools/builtin/lib/grep-output.mjs +18 -9
- package/src/runtime/agent/orchestrator/tools/builtin/lib/grep-request.mjs +2 -3
- package/src/runtime/agent/orchestrator/tools/builtin/lib/grep-single-file-rescue.mjs +80 -67
- package/src/runtime/agent/orchestrator/tools/builtin/lib/shell-task-output.mjs +12 -4
- package/src/runtime/agent/orchestrator/tools/builtin/native-search-client.mjs +1 -4
- package/src/runtime/agent/orchestrator/tools/builtin/path-diagnostics.mjs +61 -72
- package/src/runtime/agent/orchestrator/tools/builtin/read-formatting.mjs +0 -5
- package/src/runtime/agent/orchestrator/tools/builtin/read-office-files.mjs +3 -3
- package/src/runtime/agent/orchestrator/tools/builtin/read-range-index.mjs +11 -14
- package/src/runtime/agent/orchestrator/tools/builtin/read-single-render.mjs +3 -6
- package/src/runtime/agent/orchestrator/tools/builtin/read-special-files.mjs +1 -2
- package/src/runtime/agent/orchestrator/tools/builtin/read-streaming.mjs +124 -94
- package/src/runtime/agent/orchestrator/tools/builtin/read-tool/batch-dispatch.mjs +6 -3
- package/src/runtime/agent/orchestrator/tools/builtin/read-tool/reach-preflight.mjs +1 -1
- package/src/runtime/agent/orchestrator/tools/builtin/read-tool.mjs +1 -1
- package/src/runtime/agent/orchestrator/tools/builtin/read-windows.mjs +2 -2
- package/src/runtime/agent/orchestrator/tools/builtin/search-builders.mjs +1 -1
- package/src/runtime/agent/orchestrator/tools/builtin/search-glob-tool.mjs +6 -6
- package/src/runtime/agent/orchestrator/tools/builtin/search-grep-tool.mjs +2 -2
- package/src/runtime/agent/orchestrator/tools/builtin/shell-analysis.mjs +21 -35
- package/src/runtime/agent/orchestrator/tools/builtin/shell-jobs.mjs +0 -3
- package/src/runtime/agent/orchestrator/tools/builtin/shell-lossless-compact.mjs +1 -3
- package/src/runtime/agent/orchestrator/tools/builtin/shell-output.mjs +1 -1
- package/src/runtime/agent/orchestrator/tools/builtin/shell-runtime.mjs +0 -2
- package/src/runtime/agent/orchestrator/tools/builtin/task-tool.mjs +4 -3
- package/src/runtime/agent/orchestrator/tools/builtin/text-stats.mjs +6 -3
- package/src/runtime/agent/orchestrator/tools/builtin.mjs +0 -4
- package/src/runtime/agent/orchestrator/tools/code-graph/aggregate-roots.mjs +7 -7
- package/src/runtime/agent/orchestrator/tools/code-graph/ast-calls.mjs +0 -3
- package/src/runtime/agent/orchestrator/tools/code-graph/build-file-infos.mjs +182 -0
- package/src/runtime/agent/orchestrator/tools/code-graph/build.mjs +14 -159
- package/src/runtime/agent/orchestrator/tools/code-graph/constants.mjs +2 -3
- package/src/runtime/agent/orchestrator/tools/code-graph/dispatch.mjs +21 -8
- package/src/runtime/agent/orchestrator/tools/code-graph/graph-binary.mjs +0 -3
- package/src/runtime/agent/orchestrator/tools/code-graph/search-references.mjs +5 -3
- package/src/runtime/agent/orchestrator/tools/code-graph/search.mjs +2 -3
- package/src/runtime/agent/orchestrator/tools/code-graph/symbol-index.mjs +1 -2
- package/src/runtime/agent/orchestrator/tools/code-graph/text-mask.golden.json +1 -1
- package/src/runtime/agent/orchestrator/tools/code-graph/text-mask.mjs +0 -1
- package/src/runtime/agent/orchestrator/tools/graph-manifest.json +11 -11
- package/src/runtime/agent/orchestrator/tools/lib/shell-run-state.mjs +1 -1
- package/src/runtime/agent/orchestrator/tools/lib/shell-warm-standby.mjs +1 -1
- package/src/runtime/agent/orchestrator/tools/patch/dispatch.mjs +3 -6
- package/src/runtime/agent/orchestrator/tools/patch/mutation-output.mjs +1 -1
- package/src/runtime/agent/orchestrator/tools/patch/native-server.mjs +25 -39
- package/src/runtime/agent/orchestrator/tools/patch/parsing.mjs +9 -14
- package/src/runtime/agent/orchestrator/tools/patch/paths.mjs +11 -6
- package/src/runtime/agent/orchestrator/tools/patch/replay-capture.mjs +5 -6
- package/src/runtime/agent/orchestrator/tools/patch/text-lines.mjs +4 -4
- package/src/runtime/agent/orchestrator/tools/patch/v4a-anchors.mjs +10 -7
- package/src/runtime/agent/orchestrator/tools/patch/v4a-convert.mjs +20 -30
- package/src/runtime/agent/orchestrator/tools/patch/v4a-windows.mjs +4 -19
- package/src/runtime/agent/orchestrator/tools/patch/wave.mjs +13 -2
- package/src/runtime/agent/orchestrator/tools/patch.mjs +0 -19
- package/src/runtime/agent/orchestrator/tools/shell-command.mjs +60 -36
- package/src/runtime/agent/orchestrator/tools/shell-exec-output.mjs +0 -4
- package/src/runtime/agent/orchestrator/tools/shell-policy.mjs +6 -7
- package/src/runtime/attachments/store.mjs +13 -12
- package/src/runtime/browser-bridge/client.mjs +19 -9
- package/src/runtime/channels/index.mjs +1 -1
- package/src/runtime/channels/lib/event-pipeline.mjs +8 -8
- package/src/runtime/channels/lib/event-queue.mjs +15 -20
- package/src/runtime/channels/lib/executor.mjs +10 -7
- package/src/runtime/channels/lib/owner-heartbeat.mjs +0 -4
- package/src/runtime/channels/lib/runtime-paths.mjs +30 -33
- package/src/runtime/channels/lib/scheduler.mjs +11 -20
- package/src/runtime/channels/lib/voice-runtime-fetcher.mjs +0 -4
- package/src/runtime/channels/lib/voice-transcription.mjs +127 -104
- package/src/runtime/channels/lib/webhook/verification.mjs +33 -20
- package/src/runtime/channels/lib/worker-bootstrap.mjs +0 -2
- package/src/runtime/channels/lib/worker-main.mjs +2 -2
- package/src/runtime/computer-bridge/action-schema.mjs +20 -0
- package/src/runtime/computer-bridge/core-actions.mjs +15 -0
- package/src/runtime/github/client.mjs +2 -3
- package/src/runtime/github/commands.mjs +9 -3
- package/src/runtime/github/contract.mjs +7 -0
- package/src/runtime/local-provider/catalog.mjs +4 -3
- package/src/runtime/local-provider/hardware.mjs +7 -20
- package/src/runtime/local-provider/resumable-installations.mjs +12 -19
- package/src/runtime/media/auth.mjs +16 -13
- package/src/runtime/media/catalog.mjs +14 -6
- package/src/runtime/media/lanes.mjs +2 -1
- package/src/runtime/memory/index.mjs +5 -10
- package/src/runtime/memory/lib/core-memory-store.mjs +3 -7
- package/src/runtime/memory/lib/embedding-provider.mjs +9 -19
- package/src/runtime/memory/lib/embedding-worker.mjs +24 -28
- package/src/runtime/memory/lib/http-router/admin-routes.mjs +5 -51
- package/src/runtime/memory/lib/http-router/admin-trace-routes.mjs +65 -0
- package/src/runtime/memory/lib/memory-action-handlers/cycle-actions.mjs +18 -143
- package/src/runtime/memory/lib/memory-action-handlers/cycle-backfill-action.mjs +84 -0
- package/src/runtime/memory/lib/memory-action-handlers/cycle-rebuild-action.mjs +94 -0
- package/src/runtime/memory/lib/memory-action-handlers/manage-actions.mjs +92 -53
- package/src/runtime/memory/lib/memory-categories.mjs +13 -0
- package/src/runtime/memory/lib/memory-chunk-quality.mjs +134 -74
- package/src/runtime/memory/lib/memory-cycle-requests.mjs +64 -70
- package/src/runtime/memory/lib/memory-cycle1.mjs +17 -33
- package/src/runtime/memory/lib/memory-embed.mjs +10 -2
- package/src/runtime/memory/lib/memory-ops-policy.mjs +3 -2
- package/src/runtime/memory/lib/memory-recall-scope-filter.mjs +1 -1
- package/src/runtime/memory/lib/memory.mjs +7 -35
- package/src/runtime/memory/lib/pg/adapter.mjs +3 -4
- package/src/runtime/memory/lib/pg/process.mjs +23 -28
- package/src/runtime/memory/lib/pg/supervisor.mjs +10 -11
- package/src/runtime/memory/lib/query-hybrid-search.mjs +2 -2
- package/src/runtime/memory/lib/query-maintenance-handlers.mjs +112 -89
- package/src/runtime/memory/lib/query-ranking.mjs +8 -11
- package/src/runtime/memory/lib/recall-format.mjs +6 -12
- package/src/runtime/memory/lib/recall-hybrid/plan.mjs +1 -1
- package/src/runtime/memory/lib/runtime-fetcher.mjs +73 -65
- package/src/runtime/memory/lib/trace-store.mjs +10 -19
- package/src/runtime/memory/lib/transcript-ingest.mjs +7 -4
- package/src/runtime/memory/tool-defs.mjs +5 -8
- package/src/runtime/office/authoring/pptx-author-action.mjs +2 -2
- package/src/runtime/office/authoring/pptx-author-session.mjs +6 -9
- package/src/runtime/office/authoring/pptx-brief.mjs +11 -4
- package/src/runtime/office/authoring/pptx-receipt.mjs +79 -60
- package/src/runtime/office/bench/benchmark.mjs +102 -82
- package/src/runtime/office/capabilities-catalog.mjs +2 -0
- package/src/runtime/office/capabilities-signatures.mjs +8 -0
- package/src/runtime/office/capabilities.mjs +3 -4
- package/src/runtime/office/com/com-adapter.mjs +13 -1
- package/src/runtime/office/com/office-com-host.ps1 +63 -1
- package/src/runtime/office/core/office-actions-batch.mjs +12 -12
- package/src/runtime/office/core/office-actions-open.mjs +2 -7
- package/src/runtime/office/core/office-core.mjs +20 -1
- package/src/runtime/office/core/office-finalize/session-save.mjs +9 -6
- package/src/runtime/office/core/office-session-dispatch.mjs +86 -0
- package/src/runtime/office/core/office-sessionless-actions.mjs +115 -0
- package/src/runtime/office/core/office-sessions.mjs +2 -2
- package/src/runtime/office/core/office-transactions.mjs +2 -4
- package/src/runtime/office/core/tabular.mjs +13 -16
- package/src/runtime/office/design/composition-system.mjs +32 -28
- package/src/runtime/office/design/content-model.mjs +2 -1
- package/src/runtime/office/design/design-art-direction.mjs +17 -14
- package/src/runtime/office/design/design-creative-director.mjs +1 -1
- package/src/runtime/office/design/design-discipline.mjs +8 -0
- package/src/runtime/office/design/design-tokens.mjs +9 -5
- package/src/runtime/office/design/docx/design-docx-components.mjs +88 -156
- package/src/runtime/office/design/library/design-library-pack.mjs +11 -23
- package/src/runtime/office/design/library/design-library.mjs +15 -37
- package/src/runtime/office/design/library/design-template-index.mjs +1 -9
- package/src/runtime/office/design/library/design-template-induct.mjs +2 -1
- package/src/runtime/office/design/native-design.mjs +2 -3
- package/src/runtime/office/index.mjs +7 -160
- package/src/runtime/office/pdf/pdf-adapter.mjs +17 -2
- package/src/runtime/office/pdf/pdf-ocr.mjs +2 -1
- package/src/runtime/office/pdf/pdf-render.mjs +12 -13
- package/src/runtime/office/pdf/pdf-writer.mjs +135 -6
- package/src/runtime/office/portable/docx-revisions.mjs +5 -5
- package/src/runtime/office/portable/docx-runs.mjs +4 -7
- package/src/runtime/office/portable/docx-tracked-edits.mjs +14 -9
- package/src/runtime/office/portable/font-provisioner.mjs +2 -2
- package/src/runtime/office/portable/ooxml-validator.mjs +4 -1
- package/src/runtime/office/portable/portable-docx-edits.mjs +61 -45
- package/src/runtime/office/portable/portable-docx-operations.mjs +13 -4
- package/src/runtime/office/portable/portable-docx-parts.mjs +59 -70
- package/src/runtime/office/portable/portable-docx-xml.mjs +3 -5
- package/src/runtime/office/portable/portable-opc.mjs +63 -39
- package/src/runtime/office/portable/portable-package.mjs +5 -23
- package/src/runtime/office/portable/portable-pptx-chart.mjs +7 -6
- package/src/runtime/office/portable/portable-pptx-charts.mjs +27 -20
- package/src/runtime/office/portable/portable-pptx-core.mjs +10 -21
- package/src/runtime/office/portable/portable-pptx-deck.mjs +5 -8
- package/src/runtime/office/portable/portable-pptx-package.mjs +23 -30
- package/src/runtime/office/portable/portable-pptx-shapes.mjs +21 -57
- package/src/runtime/office/portable/portable-sheet-audits.mjs +14 -36
- package/src/runtime/office/portable/portable-sheet-page.mjs +22 -23
- package/src/runtime/office/portable/portable-sheet-parts.mjs +4 -5
- package/src/runtime/office/portable/portable-sheet-styles.mjs +11 -19
- package/src/runtime/office/portable/portable-sheet-xml.mjs +15 -7
- package/src/runtime/office/portable/portable-slide-shapes.mjs +4 -0
- package/src/runtime/office/portable/portable-snapshot-pptx.mjs +2 -2
- package/src/runtime/office/portable/portable-snapshot-shared.mjs +1 -1
- package/src/runtime/office/portable/portable-snapshot-xlsx.mjs +16 -17
- package/src/runtime/office/portable/portable-soffice.mjs +176 -13
- package/src/runtime/office/portable/portable-validation-package.mjs +16 -18
- package/src/runtime/office/portable/portable-validation.mjs +57 -31
- package/src/runtime/office/portable/portable-xlsx-cell-values.mjs +15 -0
- package/src/runtime/office/portable/portable-xlsx-charts.mjs +192 -0
- package/src/runtime/office/portable/portable-xlsx-drawings.mjs +323 -0
- package/src/runtime/office/portable/portable-xlsx-operations.mjs +29 -627
- package/src/runtime/office/portable/portable-xlsx-pivot-ops.mjs +94 -0
- package/src/runtime/office/portable/portable-xlsx-range-layout.mjs +236 -0
- package/src/runtime/office/portable/portable-xlsx-sheet-edits.mjs +19 -14
- package/src/runtime/office/portable/portable-xlsx.mjs +32 -0
- package/src/runtime/office/portable/portable-xml.mjs +9 -0
- package/src/runtime/office/portable/pptx-relations.mjs +11 -0
- package/src/runtime/office/portable/pptx-targets.mjs +2 -1
- package/src/runtime/office/portable/review-editability.mjs +4 -11
- package/src/runtime/office/portable/text-metrics.mjs +35 -55
- package/src/runtime/office/portable/xlsx-assertions.mjs +2 -15
- package/src/runtime/office/portable/xlsx-audit-support.mjs +2 -6
- package/src/runtime/office/portable/xlsx-contract.mjs +4 -0
- package/src/runtime/office/portable/xlsx-formula-audit.mjs +11 -4
- package/src/runtime/office/portable/xlsx-recalculate.mjs +1 -1
- package/src/runtime/office/portable/xlsx-sheet-hygiene.mjs +5 -1
- package/src/runtime/office/quality/assurance-rendered.mjs +3 -2
- package/src/runtime/office/quality/assurance-structure-pptx.mjs +16 -15
- package/src/runtime/office/quality/assurance-trust.mjs +25 -10
- package/src/runtime/office/quality/design-aesthetics.mjs +172 -145
- package/src/runtime/office/quality/design-deck-diversity.mjs +2 -5
- package/src/runtime/office/quality/design-review-authored.mjs +2 -2
- package/src/runtime/office/quality/design-review-critique.mjs +36 -37
- package/src/runtime/office/quality/design-review.mjs +113 -96
- package/src/runtime/office/quality/document-acceptance.mjs +10 -3
- package/src/runtime/office/quality/inline-audit.mjs +7 -2
- package/src/runtime/office/quality/pptx-deck-rubric.mjs +14 -6
- package/src/runtime/office/quality/pptx-slide-roles.mjs +7 -9
- package/src/runtime/office/quality/presentation-acceptance.mjs +2 -6
- package/src/runtime/office/quality/quality-pipeline.mjs +21 -14
- package/src/runtime/office/quality/quality-score.mjs +2 -2
- package/src/runtime/office/quality/render-air.mjs +12 -5
- package/src/runtime/shared/atomic-file.mjs +19 -8
- package/src/runtime/shared/config.mjs +7 -13
- package/src/runtime/shared/err-text.mjs +11 -6
- package/src/runtime/shared/file-lock.mjs +10 -6
- package/src/runtime/shared/inline-file-kind.mjs +51 -0
- package/src/runtime/shared/llm/usage-accounting.mjs +9 -6
- package/src/runtime/shared/llm/usage-ledger-rollup.mjs +272 -0
- package/src/runtime/shared/llm/usage-ledger.mjs +8 -218
- package/src/runtime/shared/pristine-execution.mjs +2 -6
- package/src/runtime/shared/process-lifecycle.mjs +34 -52
- package/src/runtime/shared/process-shutdown.mjs +1 -1
- package/src/runtime/shared/schedule-model-ref.mjs +14 -0
- package/src/runtime/shared/schedule-session-run.mjs +3 -13
- package/src/runtime/shared/schedules-db.mjs +16 -39
- package/src/runtime/shared/staged-child-result.mjs +2 -7
- package/src/runtime/shared/staged-update.mjs +1 -5
- package/src/runtime/shared/tool-card-model/agent-surface.mjs +8 -3
- package/src/runtime/shared/tool-card-model/card-status.mjs +2 -1
- package/src/runtime/shared/tool-card-model/displayed-result.mjs +2 -3
- package/src/runtime/shared/tool-card-model/generic-detail.mjs +5 -3
- package/src/runtime/shared/tool-card-model.golden-cases.mjs +10 -2
- package/src/runtime/shared/tool-card-model.golden.json +3 -12
- package/src/runtime/shared/tool-execution-contract.mjs +22 -26
- package/src/runtime/shared/tool-result-summary.mjs +7 -14
- package/src/runtime/shared/tool-surface.mjs +30 -33
- package/src/runtime/shared/tool-work-units.mjs +12 -8
- package/src/runtime/shared/turn-snapshot-store.mjs +2 -4
- package/src/runtime/shared/turn-worktree-snapshot.mjs +13 -16
- package/src/runtime/shared/update-checker.mjs +5 -0
- package/src/runtime/shared/user-data-guard.mjs +10 -17
- package/src/runtime/shared/webhook-session-run.mjs +3 -15
- package/src/runtime/tidy/history-comment.mjs +32 -27
- package/src/runtime/tidy/process.mjs +18 -21
- package/src/runtime/tidy/report.mjs +50 -9
- package/src/runtime/tidy/resolve.mjs +13 -14
- package/src/runtime/tidy/runners/air.mjs +2 -20
- package/src/runtime/tidy/runners/biome.mjs +4 -6
- package/src/runtime/tidy/runners/dprint.mjs +2 -23
- package/src/runtime/tidy/runners/mago.mjs +2 -20
- package/src/runtime/tidy/runners/ruff.mjs +2 -8
- package/src/runtime/tidy/runners/shared.mjs +35 -9
- package/src/runtime/tidy/runners/stylua.mjs +2 -20
- package/src/runtime/tidy/shadow.mjs +138 -0
- package/src/runtime/tidy/tool.mjs +33 -0
- package/src/runtime/tidy/worktree.mjs +114 -0
- package/src/runtime/web-search/index.mjs +64 -68
- package/src/runtime/web-search/lib/http-fetch.mjs +39 -54
- package/src/runtime/web-search/lib/ssrf-guard.mjs +19 -52
- package/src/runtime/web-search/lib/web-tools.mjs +2 -2
- package/src/session-runtime/config-helpers.mjs +9 -9
- package/src/session-runtime/context-inspection.mjs +64 -23
- package/src/session-runtime/context-status-cache.mjs +0 -2
- package/src/session-runtime/context-status-inspection.mjs +54 -25
- package/src/session-runtime/context-status-shape.mjs +1 -18
- package/src/session-runtime/goal-facade-api.mjs +15 -12
- package/src/session-runtime/goal-mutations.mjs +4 -3
- package/src/session-runtime/goal-runtime.mjs +5 -0
- package/src/session-runtime/goal-tasks.mjs +5 -10
- package/src/session-runtime/goal-text.mjs +46 -9
- package/src/session-runtime/goal-tool-defs.mjs +7 -8
- package/src/session-runtime/goal-tool-exec.mjs +1 -1
- package/src/session-runtime/goal-turns.mjs +54 -3
- package/src/session-runtime/hook-payload.mjs +2 -1
- package/src/session-runtime/inheritance-fit.mjs +3 -3
- package/src/session-runtime/lifecycle/teardown.mjs +9 -1
- package/src/session-runtime/mcp-glue/server-catalog.mjs +4 -6
- package/src/session-runtime/model-capabilities.mjs +1 -3
- package/src/session-runtime/notification-bus.mjs +30 -7
- package/src/session-runtime/plugin-mcp.mjs +0 -2
- package/src/session-runtime/provider-auth/accounts.mjs +2 -2
- package/src/session-runtime/provider-auth/login.mjs +0 -7
- package/src/session-runtime/provider-usage.mjs +1 -1
- package/src/session-runtime/runtime-paths.mjs +1 -3
- package/src/session-runtime/session-hooks.mjs +37 -96
- package/src/session-runtime/statusline-route.mjs +1 -4
- package/src/session-runtime/tool-catalog-schema.mjs +0 -6
- package/src/session-runtime/tool-catalog.mjs +10 -15
- package/src/session-runtime/workflow-ids.mjs +1 -2
- package/src/standalone/agent-task-status.mjs +1 -2
- package/src/standalone/agent-tool/close-flow/close-all.mjs +67 -0
- package/src/standalone/agent-tool/close-flow.mjs +2 -58
- package/src/standalone/agent-tool/execute-send.mjs +91 -0
- package/src/standalone/agent-tool/execute-spawn.mjs +76 -0
- package/src/standalone/agent-tool/execute.mjs +7 -147
- package/src/standalone/agent-tool/helpers.mjs +3 -6
- package/src/standalone/agent-tool/notify/early-completion.mjs +58 -0
- package/src/standalone/agent-tool/notify.mjs +7 -55
- package/src/standalone/agent-tool/provider-init.mjs +73 -64
- package/src/standalone/agent-tool/render.mjs +123 -115
- package/src/standalone/agent-tool/spawn-flow/spawn-plan.mjs +89 -0
- package/src/standalone/agent-tool/spawn-flow/spawn-prep.mjs +16 -75
- package/src/standalone/agent-tool/spawn-flow/spawn-run.mjs +1 -1
- package/src/standalone/agent-tool/worker-rows.mjs +0 -7
- package/src/standalone/channel-admin.mjs +13 -18
- package/src/standalone/channel-transport/remote-binding.mjs +10 -79
- package/src/standalone/channel-transport/remote-intent.mjs +52 -0
- package/src/standalone/channel-transport/remote-notify.mjs +15 -107
- package/src/standalone/channel-transport/remote-pointer.mjs +124 -0
- package/src/standalone/channel-transport/remote-state.mjs +47 -0
- package/src/standalone/daemon-agent-control.mjs +65 -0
- package/src/standalone/daemon-channels-loader.mjs +60 -0
- package/src/standalone/daemon-desktop-runtime.mjs +28 -0
- package/src/standalone/daemon-local-session-bridge.mjs +62 -0
- package/src/standalone/daemon-stored-session-views.mjs +55 -0
- package/src/standalone/daemon.mjs +175 -320
- package/src/standalone/desktop-service-registry.mjs +9 -7
- package/src/standalone/folder-dialog.mjs +11 -12
- package/src/standalone/hook-bus/command-handler.mjs +60 -49
- package/src/standalone/hook-bus/event-runner.mjs +7 -74
- package/src/standalone/hook-bus/handler-dispatch.mjs +75 -0
- package/src/standalone/provider-admin.mjs +14 -35
- package/src/standalone/session-client/attachment-calls.mjs +67 -0
- package/src/standalone/session-client/stream-link.mjs +85 -0
- package/src/standalone/session-client.mjs +31 -106
- package/src/standalone/session-frame-stream.mjs +4 -2
- package/src/standalone/session-protocol.mjs +0 -1
- package/src/standalone/session-runtime-agent-control-client.mjs +11 -27
- package/src/standalone/session-runtime-agent-control.mjs +85 -0
- package/src/standalone/session-runtime-host.mjs +30 -472
- package/src/standalone/session-runtime-inline-host/telemetry.mjs +38 -0
- package/src/standalone/session-runtime-inline-host.mjs +6 -37
- package/src/standalone/session-runtime-proxy.mjs +164 -0
- package/src/standalone/session-runtime-shard-router.mjs +4 -7
- package/src/standalone/session-runtime-spawn-leases.mjs +82 -0
- package/src/standalone/session-runtime-worker.mjs +27 -7
- package/src/standalone/session-service/agent-tree/agent-child.mjs +59 -0
- package/src/standalone/session-service/agent-tree/agent-turns.mjs +4 -55
- package/src/standalone/session-service/agent-tree.mjs +3 -1
- package/src/standalone/session-service/entries/disposal.mjs +72 -0
- package/src/standalone/session-service/entries/external-binding.mjs +6 -3
- package/src/standalone/session-service/entries/lifecycle.mjs +15 -59
- package/src/standalone/session-service/retention/busy.mjs +50 -0
- package/src/standalone/session-service/retention.mjs +5 -44
- package/src/standalone/session-service/session-calls/goal-recovery.mjs +44 -0
- package/src/standalone/session-service/session-calls/session-action.mjs +5 -8
- package/src/standalone/session-service/session-calls/session-turns.mjs +12 -42
- package/src/standalone/session-service/session-calls/session-views.mjs +8 -8
- package/src/standalone/session-transport/stack.mjs +131 -0
- package/src/standalone/session-transport.mjs +10 -103
- package/src/standalone/usage-dashboard.mjs +167 -165
- package/src/standalone/usage-stats-model.mjs +117 -97
- package/src/tui/App.jsx +27 -210
- package/src/tui/app/app-format.mjs +7 -0
- package/src/tui/app/app-view/floating-panel.jsx +229 -0
- package/src/tui/app/app-view/text-entry-labels.mjs +81 -0
- package/src/tui/app/app-view/transcript-viewport/trailing-bands.jsx +94 -0
- package/src/tui/app/app-view/transcript-viewport/transcript-rows.jsx +87 -0
- package/src/tui/app/app-view/transcript-viewport.jsx +74 -0
- package/src/tui/app/app-view.jsx +12 -450
- package/src/tui/app/core-memory-picker/core-rows.mjs +32 -0
- package/src/tui/app/core-memory-picker/entry-prompts.mjs +45 -0
- package/src/tui/app/core-memory-picker/list-panels.mjs +75 -0
- package/src/tui/app/core-memory-picker.mjs +26 -120
- package/src/tui/app/doctor.mjs +17 -12
- package/src/tui/app/extension-pickers/skills-pickers/project-skills.mjs +38 -0
- package/src/tui/app/extension-pickers/skills-pickers/skill-detail.mjs +80 -0
- package/src/tui/app/extension-pickers/skills-pickers/skill-items.mjs +44 -0
- package/src/tui/app/extension-pickers/skills-pickers.mjs +31 -137
- package/src/tui/app/model-picker/route-selection/context-window.mjs +47 -0
- package/src/tui/app/model-picker/route-selection/effort.mjs +87 -0
- package/src/tui/app/model-picker/route-selection/fast-parameters.mjs +84 -0
- package/src/tui/app/model-picker/route-selection/model-key.mjs +5 -0
- package/src/tui/app/model-picker/route-selection.mjs +34 -200
- package/src/tui/app/mouse-input/edge-autoscroll.mjs +53 -0
- package/src/tui/app/mouse-input/geometry.mjs +109 -0
- package/src/tui/app/mouse-input/sgr-buttons.mjs +14 -0
- package/src/tui/app/mouse-input/wheel-router.mjs +96 -0
- package/src/tui/app/panel-signature.mjs +58 -0
- package/src/tui/app/project-picker/project-service.mjs +3 -7
- package/src/tui/app/prompt-submit.mjs +2 -7
- package/src/tui/app/provider-setup/api-key-actions.mjs +7 -80
- package/src/tui/app/provider-setup/oauth-actions.mjs +2 -10
- package/src/tui/app/provider-setup/provider-items.mjs +20 -3
- package/src/tui/app/settings-picker/settings-toggles/cycled.mjs +4 -0
- package/src/tui/app/settings-picker/settings-toggles/listed-cyclers.mjs +77 -0
- package/src/tui/app/settings-picker/settings-toggles/store-toggles.mjs +61 -0
- package/src/tui/app/settings-picker/settings-toggles/theme-cycler.mjs +36 -0
- package/src/tui/app/settings-picker/settings-toggles.mjs +21 -149
- package/src/tui/app/slash-commands.mjs +14 -4
- package/src/tui/app/store-service-call.mjs +17 -0
- package/src/tui/app/text-entry-policy.mjs +1 -3
- package/src/tui/app/transcript-row-estimate.mjs +31 -91
- package/src/tui/app/transcript-window.mjs +19 -0
- package/src/tui/app/use-disabled-skills.mjs +52 -0
- package/src/tui/app/use-global-key-input/copy-first.mjs +43 -0
- package/src/tui/app/use-global-key-input/shift-arrow.mjs +30 -0
- package/src/tui/app/use-global-key-input.mjs +10 -67
- package/src/tui/app/use-model-catalog-cache.mjs +86 -0
- package/src/tui/app/use-mouse-input.mjs +64 -218
- package/src/tui/app/use-pasted-buffers.mjs +55 -81
- package/src/tui/app/use-prompt-draft-flow/draft-change.mjs +66 -0
- package/src/tui/app/use-prompt-draft-flow/prompt-cancels.mjs +40 -0
- package/src/tui/app/use-prompt-draft-flow.mjs +38 -77
- package/src/tui/app/use-prompt-queue-history/prompt-history.mjs +22 -0
- package/src/tui/app/use-prompt-queue-history/restore-to-prompt.mjs +147 -0
- package/src/tui/app/use-prompt-queue-history.mjs +20 -117
- package/src/tui/app/use-selection-focus.mjs +2 -1
- package/src/tui/app/use-selection-geometry.mjs +9 -12
- package/src/tui/app/use-selection-paint/paint-queue.mjs +90 -0
- package/src/tui/app/use-selection-paint/selection-clip.mjs +33 -0
- package/src/tui/app/use-selection-paint.mjs +31 -102
- package/src/tui/app/use-transcript-activity.mjs +1 -1
- package/src/tui/app/use-transcript-window/instance-state.mjs +1 -1
- package/src/tui/app/use-welcome-prompt-hint.mjs +1 -1
- package/src/tui/app/workflow-cycle.mjs +63 -0
- package/src/tui/components/AnsiText.jsx +47 -15
- package/src/tui/components/ItemRightHintOverprint.jsx +2 -15
- package/src/tui/components/Picker.jsx +9 -23
- package/src/tui/components/PromptInput.jsx +89 -335
- package/src/tui/components/QueuedCommands.jsx +1 -3
- package/src/tui/components/Spinner.jsx +1 -2
- package/src/tui/components/StatusLine.jsx +11 -21
- package/src/tui/components/TextEntryPanel.jsx +5 -52
- package/src/tui/components/ToolExecution.jsx +5 -14
- package/src/tui/components/TranscriptItem.jsx +0 -2
- package/src/tui/components/TurnDone.jsx +2 -14
- package/src/tui/components/prompt-input/cursor-anchor.mjs +65 -0
- package/src/tui/components/prompt-input/edit-helpers.mjs +1 -2
- package/src/tui/components/prompt-input/key-signals.mjs +122 -0
- package/src/tui/components/prompt-input/mouse-selection.mjs +114 -0
- package/src/tui/components/prompt-input/restore-policy.d.mts +0 -1
- package/src/tui/components/prompt-input/restore-policy.mjs +3 -8
- package/src/tui/components/prompt-input/selected-text.jsx +25 -0
- package/src/tui/components/prompt-input/undo-stack.mjs +75 -0
- package/src/tui/components/tool-output-format.mjs +1 -2
- package/src/tui/dist/index.mjs +2841 -2469
- package/src/tui/index.jsx +2 -2
- package/src/tui/markdown/measure-rendered-rows.mjs +8 -33
- package/src/tui/markdown/render-ansi.mjs +5 -5
- package/src/tui/markdown/table-layout/box-lines.mjs +46 -0
- package/src/tui/markdown/table-layout/cells.mjs +23 -0
- package/src/tui/markdown/table-layout/column-widths.mjs +65 -0
- package/src/tui/markdown/table-layout/vertical-lines.mjs +46 -0
- package/src/tui/markdown/table-layout.mjs +21 -151
- package/src/tui/prompt-history-store.mjs +0 -9
- package/src/tui/session/active-tool-summary.mjs +19 -27
- package/src/tui/session/agent-envelope.mjs +9 -9
- package/src/tui/session/live-share/owner.mjs +9 -7
- package/src/tui/session/live-share/retry.mjs +15 -0
- package/src/tui/session/live-share/viewer/frame-router.mjs +32 -0
- package/src/tui/session/live-share/viewer/pipe-client.mjs +108 -0
- package/src/tui/session/live-share/viewer.mjs +23 -114
- package/src/tui/session/notice-surface.mjs +1 -1
- package/src/tui/session/notice-text.mjs +21 -17
- package/src/tui/session/queue-helpers.mjs +12 -14
- package/src/tui/session/session-api/intake/submission.mjs +3 -8
- package/src/tui/session/session-api/integrations.mjs +0 -11
- package/src/tui/session/session-api/lifecycle/session-reset.mjs +76 -74
- package/src/tui/session/session-flow/drain.mjs +18 -5
- package/src/tui/session/session-flow/queue/entry-shape.mjs +53 -0
- package/src/tui/session/session-flow/queue/take-entries.mjs +82 -0
- package/src/tui/session/session-flow/queue.mjs +10 -112
- package/src/tui/session/session-flow/session-reset.mjs +2 -7
- package/src/tui/session/tool-approval.mjs +13 -17
- package/src/tui/session/tool-result-text.mjs +16 -28
- package/src/tui/session/transcript-spill.mjs +3 -7
- package/src/tui/session/transcript-store/replace-items.mjs +66 -0
- package/src/tui/session/transcript-store/streaming-tail.mjs +49 -0
- package/src/tui/session/transcript-store.mjs +16 -95
- package/src/tui/statusline-ansi-bridge.mjs +1 -40
- package/src/ui/markdown.mjs +3 -2
- package/src/ui/statusline-agents.mjs +9 -14
- package/src/ui/statusline.mjs +4 -4
- package/src/runtime/channels/lib/cli-worker-host.mjs +0 -2
- package/src/runtime/memory/lib/llm-worker-host.mjs +0 -11
- package/src/standalone/opencode-go-login.mjs +0 -140
|
@@ -0,0 +1,309 @@
|
|
|
1
|
+
// V8 coverage, folded into the one artifact that answers a single question:
|
|
2
|
+
// does the suite actually execute this function? Grepping test files for a
|
|
3
|
+
// symbol answers it wrongly whenever a helper is reached only through a public
|
|
4
|
+
// entry point, so the evidence comes from Node itself — NODE_V8_COVERAGE makes
|
|
5
|
+
// every test process dump raw V8 coverage JSON at exit, and this module folds
|
|
6
|
+
// those dumps (one per batch, one per test child) into per-function lines.
|
|
7
|
+
//
|
|
8
|
+
// No dependency is involved: V8 emits the raw data, `node:fs` reads it back.
|
|
9
|
+
import { createHash } from 'node:crypto';
|
|
10
|
+
import { mkdir, readFile, readdir, stat, writeFile } from 'node:fs/promises';
|
|
11
|
+
import { dirname, isAbsolute, join, relative, resolve } from 'node:path';
|
|
12
|
+
import { fileURLToPath } from 'node:url';
|
|
13
|
+
|
|
14
|
+
export const ARTIFACT_PATH = '.runtime/coverage/coverage.json';
|
|
15
|
+
|
|
16
|
+
// The runner's own lane patterns: *.test.mjs and *-test.mjs (plus the jsx/ts
|
|
17
|
+
// spellings used by apps/desktop). A test executing itself proves nothing.
|
|
18
|
+
const TEST_FILE = /[.-]test\.[cm]?[jt]sx?$/;
|
|
19
|
+
// tsx transpiles TypeScript before V8 sees it, so the offsets V8 reports for a
|
|
20
|
+
// .ts file describe the transpiled output instead of the file on disk.
|
|
21
|
+
const TYPESCRIPT_FILE = /\.[cm]?tsx?$/;
|
|
22
|
+
|
|
23
|
+
export const EXCLUSION_REASONS = {
|
|
24
|
+
'transpiled-by-tsx':
|
|
25
|
+
'TypeScript executed through tsx: V8 offsets describe the transpiled output, not this file, and this round does not translate them through source maps',
|
|
26
|
+
'offsets-beyond-source':
|
|
27
|
+
'recorded offsets run past the file on disk, so the executed source was generated or transformed rather than read from here',
|
|
28
|
+
'source-unreadable': 'the file could not be read back while folding coverage',
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
// The identity a later query uses to tell whether the file still is what was
|
|
32
|
+
// measured. Content, not mtime or size: a checkout, a formatter rewriting a
|
|
33
|
+
// file byte-identically or a restored backup all move mtime without changing a
|
|
34
|
+
// single line, and an edit that swaps two lines keeps the size — either way the
|
|
35
|
+
// recorded line ranges would be wrong or wrongly distrusted. Folding already
|
|
36
|
+
// reads every source in full to translate offsets, so hashing it costs nothing
|
|
37
|
+
// and answers exactly. (A file absent from the artifact has no recorded
|
|
38
|
+
// content to compare, so its mtime against the collection time is the only
|
|
39
|
+
// evidence there is; `queryCoverage` uses that for the unknown-file case.)
|
|
40
|
+
export function sourceDigest(source) {
|
|
41
|
+
return createHash('sha256').update(source).digest('hex');
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// Sources are normalized the same way at folding and at query time, so the
|
|
45
|
+
// digest compares like with like.
|
|
46
|
+
async function currentSource(rootPath, path) {
|
|
47
|
+
try {
|
|
48
|
+
return (await readFile(join(rootPath, path), 'utf8')).replace(/^\uFEFF/, '');
|
|
49
|
+
} catch {
|
|
50
|
+
return null;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
async function modifiedAfter(rootPath, path, generatedAt) {
|
|
55
|
+
const collected = Date.parse(generatedAt ?? '');
|
|
56
|
+
if (Number.isNaN(collected)) return false;
|
|
57
|
+
try {
|
|
58
|
+
return (await stat(join(rootPath, path))).mtimeMs > collected;
|
|
59
|
+
} catch {
|
|
60
|
+
return false;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function lineStarts(source) {
|
|
65
|
+
const starts = [0];
|
|
66
|
+
for (let index = 0; index < source.length; index++) {
|
|
67
|
+
if (source.charCodeAt(index) === 10) starts.push(index + 1);
|
|
68
|
+
}
|
|
69
|
+
return starts;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// 1-based line holding a 0-based character offset.
|
|
73
|
+
function lineOf(starts, offset) {
|
|
74
|
+
let low = 0;
|
|
75
|
+
let high = starts.length - 1;
|
|
76
|
+
while (low < high) {
|
|
77
|
+
const mid = (low + high + 1) >> 1;
|
|
78
|
+
if (starts[mid] <= offset) low = mid;
|
|
79
|
+
else high = mid - 1;
|
|
80
|
+
}
|
|
81
|
+
return low + 1;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// V8 keeps the script's own top-level range as a nameless function spanning the
|
|
85
|
+
// whole source. It is the module body, not a function anyone can call, so it
|
|
86
|
+
// becomes the file's `loaded` flag instead of an entry every line sits inside.
|
|
87
|
+
const isModuleBody = (entry, length) => entry.name === '' && entry.startOffset === 0 && entry.endOffset >= length;
|
|
88
|
+
|
|
89
|
+
function repoRelative(url, rootPath) {
|
|
90
|
+
if (!url.startsWith('file:')) return '';
|
|
91
|
+
const parsed = new URL(url);
|
|
92
|
+
parsed.search = '';
|
|
93
|
+
parsed.hash = '';
|
|
94
|
+
const absolute = fileURLToPath(parsed);
|
|
95
|
+
const path = relative(rootPath, absolute).replaceAll('\\', '/');
|
|
96
|
+
if (path === '' || path.startsWith('../') || isAbsolute(path)) return '';
|
|
97
|
+
return path;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// Raw V8 dumps -> { path -> [{ name, startOffset, endOffset, executed }] }.
|
|
101
|
+
// Every dump contributes: a function executed in any batch is executed.
|
|
102
|
+
function collectFunctions(reports, rootPath, excluded) {
|
|
103
|
+
const byFile = new Map();
|
|
104
|
+
for (const report of reports) {
|
|
105
|
+
for (const script of report?.result ?? []) {
|
|
106
|
+
const path = repoRelative(script.url ?? '', rootPath);
|
|
107
|
+
if (path === '' || path.split('/').includes('node_modules') || TEST_FILE.test(path)) continue;
|
|
108
|
+
if (TYPESCRIPT_FILE.test(path)) {
|
|
109
|
+
excluded.set(path, 'transpiled-by-tsx');
|
|
110
|
+
continue;
|
|
111
|
+
}
|
|
112
|
+
const functions = byFile.get(path) ?? new Map();
|
|
113
|
+
byFile.set(path, functions);
|
|
114
|
+
for (const entry of script.functions ?? []) {
|
|
115
|
+
const range = entry.ranges?.[0];
|
|
116
|
+
if (!range) continue;
|
|
117
|
+
const key = `${entry.functionName}\u0000${range.startOffset}\u0000${range.endOffset}`;
|
|
118
|
+
const merged = functions.get(key);
|
|
119
|
+
if (merged) merged.executed ||= range.count > 0;
|
|
120
|
+
else
|
|
121
|
+
functions.set(key, {
|
|
122
|
+
name: entry.functionName,
|
|
123
|
+
startOffset: range.startOffset,
|
|
124
|
+
endOffset: range.endOffset,
|
|
125
|
+
executed: range.count > 0,
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
return byFile;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// The artifact: per repository source file, every function V8 covered, with
|
|
134
|
+
// offsets translated to 1-based lines and the single bit that matters.
|
|
135
|
+
export async function foldCoverage(reports, { root = process.cwd() } = {}) {
|
|
136
|
+
const rootPath = resolve(root);
|
|
137
|
+
const excluded = new Map();
|
|
138
|
+
const collected = collectFunctions(reports, rootPath, excluded);
|
|
139
|
+
const files = {};
|
|
140
|
+
for (const path of [...collected.keys()].sort()) {
|
|
141
|
+
const entries = [...collected.get(path).values()].sort(
|
|
142
|
+
(left, right) => left.startOffset - right.startOffset || left.endOffset - right.endOffset
|
|
143
|
+
);
|
|
144
|
+
let source;
|
|
145
|
+
try {
|
|
146
|
+
source = (await readFile(join(rootPath, path), 'utf8')).replace(/^\uFEFF/, '');
|
|
147
|
+
} catch {
|
|
148
|
+
excluded.set(path, 'source-unreadable');
|
|
149
|
+
continue;
|
|
150
|
+
}
|
|
151
|
+
if (entries.some((entry) => entry.endOffset > source.length)) {
|
|
152
|
+
excluded.set(path, 'offsets-beyond-source');
|
|
153
|
+
continue;
|
|
154
|
+
}
|
|
155
|
+
const starts = lineStarts(source);
|
|
156
|
+
const body = entries.find((entry) => isModuleBody(entry, source.length));
|
|
157
|
+
files[path] = {
|
|
158
|
+
loaded: body ? body.executed : entries.some((entry) => entry.executed),
|
|
159
|
+
sha256: sourceDigest(source),
|
|
160
|
+
functions: entries
|
|
161
|
+
.filter((entry) => entry !== body)
|
|
162
|
+
.map((entry) => ({
|
|
163
|
+
name: entry.name || '(anonymous)',
|
|
164
|
+
startLine: lineOf(starts, entry.startOffset),
|
|
165
|
+
endLine: lineOf(starts, Math.max(entry.startOffset, entry.endOffset - 1)),
|
|
166
|
+
executed: entry.executed,
|
|
167
|
+
})),
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
const covered = Object.values(files);
|
|
171
|
+
return {
|
|
172
|
+
version: 2,
|
|
173
|
+
generatedAt: new Date().toISOString(),
|
|
174
|
+
root: rootPath.replaceAll('\\', '/'),
|
|
175
|
+
summary: {
|
|
176
|
+
filesWithExecutedFunctions: covered.filter((file) => file.functions.some((fn) => fn.executed)).length,
|
|
177
|
+
filesWithoutExecutedFunctions: covered.filter((file) => !file.functions.some((fn) => fn.executed)).length,
|
|
178
|
+
excludedFiles: excluded.size,
|
|
179
|
+
},
|
|
180
|
+
files,
|
|
181
|
+
exclusionReasons: EXCLUSION_REASONS,
|
|
182
|
+
excluded: [...excluded.keys()].sort().map((path) => ({ path, reason: excluded.get(path) })),
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
export async function readRawCoverage(directory) {
|
|
187
|
+
const names = (await readdir(directory)).filter((name) => name.endsWith('.json')).sort();
|
|
188
|
+
const reports = [];
|
|
189
|
+
for (const name of names) reports.push(JSON.parse(await readFile(join(directory, name), 'utf8')));
|
|
190
|
+
return reports;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
export async function writeCoverageArtifact(artifact, { root = process.cwd(), artifactPath = ARTIFACT_PATH } = {}) {
|
|
194
|
+
const target = resolve(root, artifactPath);
|
|
195
|
+
await mkdir(dirname(target), { recursive: true });
|
|
196
|
+
await writeFile(target, `${JSON.stringify(artifact, null, 2)}\n`);
|
|
197
|
+
return target;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
export async function loadCoverageArtifact({ root = process.cwd(), artifactPath = ARTIFACT_PATH } = {}) {
|
|
201
|
+
return JSON.parse(await readFile(resolve(root, artifactPath), 'utf8'));
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// Is this line inside a function the suite executed? The innermost enclosing
|
|
205
|
+
// function answers; a line the file's functions do not cover is reported as
|
|
206
|
+
// such rather than borrowed from the module body around it.
|
|
207
|
+
//
|
|
208
|
+
// A recorded answer is only worth giving while the file still is the file that
|
|
209
|
+
// was measured, so every query re-reads the source and compares its digest
|
|
210
|
+
// first: a changed file answers `stale`, never EXECUTED or NOT EXECUTED. An
|
|
211
|
+
// artifact predating this check has no digest to match, which is itself a
|
|
212
|
+
// mismatch, so it announces itself too.
|
|
213
|
+
export async function queryCoverage(artifact, file, line, { root = process.cwd() } = {}) {
|
|
214
|
+
const rootPath = resolve(root);
|
|
215
|
+
const path = String(file).replaceAll('\\', '/').replace(/^\.\//, '');
|
|
216
|
+
const generatedAt = artifact.generatedAt;
|
|
217
|
+
const entry = artifact.files?.[path];
|
|
218
|
+
const excluded = artifact.excluded?.find((item) => item.path === path);
|
|
219
|
+
if (!entry) {
|
|
220
|
+
if (excluded) return { path, line, status: 'excluded', reason: excluded.reason, generatedAt };
|
|
221
|
+
// Nothing was recorded for this file, so there is no content to compare
|
|
222
|
+
// against; a file touched after the collection is the case where a test
|
|
223
|
+
// was added since, which is exactly how this answer misled a reader.
|
|
224
|
+
const changed = await modifiedAfter(rootPath, path, generatedAt);
|
|
225
|
+
return { path, line, status: 'unknown-file', generatedAt, changedSinceCollection: changed };
|
|
226
|
+
}
|
|
227
|
+
const source = await currentSource(rootPath, path);
|
|
228
|
+
if (source === null) return { path, line, status: 'stale', change: 'missing', generatedAt };
|
|
229
|
+
if (sourceDigest(source) !== entry.sha256) return { path, line, status: 'stale', change: 'modified', generatedAt };
|
|
230
|
+
const inside = entry.functions.filter((fn) => line >= fn.startLine && line <= fn.endLine);
|
|
231
|
+
const innermost = inside.reduce(
|
|
232
|
+
(best, fn) => (best === null || fn.endLine - fn.startLine <= best.endLine - best.startLine ? fn : best),
|
|
233
|
+
null
|
|
234
|
+
);
|
|
235
|
+
if (!innermost) return { path, line, status: 'outside-functions', loaded: entry.loaded, generatedAt };
|
|
236
|
+
return {
|
|
237
|
+
path,
|
|
238
|
+
line,
|
|
239
|
+
status: innermost.executed ? 'executed' : 'not-executed',
|
|
240
|
+
loaded: entry.loaded,
|
|
241
|
+
generatedAt,
|
|
242
|
+
function: innermost,
|
|
243
|
+
};
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
// Every answer a reader must not act on says what to do instead.
|
|
247
|
+
const RECOLLECT = 're-run `node scripts/test.mjs --coverage`';
|
|
248
|
+
|
|
249
|
+
export function formatCoverageAnswer(result) {
|
|
250
|
+
const at = `${result.path}:${result.line}`;
|
|
251
|
+
const collected = result.generatedAt ? `collected ${result.generatedAt}` : 'collected at an unrecorded time';
|
|
252
|
+
if (result.status === 'stale')
|
|
253
|
+
return result.change === 'missing'
|
|
254
|
+
? `STALE ${at} — the file is gone since coverage was ${collected}, so nothing here can be answered: ${RECOLLECT}`
|
|
255
|
+
: `STALE ${at} — the file changed since coverage was ${collected}, so its recorded lines no longer describe it: ${RECOLLECT}`;
|
|
256
|
+
if (result.status === 'excluded')
|
|
257
|
+
return `EXCLUDED ${at} — not in the artifact: ${EXCLUSION_REASONS[result.reason] ?? result.reason}`;
|
|
258
|
+
if (result.status === 'unknown-file')
|
|
259
|
+
return result.changedSinceCollection
|
|
260
|
+
? `STALE ${at} — no coverage was recorded for this file and it changed after coverage was ${collected}, so a test may cover it now: ${RECOLLECT}`
|
|
261
|
+
: `NO COVERAGE ${at} — the run ${collected} recorded no coverage for this file; if a test was added since, ${RECOLLECT}`;
|
|
262
|
+
if (result.status === 'outside-functions')
|
|
263
|
+
return `NO FUNCTION ${at} — outside every covered function (module body ${result.loaded ? 'ran' : 'never ran'})`;
|
|
264
|
+
const where = `${result.function.name} (lines ${result.function.startLine}-${result.function.endLine})`;
|
|
265
|
+
return result.status === 'executed'
|
|
266
|
+
? `EXECUTED ${at} — inside ${where}, which the suite ran`
|
|
267
|
+
: `NOT EXECUTED ${at} — inside ${where}, which no test ran`;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
// The whole-artifact signal: how much of what was measured no longer exists as
|
|
271
|
+
// measured, and when it was measured, so a caller can decide to re-collect
|
|
272
|
+
// before asking anything at all.
|
|
273
|
+
export async function coverageStaleness(artifact, { root = process.cwd() } = {}) {
|
|
274
|
+
const rootPath = resolve(root);
|
|
275
|
+
const changed = [];
|
|
276
|
+
const missing = [];
|
|
277
|
+
for (const [path, entry] of Object.entries(artifact.files ?? {})) {
|
|
278
|
+
const source = await currentSource(rootPath, path);
|
|
279
|
+
if (source === null) missing.push(path);
|
|
280
|
+
else if (sourceDigest(source) !== entry.sha256) changed.push(path);
|
|
281
|
+
}
|
|
282
|
+
return {
|
|
283
|
+
generatedAt: artifact.generatedAt,
|
|
284
|
+
files: Object.keys(artifact.files ?? {}).length,
|
|
285
|
+
changed: changed.sort(),
|
|
286
|
+
missing: missing.sort(),
|
|
287
|
+
changedCount: changed.length + missing.length,
|
|
288
|
+
stale: changed.length + missing.length > 0,
|
|
289
|
+
};
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
export function formatStaleness(state) {
|
|
293
|
+
const collected = state.generatedAt ? `collected ${state.generatedAt}` : 'collected at an unrecorded time';
|
|
294
|
+
return state.stale
|
|
295
|
+
? `STALE coverage ${collected}: ${state.changedCount} of ${state.files} files changed since (${state.missing.length} gone); ${RECOLLECT}`
|
|
296
|
+
: `FRESH coverage ${collected}: all ${state.files} files unchanged since`;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
// Called once per `--coverage` run, after the last batch has exited.
|
|
300
|
+
export async function reportCoverage(rawDirectory, { root = process.cwd(), log = console.error } = {}) {
|
|
301
|
+
const artifact = await foldCoverage(await readRawCoverage(rawDirectory), { root });
|
|
302
|
+
const target = await writeCoverageArtifact(artifact, { root });
|
|
303
|
+
const { filesWithExecutedFunctions, filesWithoutExecutedFunctions, excludedFiles } = artifact.summary;
|
|
304
|
+
log(
|
|
305
|
+
`Coverage: ${filesWithExecutedFunctions} files with executed functions, ${filesWithoutExecutedFunctions} without, ${excludedFiles} excluded`
|
|
306
|
+
);
|
|
307
|
+
log(`Coverage artifact: ${target}`);
|
|
308
|
+
return target;
|
|
309
|
+
}
|
|
@@ -97,6 +97,8 @@ export function buildEvaluation(caseFile, kase) {
|
|
|
97
97
|
: [];
|
|
98
98
|
if (typeof query !== 'string' || !query.trim() || targets.length === 0) return null;
|
|
99
99
|
const temporal = parsePeriod(String(kase?.args?.period || ''), true);
|
|
100
|
+
const startMs = Number(temporal?.startMs);
|
|
101
|
+
const endMs = Number(temporal?.endMs);
|
|
100
102
|
return {
|
|
101
103
|
id: String(kase.id),
|
|
102
104
|
caseFile,
|
|
@@ -114,8 +116,8 @@ export function buildEvaluation(caseFile, kase) {
|
|
|
114
116
|
.toLowerCase()
|
|
115
117
|
)
|
|
116
118
|
.filter(Boolean),
|
|
117
|
-
startMs: Number.isFinite(
|
|
118
|
-
endMs: Number.isFinite(
|
|
119
|
+
startMs: Number.isFinite(startMs) ? startMs : null,
|
|
120
|
+
endMs: Number.isFinite(endMs) ? endMs : null,
|
|
119
121
|
},
|
|
120
122
|
};
|
|
121
123
|
}
|
|
@@ -184,12 +184,12 @@ export function scoreWithinPeriod(items, temporal, toleranceMs = 60_000) {
|
|
|
184
184
|
const endMs = Number(temporal?.endMs);
|
|
185
185
|
const hasStart = Number.isFinite(startMs);
|
|
186
186
|
const hasEnd = Number.isFinite(endMs);
|
|
187
|
-
const
|
|
187
|
+
const timestamped = items.filter((item) => item.timestampMs !== null);
|
|
188
|
+
const offenders = timestamped.filter(
|
|
188
189
|
(item) =>
|
|
189
|
-
item.timestampMs
|
|
190
|
-
((hasStart && item.timestampMs < startMs - toleranceMs) || (hasEnd && item.timestampMs > endMs + toleranceMs))
|
|
190
|
+
(hasStart && item.timestampMs < startMs - toleranceMs) || (hasEnd && item.timestampMs > endMs + toleranceMs)
|
|
191
191
|
);
|
|
192
|
-
return { checked:
|
|
192
|
+
return { checked: timestamped.length, offenders, ok: offenders.length === 0 };
|
|
193
193
|
}
|
|
194
194
|
|
|
195
195
|
export function evaluateCase(kase, outcome, quality, recency, allContain, withinPeriod, pageOrder) {
|
|
@@ -1,28 +1,152 @@
|
|
|
1
1
|
import { spawn } from 'node:child_process';
|
|
2
|
-
import { mkdtemp } from 'node:fs/promises';
|
|
2
|
+
import { appendFile, mkdtemp, readFile, rm } from 'node:fs/promises';
|
|
3
3
|
import { tmpdir } from 'node:os';
|
|
4
4
|
import { join } from 'node:path';
|
|
5
|
+
import { FAILURE_RECORDS_ENV } from './test-failure-records.mjs';
|
|
6
|
+
import { classifyRerunOutcomes, formatFailureSummary } from './test-failure-summary.mjs';
|
|
5
7
|
|
|
6
8
|
const SUMMARY_REPORTER = new URL('./test-summary-reporter.mjs', import.meta.url).href;
|
|
7
9
|
|
|
10
|
+
// A whole-suite run passes ~700 paths to one node process. Windows caps a
|
|
11
|
+
// command line at 32767 characters (CreateProcess) and POSIX caps argv+env at
|
|
12
|
+
// ARG_MAX, so the file list is spawned in batches that stay under the cap
|
|
13
|
+
// instead of dying with spawn ENAMETOOLONG before any test runs.
|
|
14
|
+
export const ARG_BUDGET = process.platform === 'win32' ? 30_000 : 1_000_000;
|
|
15
|
+
|
|
16
|
+
// An argument costs its own characters plus the separator and its quotes.
|
|
17
|
+
const argCost = (arg) => arg.length + 3;
|
|
18
|
+
|
|
19
|
+
// Batches keep the file order Node was given; a single path larger than the
|
|
20
|
+
// budget still gets its own batch rather than being dropped. An empty list
|
|
21
|
+
// stays one batch: Node's own discovery is what an argument-less run means.
|
|
22
|
+
export function chunkFileArgs(fileArgs, budget) {
|
|
23
|
+
const chunks = [[]];
|
|
24
|
+
let cost = 0;
|
|
25
|
+
for (const file of fileArgs) {
|
|
26
|
+
const current = chunks.at(-1);
|
|
27
|
+
if (current.length > 0 && cost + argCost(file) > budget) {
|
|
28
|
+
chunks.push([file]);
|
|
29
|
+
cost = argCost(file);
|
|
30
|
+
continue;
|
|
31
|
+
}
|
|
32
|
+
current.push(file);
|
|
33
|
+
cost += argCost(file);
|
|
34
|
+
}
|
|
35
|
+
return chunks;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function spawnBatch(args, env) {
|
|
39
|
+
return new Promise((resolve) => {
|
|
40
|
+
const child = spawn(process.execPath, args, { stdio: 'inherit', env });
|
|
41
|
+
child.on('error', (error) => {
|
|
42
|
+
console.error(error);
|
|
43
|
+
resolve(1);
|
|
44
|
+
});
|
|
45
|
+
child.on('exit', (code, signal) => resolve(code ?? (signal ? 1 : 0)));
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// The failure reporter writes one JSON line per failed test; a spawn that
|
|
50
|
+
// died before Node opened its destination leaves no file at all.
|
|
51
|
+
async function readFailureRecords(path) {
|
|
52
|
+
const text = await readFile(path, 'utf8').catch(() => '');
|
|
53
|
+
return text
|
|
54
|
+
.split('\n')
|
|
55
|
+
.filter(Boolean)
|
|
56
|
+
.map((line) => JSON.parse(line));
|
|
57
|
+
}
|
|
58
|
+
|
|
8
59
|
// Both discovery and explicit-file runs use the same output contract. Node
|
|
9
|
-
// still owns test selection, diagnostics, skip/todo counts and exit status
|
|
10
|
-
|
|
60
|
+
// still owns test selection, diagnostics, skip/todo counts and exit status;
|
|
61
|
+
// a batched run keeps one full log and fails on the first failing batch.
|
|
62
|
+
//
|
|
63
|
+
// With `coverage`, every batch inherits one fresh NODE_V8_COVERAGE directory,
|
|
64
|
+
// so V8 writes a dump per test process into it and the batches fold into a
|
|
65
|
+
// single artifact. Without it nothing here changes — not the spawn options,
|
|
66
|
+
// not the environment, not the output.
|
|
67
|
+
//
|
|
68
|
+
// `rerunFailed` (default 0 = off) re-runs the files that failed, up to n
|
|
69
|
+
// times. A test that passes on a re-run is reported as flaky and, because the
|
|
70
|
+
// flag was passed, does not fail the run; a test that fails every re-run
|
|
71
|
+
// still exits 1. Every run ends with the failure summary block, so a filtered
|
|
72
|
+
// log can still name what failed.
|
|
73
|
+
export async function runNodeTests(
|
|
74
|
+
nodeArgs,
|
|
75
|
+
fileArgs,
|
|
76
|
+
{ argBudget = ARG_BUDGET, coverage = false, rerunFailed = 0 } = {}
|
|
77
|
+
) {
|
|
11
78
|
const logPath = join(await mkdtemp(join(tmpdir(), 'mixdog-test-output-')), 'full.log');
|
|
12
79
|
console.error(`Full test log: ${logPath}`);
|
|
13
|
-
|
|
80
|
+
// Failure records live outside the log directory: the log directory holds
|
|
81
|
+
// exactly one merged log, and these records are an internal artifact.
|
|
82
|
+
const failureDir = await mkdtemp(join(tmpdir(), 'mixdog-test-failures-'));
|
|
83
|
+
const runArgs = [
|
|
14
84
|
...nodeArgs,
|
|
15
85
|
`--test-reporter=${SUMMARY_REPORTER}`,
|
|
16
86
|
'--test-reporter-destination=stdout',
|
|
17
87
|
'--test-reporter=spec',
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
88
|
+
];
|
|
89
|
+
const fixedCost =
|
|
90
|
+
argCost(process.execPath) +
|
|
91
|
+
runArgs.reduce((total, arg) => total + argCost(arg), 0) +
|
|
92
|
+
argCost(`--test-reporter-destination=${logPath}`) +
|
|
93
|
+
argCost('.999'); // the per-batch log suffix
|
|
94
|
+
const batches = chunkFileArgs(fileArgs, Math.max(argBudget - fixedCost, 1));
|
|
95
|
+
const coverageDir = coverage ? await mkdtemp(join(tmpdir(), 'mixdog-v8-coverage-')) : '';
|
|
96
|
+
// Failure records travel by environment, not argv: the command line is
|
|
97
|
+
// already budgeted, and a third reporter would warn about listeners.
|
|
98
|
+
const childEnv = (recordsPath) => ({
|
|
99
|
+
...process.env,
|
|
100
|
+
[FAILURE_RECORDS_ENV]: recordsPath,
|
|
101
|
+
...(coverageDir ? { NODE_V8_COVERAGE: coverageDir } : {}),
|
|
27
102
|
});
|
|
103
|
+
let status = 0;
|
|
104
|
+
const failures = [];
|
|
105
|
+
for (const [index, batch] of batches.entries()) {
|
|
106
|
+
const batchLog = batches.length === 1 ? logPath : `${logPath}.${index + 1}`;
|
|
107
|
+
const batchFailures = join(failureDir, `batch-${index + 1}.jsonl`);
|
|
108
|
+
const code = await spawnBatch(
|
|
109
|
+
[...runArgs, `--test-reporter-destination=${batchLog}`, ...batch],
|
|
110
|
+
childEnv(batchFailures)
|
|
111
|
+
);
|
|
112
|
+
if (batchLog !== logPath) {
|
|
113
|
+
await appendFile(logPath, await readFile(batchLog));
|
|
114
|
+
await rm(batchLog, { force: true });
|
|
115
|
+
}
|
|
116
|
+
failures.push(...(await readFailureRecords(batchFailures)));
|
|
117
|
+
if (status === 0) status = code;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
const attempts = [];
|
|
121
|
+
let lastAttemptStatus = 0;
|
|
122
|
+
let pending = failures;
|
|
123
|
+
for (let attempt = 1; attempt <= rerunFailed && pending.length > 0; attempt += 1) {
|
|
124
|
+
const files = [...new Set(pending.map((record) => record.file))];
|
|
125
|
+
console.error(`Re-running ${files.length} failed file(s): attempt ${attempt}/${rerunFailed}`);
|
|
126
|
+
const attemptLog = `${logPath}.rerun-${attempt}`;
|
|
127
|
+
const attemptFailures = join(failureDir, `rerun-${attempt}.jsonl`);
|
|
128
|
+
lastAttemptStatus = await spawnBatch(
|
|
129
|
+
[...runArgs, `--test-reporter-destination=${attemptLog}`, ...files],
|
|
130
|
+
childEnv(attemptFailures)
|
|
131
|
+
);
|
|
132
|
+
await appendFile(logPath, await readFile(attemptLog));
|
|
133
|
+
await rm(attemptLog, { force: true });
|
|
134
|
+
attempts.push({ files, failures: await readFailureRecords(attemptFailures) });
|
|
135
|
+
pending = classifyRerunOutcomes(failures, attempts).failed;
|
|
136
|
+
}
|
|
137
|
+
const { failed, flaky } = classifyRerunOutcomes(failures, attempts);
|
|
138
|
+
// Flaky tolerance applies only to an ordinary test-failure exit that the
|
|
139
|
+
// re-runs fully explained: nothing failed twice and the last re-run itself
|
|
140
|
+
// came back clean.
|
|
141
|
+
if (rerunFailed > 0 && status === 1 && failed.length === 0 && flaky.length > 0 && lastAttemptStatus === 0) status = 0;
|
|
142
|
+
process.exitCode = status;
|
|
143
|
+
|
|
144
|
+
await rm(failureDir, { recursive: true, force: true });
|
|
145
|
+
if (coverageDir) {
|
|
146
|
+
const { reportCoverage } = await import('./coverage.mjs');
|
|
147
|
+
await reportCoverage(coverageDir);
|
|
148
|
+
await rm(coverageDir, { recursive: true, force: true });
|
|
149
|
+
}
|
|
150
|
+
// Last line of the run, after every reporter and the coverage report.
|
|
151
|
+
process.stdout.write(`\n${formatFailureSummary({ failed, flaky, rerunFailed })}`);
|
|
28
152
|
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
// Structured failure records for the run-level failure summary.
|
|
2
|
+
//
|
|
3
|
+
// Node keeps ownership of diagnostics, totals and exit status; this only
|
|
4
|
+
// records "which test failed, in which file" as one JSON line each, so the
|
|
5
|
+
// runner can name every failure after all other output even when the run log
|
|
6
|
+
// was filtered (scripts/lib/run-node-tests.mjs).
|
|
7
|
+
//
|
|
8
|
+
// It rides the existing summary reporter instead of adding a third node:test
|
|
9
|
+
// reporter: a third reporter pushes the shared TestsStream past its listener
|
|
10
|
+
// cap and every run would start with a MaxListenersExceededWarning.
|
|
11
|
+
import { appendFileSync } from 'node:fs';
|
|
12
|
+
import { relative } from 'node:path';
|
|
13
|
+
|
|
14
|
+
export const FAILURE_RECORDS_ENV = 'MIXDOG_TEST_FAILURE_RECORDS';
|
|
15
|
+
|
|
16
|
+
// Ancestor names are tracked per file: node:test runs files concurrently, so
|
|
17
|
+
// one flat stack would mix their nestings.
|
|
18
|
+
export function failureRecord(event, stacks) {
|
|
19
|
+
const file = event.data?.file ? relative(process.cwd(), String(event.data.file)).replaceAll('\\', '/') : '';
|
|
20
|
+
if (event.type === 'test:start') {
|
|
21
|
+
const stack = stacks.get(file) ?? [];
|
|
22
|
+
stack.length = event.data.nesting;
|
|
23
|
+
stack[event.data.nesting] = event.data.name;
|
|
24
|
+
stacks.set(file, stack);
|
|
25
|
+
return null;
|
|
26
|
+
}
|
|
27
|
+
if (event.type !== 'test:fail') return null;
|
|
28
|
+
// A failing todo does not fail the run, and a suite reports its children's
|
|
29
|
+
// failures as its own; only the leaves that decide the exit status belong
|
|
30
|
+
// in the summary.
|
|
31
|
+
if (event.data.todo) return null;
|
|
32
|
+
if (event.data.details?.error?.failureType === 'subtestsFailed') return null;
|
|
33
|
+
const ancestors = (stacks.get(file) ?? []).slice(0, event.data.nesting);
|
|
34
|
+
return { file, name: [...ancestors, event.data.name].join(' > ') };
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// The runner sets FAILURE_RECORDS_ENV to a per-spawn JSONL path. Without it
|
|
38
|
+
// nothing is recorded, so a direct `node --test` run is unaffected.
|
|
39
|
+
export function createFailureRecorder(path = process.env[FAILURE_RECORDS_ENV]) {
|
|
40
|
+
if (!path) return () => {};
|
|
41
|
+
const stacks = new Map();
|
|
42
|
+
return (event) => {
|
|
43
|
+
const record = failureRecord(event, stacks);
|
|
44
|
+
if (record) appendFileSync(path, `${JSON.stringify(record)}\n`);
|
|
45
|
+
};
|
|
46
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
// Run-level failure classification and the final summary block.
|
|
2
|
+
//
|
|
3
|
+
// A filtered run log loses a failure: `ℹ fail 1` says a test failed but never
|
|
4
|
+
// which one. Every line here carries SUMMARY_TAG and names its file and test,
|
|
5
|
+
// so the block survives `grep`/`Select-String` filtering, and the runner
|
|
6
|
+
// prints it after all other output (scripts/lib/run-node-tests.mjs).
|
|
7
|
+
export const SUMMARY_TAG = '[failure-summary]';
|
|
8
|
+
|
|
9
|
+
const keyOf = (record) => `${record.file}\u0000${record.name}`;
|
|
10
|
+
const byLocation = (a, b) => (a.file === b.file ? a.name.localeCompare(b.name) : a.file.localeCompare(b.file));
|
|
11
|
+
|
|
12
|
+
// A test that failed and then passed inside the re-run budget is flaky; one
|
|
13
|
+
// that fails every attempt stays failed. A failure that only appears during a
|
|
14
|
+
// re-run joins the failed set from the attempt that produced it, so a re-run
|
|
15
|
+
// can never hide a test it just broke.
|
|
16
|
+
//
|
|
17
|
+
// attempts: [{ files: [rerun file paths], failures: [{ file, name }] }]
|
|
18
|
+
export function classifyRerunOutcomes(initialFailures, attempts = []) {
|
|
19
|
+
const pending = new Map(initialFailures.map((record) => [keyOf(record), { ...record }]));
|
|
20
|
+
const flaky = new Map();
|
|
21
|
+
for (const [index, attempt] of attempts.entries()) {
|
|
22
|
+
const attemptNumber = index + 1;
|
|
23
|
+
const rerunFiles = new Set(attempt.files);
|
|
24
|
+
const stillFailing = new Set(attempt.failures.map(keyOf));
|
|
25
|
+
for (const [key, record] of [...pending]) {
|
|
26
|
+
if (!rerunFiles.has(record.file) || stillFailing.has(key)) continue;
|
|
27
|
+
pending.delete(key);
|
|
28
|
+
flaky.set(key, { ...record, passedOnAttempt: attemptNumber });
|
|
29
|
+
}
|
|
30
|
+
for (const record of attempt.failures) {
|
|
31
|
+
const key = keyOf(record);
|
|
32
|
+
if (pending.has(key) || flaky.has(key)) continue;
|
|
33
|
+
pending.set(key, { ...record, firstSeenOnRerun: attemptNumber });
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
return { failed: [...pending.values()].sort(byLocation), flaky: [...flaky.values()].sort(byLocation) };
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// Exit-code policy, stated wherever the classification is shown.
|
|
40
|
+
export function policyLine(rerunFailed) {
|
|
41
|
+
return rerunFailed > 0
|
|
42
|
+
? `policy: --rerun-failed ${rerunFailed} is set, so a flaky test does not fail the run; a test that fails every re-run still exits 1`
|
|
43
|
+
: 'policy: --rerun-failed is off, so every failure fails the run (pass --rerun-failed <n> to classify flaky)';
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export function formatFailureSummary({ failed = [], flaky = [], rerunFailed = 0 }) {
|
|
47
|
+
const lines = [`${SUMMARY_TAG} ${failed.length} failed, ${flaky.length} flaky (every failed test is named below)`];
|
|
48
|
+
for (const record of failed) {
|
|
49
|
+
const origin = record.firstSeenOnRerun ? ` (first seen on re-run ${record.firstSeenOnRerun})` : '';
|
|
50
|
+
lines.push(`${SUMMARY_TAG} FAILED ${record.file} > ${record.name}${origin}`);
|
|
51
|
+
}
|
|
52
|
+
for (const record of flaky) {
|
|
53
|
+
lines.push(
|
|
54
|
+
`${SUMMARY_TAG} FLAKY ${record.file} > ${record.name} (failed, then passed on re-run ${record.passedOnAttempt}/${rerunFailed})`
|
|
55
|
+
);
|
|
56
|
+
}
|
|
57
|
+
if (failed.length > 0 || flaky.length > 0) lines.push(`${SUMMARY_TAG} ${policyLine(rerunFailed)}`);
|
|
58
|
+
return `${lines.join('\n')}\n`;
|
|
59
|
+
}
|
|
@@ -1,14 +1,18 @@
|
|
|
1
1
|
import { Readable } from 'node:stream';
|
|
2
2
|
import { spec } from 'node:test/reporters';
|
|
3
|
+
import { createFailureRecorder } from './test-failure-records.mjs';
|
|
3
4
|
import timingReporter from './test-timing-reporter.mjs';
|
|
4
5
|
|
|
5
6
|
// Filter structured success events, never arbitrary stdout/stderr text.
|
|
6
7
|
// Node's own reporter still owns failure diagnostics, warnings and totals.
|
|
7
|
-
// The runner writes an unfiltered spec report alongside this summary
|
|
8
|
+
// The runner writes an unfiltered spec report alongside this summary, and
|
|
9
|
+
// every failure is also recorded for the run's final failure summary.
|
|
8
10
|
export default async function* summaryReporter(source) {
|
|
9
11
|
const timings = [];
|
|
12
|
+
const recordFailure = createFailureRecorder();
|
|
10
13
|
async function* events() {
|
|
11
14
|
for await (const event of source) {
|
|
15
|
+
recordFailure(event);
|
|
12
16
|
if ((event.type === 'test:pass' || event.type === 'test:fail') && event.data.nesting === 0) {
|
|
13
17
|
timings.push({
|
|
14
18
|
type: event.type,
|
|
@@ -17,8 +21,8 @@ export default async function* summaryReporter(source) {
|
|
|
17
21
|
}
|
|
18
22
|
// A start without its matching pass leaves a pending name in spec's
|
|
19
23
|
// parent stack. Suppress both; fail events carry their own location.
|
|
20
|
-
if (event.type !== 'test:start' &&
|
|
21
|
-
|
|
24
|
+
if (event.type !== 'test:start' && (event.type !== 'test:pass' || event.data.skip || event.data.todo))
|
|
25
|
+
yield event;
|
|
22
26
|
}
|
|
23
27
|
}
|
|
24
28
|
yield* Readable.from(events()).pipe(new spec());
|
package/scripts/mouse-probe.mjs
CHANGED
|
@@ -29,10 +29,8 @@ process.on('SIGINT', cleanup);
|
|
|
29
29
|
process.stdin.on('data', (buf) => {
|
|
30
30
|
const s = buf.toString('utf8');
|
|
31
31
|
if (s === 'q' || s === '\x03') return cleanup();
|
|
32
|
-
let m;
|
|
33
|
-
MOUSE.lastIndex = 0;
|
|
34
32
|
let sawMouse = false;
|
|
35
|
-
|
|
33
|
+
for (const m of s.matchAll(MOUSE)) {
|
|
36
34
|
sawMouse = true;
|
|
37
35
|
const b = Number(m[1]);
|
|
38
36
|
const mods = [b & 4 ? 'shift' : '', b & 8 ? 'alt' : '', b & 16 ? 'ctrl' : ''].filter(Boolean).join('+') || 'none';
|