@herbertgao/pi-extensions 2026.8.14 → 2026.8.15
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/README.md +16 -13
- package/THIRD_PARTY_NOTICES.md +0 -1
- package/node_modules/@czottmann/pi-automode/CHANGELOG.md +8 -1
- package/node_modules/@czottmann/pi-automode/README.md +4 -0
- package/node_modules/@czottmann/pi-automode/extensions/auto-mode/classifier.ts +71 -9
- package/node_modules/@czottmann/pi-automode/package.json +1 -1
- package/node_modules/@herbertgao/pi-bark/LICENSE +21 -0
- package/node_modules/@herbertgao/pi-bark/README.md +44 -0
- package/node_modules/@herbertgao/pi-bark/assets/pi-icon.png +0 -0
- package/node_modules/@herbertgao/pi-bark/package.json +53 -0
- package/node_modules/@herbertgao/pi-bark/src/index.ts +178 -0
- package/node_modules/@juicesharp/rpiv-ask-user-question/package.json +2 -2
- package/node_modules/@narumitw/pi-btw/README.md +125 -88
- package/node_modules/@narumitw/pi-btw/dist/index.ts +140 -45
- package/node_modules/@narumitw/pi-btw/dist/index.ts.map +2 -2
- package/node_modules/@narumitw/pi-btw/package.json +5 -8
- package/node_modules/@narumitw/pi-btw/src/btw.ts +31 -26
- package/node_modules/@narumitw/pi-btw/src/fullscreen-ui.ts +106 -17
- package/node_modules/@narumitw/pi-btw/src/menu.ts +34 -2
- package/node_modules/@narumitw/pi-btw/src/settings.ts +16 -0
- package/node_modules/@tifan/pi-handoff/README.md +36 -13
- package/node_modules/@tifan/pi-handoff/package.json +2 -2
- package/node_modules/@tifan/pi-handoff/src/index.ts +160 -4
- package/node_modules/@tifan/pi-recap/README.md +1 -1
- package/node_modules/@tifan/pi-recap/package.json +2 -2
- package/node_modules/@tifan/pi-recap/src/index.ts +15 -0
- package/node_modules/@tifan/pi-rename/README.md +42 -21
- package/node_modules/@tifan/pi-rename/package.json +1 -1
- package/node_modules/@tifan/pi-rename/src/index.ts +62 -11
- package/node_modules/@tifan/pi-rename/src/language.ts +18 -0
- package/node_modules/@tifan/pi-rename/src/models.ts +69 -13
- package/node_modules/@tifan/pi-rename/src/naming.ts +36 -7
- package/node_modules/@tifan/pi-rename/src/sanitize.ts +22 -14
- package/node_modules/pi-lens/CHANGELOG.md +320 -0
- package/node_modules/pi-lens/config/dependency-cruiser-eager-allowlist.json +79 -0
- package/node_modules/pi-lens/dist/clients/actionable-warnings-logger.js +16 -1
- package/node_modules/pi-lens/dist/clients/ast-grep-client.js +13 -6
- package/node_modules/pi-lens/dist/clients/availability-probe-flight.js +12 -0
- package/node_modules/pi-lens/dist/clients/biome-client.js +7 -0
- package/node_modules/pi-lens/dist/clients/blocker-freshness.js +20 -0
- package/node_modules/pi-lens/dist/clients/bounded-telemetry.js +56 -19
- package/node_modules/pi-lens/dist/clients/build-identity.js +95 -0
- package/node_modules/pi-lens/dist/clients/bus-publish.js +3 -0
- package/node_modules/pi-lens/dist/clients/cache/rule-cache.js +2 -1
- package/node_modules/pi-lens/dist/clients/cache-observability.js +2 -1
- package/node_modules/pi-lens/dist/clients/cascade-logger.js +15 -1
- package/node_modules/pi-lens/dist/clients/dead-code-client.js +14 -11
- package/node_modules/pi-lens/dist/clients/degradation-ledger.js +56 -4
- package/node_modules/pi-lens/dist/clients/demoted-finding-render.js +14 -0
- package/node_modules/pi-lens/dist/clients/dependency-checker.js +20 -3
- package/node_modules/pi-lens/dist/clients/deps/minimatch.js +1 -1
- package/node_modules/pi-lens/dist/clients/dispatch/collect-later-tier.js +39 -0
- package/node_modules/pi-lens/dist/clients/dispatch/dispatcher.js +150 -9
- package/node_modules/pi-lens/dist/clients/dispatch/fact-store.js +231 -9
- package/node_modules/pi-lens/dist/clients/dispatch/integration.js +205 -100
- package/node_modules/pi-lens/dist/clients/dispatch/pending-runner-findings.js +87 -0
- package/node_modules/pi-lens/dist/clients/dispatch/plan.js +21 -5
- package/node_modules/pi-lens/dist/clients/dispatch/runners/ast-grep-napi.js +174 -55
- package/node_modules/pi-lens/dist/clients/dispatch/runners/eslint.js +5 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/helm-lint.js +16 -3
- package/node_modules/pi-lens/dist/clients/dispatch/runners/helm-render.js +54 -9
- package/node_modules/pi-lens/dist/clients/dispatch/runners/index.js +1 -3
- package/node_modules/pi-lens/dist/clients/dispatch/runners/lsp.js +37 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/markdownlint.js +4 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/oxlint.js +11 -4
- package/node_modules/pi-lens/dist/clients/dispatch/runners/psscriptanalyzer.js +4 -0
- package/node_modules/pi-lens/dist/clients/dispatch/runners/rust-clippy.js +8 -3
- package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/runner-helpers.js +76 -25
- package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/spawn-outcome.js +4 -0
- package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/tool-failure.js +19 -7
- package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/toolchain-availability.js +7 -13
- package/node_modules/pi-lens/dist/clients/dispatch/runners/yaml-rule-parser.js +51 -17
- package/node_modules/pi-lens/dist/clients/event-loop-monitor.js +66 -1
- package/node_modules/pi-lens/dist/clients/file-utils.js +157 -7
- package/node_modules/pi-lens/dist/clients/finding-delivery-gate.js +5 -0
- package/node_modules/pi-lens/dist/clients/formatters.js +28 -3
- package/node_modules/pi-lens/dist/clients/git-guard.js +197 -47
- package/node_modules/pi-lens/dist/clients/git-tracked-ignore.js +32 -1
- package/node_modules/pi-lens/dist/clients/govulncheck-client.js +5 -1
- package/node_modules/pi-lens/dist/clients/gzip-stage-write.js +7 -0
- package/node_modules/pi-lens/dist/clients/installer/index.js +131 -34
- package/node_modules/pi-lens/dist/clients/installer/managed-tool-refresh.js +3 -1
- package/node_modules/pi-lens/dist/clients/instance-reaper.js +4 -13
- package/node_modules/pi-lens/dist/clients/instance-registry-lock.js +145 -0
- package/node_modules/pi-lens/dist/clients/instance-registry.js +432 -122
- package/node_modules/pi-lens/dist/clients/jscpd-client.js +6 -1
- package/node_modules/pi-lens/dist/clients/language-profile.js +4 -2
- package/node_modules/pi-lens/dist/clients/latency-logger.js +19 -1
- package/node_modules/pi-lens/dist/clients/lens-flag-registry.js +8 -0
- package/node_modules/pi-lens/dist/clients/lens-map.js +3 -1
- package/node_modules/pi-lens/dist/clients/lsp/cascade-tier.js +4 -0
- package/node_modules/pi-lens/dist/clients/lsp/client.js +516 -31
- package/node_modules/pi-lens/dist/clients/lsp/config.js +25 -2
- package/node_modules/pi-lens/dist/clients/lsp/diagnostic-binding.js +9 -4
- package/node_modules/pi-lens/dist/clients/lsp/index.js +509 -182
- package/node_modules/pi-lens/dist/clients/lsp/jvm-runtime.js +5 -0
- package/node_modules/pi-lens/dist/clients/lsp/launch.js +36 -8
- package/node_modules/pi-lens/dist/clients/lsp/pending-aux-coverage.js +116 -4
- package/node_modules/pi-lens/dist/clients/lsp/server.js +142 -13
- package/node_modules/pi-lens/dist/clients/lsp/session-roots.js +99 -0
- package/node_modules/pi-lens/dist/clients/lsp/workspace-diagnostics-cache.js +16 -6
- package/node_modules/pi-lens/dist/clients/lsp/workspace-sweep-hold.js +32 -23
- package/node_modules/pi-lens/dist/clients/lsp-mutation.js +16 -2
- package/node_modules/pi-lens/dist/clients/mcp/analyze.js +12 -6
- package/node_modules/pi-lens/dist/clients/mcp/session.js +14 -1
- package/node_modules/pi-lens/dist/clients/memory-sampler.js +23 -1
- package/node_modules/pi-lens/dist/clients/message-end-attribution.js +30 -0
- package/node_modules/pi-lens/dist/clients/metrics-history.js +21 -4
- package/node_modules/pi-lens/dist/clients/opaque-mutation-scan.js +232 -18
- package/node_modules/pi-lens/dist/clients/package-manager.js +6 -18
- package/node_modules/pi-lens/dist/clients/path-keyed-map.js +14 -1
- package/node_modules/pi-lens/dist/clients/path-utils.js +32 -0
- package/node_modules/pi-lens/dist/clients/pipeline.js +0 -8
- package/node_modules/pi-lens/dist/clients/process-singletons.js +210 -0
- package/node_modules/pi-lens/dist/clients/project-diagnostics/runner-adapters/madge.js +2 -1
- package/node_modules/pi-lens/dist/clients/project-diagnostics/scanner.js +10 -4
- package/node_modules/pi-lens/dist/clients/project-snapshot.js +33 -11
- package/node_modules/pi-lens/dist/clients/read-guard-logger.js +24 -2
- package/node_modules/pi-lens/dist/clients/read-guard.js +74 -7
- package/node_modules/pi-lens/dist/clients/recent-touches.js +17 -5
- package/node_modules/pi-lens/dist/clients/review-graph/builder.js +639 -62
- package/node_modules/pi-lens/dist/clients/review-graph/tsconfig-paths.js +5 -1
- package/node_modules/pi-lens/dist/clients/review-graph-logger.js +21 -1
- package/node_modules/pi-lens/dist/clients/runtime-coordinator.js +68 -0
- package/node_modules/pi-lens/dist/clients/runtime-session.js +30 -12
- package/node_modules/pi-lens/dist/clients/runtime-tool-call.js +15 -0
- package/node_modules/pi-lens/dist/clients/runtime-tool-result.js +106 -7
- package/node_modules/pi-lens/dist/clients/runtime-turn.js +242 -27
- package/node_modules/pi-lens/dist/clients/safe-spawn.js +158 -18
- package/node_modules/pi-lens/dist/clients/security-scan-client.js +48 -17
- package/node_modules/pi-lens/dist/clients/session-lifecycle.js +238 -42
- package/node_modules/pi-lens/dist/clients/sg-runner.js +27 -15
- package/node_modules/pi-lens/dist/clients/shared-checkout-guard.js +338 -0
- package/node_modules/pi-lens/dist/clients/spawn-output-cap.js +37 -0
- package/node_modules/pi-lens/dist/clients/spawn-timeout-cooldown.js +15 -1
- package/node_modules/pi-lens/dist/clients/startup-scan.js +6 -7
- package/node_modules/pi-lens/dist/clients/startup-timing.js +69 -4
- package/node_modules/pi-lens/dist/clients/string-utils.js +20 -0
- package/node_modules/pi-lens/dist/clients/test-runner-client.js +419 -88
- package/node_modules/pi-lens/dist/clients/tool-policy.js +1 -1
- package/node_modules/pi-lens/dist/clients/tree-sitter-client.js +11 -5
- package/node_modules/pi-lens/dist/clients/tree-sitter-logger.js +17 -1
- package/node_modules/pi-lens/dist/clients/warm-attach.js +8 -10
- package/node_modules/pi-lens/dist/clients/word-index-logger.js +13 -1
- package/node_modules/pi-lens/dist/clients/word-index-store.js +448 -0
- package/node_modules/pi-lens/dist/clients/word-index.js +582 -104
- package/node_modules/pi-lens/dist/clients/workspace-topology.js +17 -0
- package/node_modules/pi-lens/dist/clients/zizmor-config.js +26 -6
- package/node_modules/pi-lens/dist/index.js +11243 -6651
- package/node_modules/pi-lens/dist/mcp/build-staleness.js +8 -7
- package/node_modules/pi-lens/dist/mcp/cli.js +3 -1
- package/node_modules/pi-lens/dist/mcp/server.js +6 -1
- package/node_modules/pi-lens/dist/tools/lens-diagnostics.js +4 -0
- package/node_modules/pi-lens/dist/tools/lsp-diagnostics.js +14 -5
- package/node_modules/pi-lens/dist/tools/lsp-navigation.js +3 -1
- package/node_modules/pi-lens/docs/agent-guide.md +4 -2
- package/node_modules/pi-lens/docs/ast-grep_rules_catalog.md +41 -10
- package/node_modules/pi-lens/docs/astplayground.md +26 -17
- package/node_modules/pi-lens/package.json +5 -1
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/c-no-malloc-free-test.yml +20 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/cpp-no-malloc-free-test.yml +25 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/cpp-no-printf-test.yml +19 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/go-no-fmt-println-test.yml +19 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/go-no-panic-in-lib-test.yml +17 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/go-no-underscore-func-name-test.yml +18 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/go-prefer-errors-is-test.yml +15 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/go-prefer-string-builder-test.yml +21 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/hyphenated-svg-attribute-test.yml +11 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/kotlin-no-lateinit-test.yml +17 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/kotlin-no-nullable-boolean-test.yml +15 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/kotlin-no-println-test.yml +19 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/kotlin-no-unnecessary-let-test.yml +7 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/kotlin-no-unsafe-cast-test.yml +7 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/kotlin-no-var-test.yml +7 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/kotlin-prefer-data-class-test.yml +25 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/kotlin-prefer-is-empty-test.yml +11 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-important-test.yml +21 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-raw-types-test.yml +30 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-string-concat-in-loop-test.yml +100 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-system-out-println-test.yml +7 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/prefer-string-is-empty-test.yml +7 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/python-optional-type-test.yml +9 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/reducible-list-comprehension-test.yml +11 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/ruby-symbol-to-proc-candidate-test.yml +10 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/rust-no-chars-enumerate-test.yml +7 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/c-no-malloc-free.yml +39 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/cpp-no-malloc-free.yml +45 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/cpp-no-printf.yml +41 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/go-no-fmt-println.yml +43 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/go-no-panic-in-lib.yml +41 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/go-no-underscore-func-name.yml +35 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/go-prefer-errors-is.yml +40 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/go-prefer-string-builder.yml +43 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/hyphenated-svg-attribute.yml +32 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/kotlin-no-lateinit.yml +37 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/kotlin-no-nullable-boolean.yml +33 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/kotlin-no-println.yml +42 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/kotlin-no-unnecessary-let.yml +28 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/kotlin-no-unsafe-cast.yml +28 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/kotlin-no-var.yml +35 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/kotlin-prefer-data-class.yml +66 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/kotlin-prefer-is-empty.yml +35 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-important.yml +21 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-raw-types.yml +33 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-string-concat-in-loop.yml +90 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-system-out-println.yml +16 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/prefer-string-is-empty.yml +11 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/python-optional-type.yml +22 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/reducible-list-comprehension.yml +29 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/ruby-symbol-to-proc-candidate.yml +25 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/rust-no-chars-enumerate.yml +19 -0
- package/node_modules/pi-mcp-adapter/CHANGELOG.md +15 -0
- package/node_modules/pi-mcp-adapter/README.md +2 -2
- package/node_modules/pi-mcp-adapter/commands.ts +1 -1
- package/node_modules/pi-mcp-adapter/dist/types.d.ts +1 -1
- package/node_modules/pi-mcp-adapter/mcp-auth-flow.ts +105 -32
- package/node_modules/pi-mcp-adapter/package.json +1 -1
- package/node_modules/pi-mcp-adapter/proxy-modes.ts +22 -12
- package/node_modules/pi-mcp-adapter/server-manager.ts +6 -0
- package/node_modules/pi-mcp-adapter/types.ts +1 -1
- package/node_modules/pi-web-access/CHANGELOG.md +20 -0
- package/node_modules/pi-web-access/README.md +15 -2
- package/node_modules/pi-web-access/extract.ts +57 -12
- package/node_modules/pi-web-access/github-extract.ts +47 -3
- package/node_modules/pi-web-access/index.ts +1 -1
- package/node_modules/pi-web-access/package.json +1 -1
- package/node_modules/pi-web-access/page-query.ts +61 -8
- package/package.json +16 -16
- package/node_modules/@tifan/pi-titlebar-spinner/README.md +0 -24
- package/node_modules/@tifan/pi-titlebar-spinner/package.json +0 -39
- package/node_modules/@tifan/pi-titlebar-spinner/src/index.ts +0 -49
|
@@ -3,6 +3,12 @@ import { logExtension } from "./extension-log.js";
|
|
|
3
3
|
import { LEDGER_FIELD_MAX, truncateForLedger } from "./ledger-bounds.js";
|
|
4
4
|
import { logLatency } from "./latency-logger.js";
|
|
5
5
|
import { getSinkWriteFailures, resetSinkWriteFailures, } from "./ndjson-logger.js";
|
|
6
|
+
// #2146: pulled at READ time, never pushed. `process-singletons.ts` is a
|
|
7
|
+
// dependency leaf on purpose — it cannot import this module without closing a
|
|
8
|
+
// no-client-cycles cycle through instance-registry/instance-reaper — so the
|
|
9
|
+
// ledger reaches IN for its reset log, the same inversion `getSinkWriteFailures`
|
|
10
|
+
// above uses.
|
|
11
|
+
import { getProcessSingletonResets, PROCESS_SINGLETON_RESET_KIND, } from "./process-singletons.js";
|
|
6
12
|
// Re-exported so existing importers keep one name for the ledger's bound.
|
|
7
13
|
export { LEDGER_FIELD_MAX, truncateForLedger };
|
|
8
14
|
const ENTRIES_PER_KIND = 20;
|
|
@@ -59,7 +65,7 @@ export function recordDegradationOnce(record) {
|
|
|
59
65
|
return;
|
|
60
66
|
onceKeys.add(key);
|
|
61
67
|
if (recordDegradation({ kind, subject, reason: record.reason })) {
|
|
62
|
-
logDurableDegradation(kind, subject, 1);
|
|
68
|
+
logDurableDegradation(kind, subject, 1, record.metadata);
|
|
63
69
|
}
|
|
64
70
|
}
|
|
65
71
|
catch (error) {
|
|
@@ -108,7 +114,7 @@ export function incrementDegradationCount(record) {
|
|
|
108
114
|
// Durable rows use the summary's admission and emit the first event and
|
|
109
115
|
// power-of-two milestones only, keeping the sink bounded.
|
|
110
116
|
if (admitted && isPowerOfTwo(count)) {
|
|
111
|
-
logDurableDegradation(kind, subject, count);
|
|
117
|
+
logDurableDegradation(kind, subject, count, record.metadata);
|
|
112
118
|
}
|
|
113
119
|
return count === 1;
|
|
114
120
|
}
|
|
@@ -124,15 +130,34 @@ export function incrementDegradationCount(record) {
|
|
|
124
130
|
* redaction, and write queue. The subject and kind were already bounded by the
|
|
125
131
|
* ledger before reaching this seam.
|
|
126
132
|
*/
|
|
127
|
-
function logDurableDegradation(kind, subject, count) {
|
|
133
|
+
function logDurableDegradation(kind, subject, count, metadata) {
|
|
134
|
+
const boundedMetadata = boundLedgerMetadata(metadata);
|
|
128
135
|
logLatency({
|
|
129
136
|
type: "phase",
|
|
130
137
|
phase: "degradation_ledger",
|
|
131
138
|
filePath: subject,
|
|
132
139
|
durationMs: 0,
|
|
133
|
-
metadata: {
|
|
140
|
+
metadata: {
|
|
141
|
+
...boundedMetadata,
|
|
142
|
+
kind,
|
|
143
|
+
subject,
|
|
144
|
+
count,
|
|
145
|
+
ledgerGeneration,
|
|
146
|
+
},
|
|
134
147
|
});
|
|
135
148
|
}
|
|
149
|
+
const MAX_METADATA_KEYS = 8;
|
|
150
|
+
function boundLedgerMetadata(metadata) {
|
|
151
|
+
if (!metadata)
|
|
152
|
+
return {};
|
|
153
|
+
const entries = Object.entries(metadata);
|
|
154
|
+
const kept = entries.slice(0, MAX_METADATA_KEYS);
|
|
155
|
+
const bounded = Object.fromEntries(kept.map(([key, value]) => [key, truncateForLedger(value)]));
|
|
156
|
+
const dropped = entries.length - kept.length;
|
|
157
|
+
if (dropped > 0)
|
|
158
|
+
bounded.metadataDropped = dropped;
|
|
159
|
+
return bounded;
|
|
160
|
+
}
|
|
136
161
|
function isPowerOfTwo(value) {
|
|
137
162
|
return value > 0 && (value & (value - 1)) === 0;
|
|
138
163
|
}
|
|
@@ -165,6 +190,21 @@ export function getDegradationSummary() {
|
|
|
165
190
|
})),
|
|
166
191
|
});
|
|
167
192
|
}
|
|
193
|
+
// #2146, same read-time fold: process-singleton resets live in the leaf
|
|
194
|
+
// module's own bounded log. One entry per family, so this group's count is
|
|
195
|
+
// the number of families this build could not adopt, never an event tally.
|
|
196
|
+
const singletonResets = getProcessSingletonResets();
|
|
197
|
+
if (singletonResets.length > 0) {
|
|
198
|
+
summary.push({
|
|
199
|
+
kind: PROCESS_SINGLETON_RESET_KIND,
|
|
200
|
+
count: singletonResets.length,
|
|
201
|
+
droppedCount: 0,
|
|
202
|
+
latestReasons: singletonResets.map((reset) => ({
|
|
203
|
+
subject: truncateForLedger(reset.family),
|
|
204
|
+
reason: truncateForLedger(reset.reason),
|
|
205
|
+
})),
|
|
206
|
+
});
|
|
207
|
+
}
|
|
168
208
|
return summary;
|
|
169
209
|
}
|
|
170
210
|
function isRenderableSummary(value) {
|
|
@@ -218,6 +258,18 @@ export function resetDegradationLedger() {
|
|
|
218
258
|
// process-lifetime latch too — it re-arms alongside the rest of the
|
|
219
259
|
// ledger rather than surviving past the session that observed it.
|
|
220
260
|
resetSinkWriteFailures();
|
|
261
|
+
// #2146 review F3: the OTHER pulled source, `getProcessSingletonResets()`,
|
|
262
|
+
// deliberately does NOT re-arm here, and the difference from its neighbour
|
|
263
|
+
// above is the point. A sink write failure recurs — new writes fail, so
|
|
264
|
+
// clearing the tally costs nothing and a later session re-observes the
|
|
265
|
+
// problem. A process-singleton reset happens once, at module-evaluation
|
|
266
|
+
// time, and cannot recur: after it, the container holds only compatible
|
|
267
|
+
// cells. Clearing it would show the fact in the first session's
|
|
268
|
+
// `pilens_health` and hide it from every session after, which is exactly
|
|
269
|
+
// when someone reads that line. The row is bounded independently of the
|
|
270
|
+
// session (one entry per family, capped at 16), so leaving it costs a fixed
|
|
271
|
+
// handful of lines and keeps a process-scope fact visible for the process's
|
|
272
|
+
// life. Deliberate exception to catalog shape 17, not an oversight.
|
|
221
273
|
}
|
|
222
274
|
export const DEGRADATION_ENTRIES_PER_KIND = ENTRIES_PER_KIND;
|
|
223
275
|
export const DEGRADATION_MAX_DISTINCT_KINDS = MAX_DISTINCT_KINDS;
|
|
@@ -153,3 +153,17 @@ export function formatRetirementNote(deadLines) {
|
|
|
153
153
|
return (`Retired after this delivery${cited}: the file no longer has these lines, ` +
|
|
154
154
|
"so this finding cannot be re-confirmed and will not be served again.");
|
|
155
155
|
}
|
|
156
|
+
/**
|
|
157
|
+
* The one-line note a surface appends when it RETIRES a dependency-drift
|
|
158
|
+
* demotion at its delivery cap (#1950). Deliberately worded differently from
|
|
159
|
+
* {@link formatRetirementNote}: that one means the finding is provably
|
|
160
|
+
* unrecoverable (the cited lines are gone), while THIS retirement is a
|
|
161
|
+
* bounded-noise cap on a record that is still recoverable — a fresh dispatch
|
|
162
|
+
* of the file could still confirm or clear it. Saying "cannot be re-confirmed"
|
|
163
|
+
* here would be false.
|
|
164
|
+
*/
|
|
165
|
+
export function formatDeliveryCapNote(deliveryCount) {
|
|
166
|
+
return (`Not shown again after ${deliveryCount} deliveries with no re-run: ` +
|
|
167
|
+
"this finding may still be accurate, but re-serving it further adds no " +
|
|
168
|
+
"new information. A fresh dispatch of this file will re-confirm or clear it.");
|
|
169
|
+
}
|
|
@@ -14,6 +14,7 @@ import * as path from "node:path";
|
|
|
14
14
|
import { findNodeToolBinary } from "./package-manager.js";
|
|
15
15
|
import { isFullyQualified } from "./path-utils.js";
|
|
16
16
|
import { safeSpawnAsync } from "./safe-spawn.js";
|
|
17
|
+
import { compareOrdinal } from "./string-utils.js";
|
|
17
18
|
import { createAvailabilityChecker, discoverManagedTool, getManagedToolEnvironment, resolveAvailableOrInstall, } from "./dispatch/runners/utils/runner-helpers.js";
|
|
18
19
|
import { createAvailabilityLatch, } from "./dispatch/runners/utils/availability-policy.js";
|
|
19
20
|
/**
|
|
@@ -501,8 +502,14 @@ export class DependencyChecker {
|
|
|
501
502
|
const existing = this.checkInFlight.get(key);
|
|
502
503
|
if (existing)
|
|
503
504
|
return existing;
|
|
505
|
+
// Identity-guarded release (#1968's pattern): delete only if THIS run is
|
|
506
|
+
// still the registered one. A bare delete-by-key lets a late-settling run
|
|
507
|
+
// evict a live successor a second writer registered under the same key
|
|
508
|
+
// mid-flight, after which the next caller starts a duplicate check.
|
|
504
509
|
const promise = this.runCheckFile(normalized, projectRoot, gen).finally(() => {
|
|
505
|
-
this.checkInFlight.
|
|
510
|
+
if (this.checkInFlight.get(key) === promise) {
|
|
511
|
+
this.checkInFlight.delete(key);
|
|
512
|
+
}
|
|
506
513
|
});
|
|
507
514
|
this.checkInFlight.set(key, promise);
|
|
508
515
|
return promise;
|
|
@@ -781,8 +788,14 @@ export class DependencyChecker {
|
|
|
781
788
|
// does, and the two overlap (runtime-session, fresh-fetch), so it takes a
|
|
782
789
|
// generation too — claimed before the spawn, as late as a scan can.
|
|
783
790
|
const gen = ++this.opGeneration;
|
|
791
|
+
// Identity-guarded release (#1968's pattern): delete only if THIS run is
|
|
792
|
+
// still the registered one. A bare delete-by-key lets a late-settling run
|
|
793
|
+
// evict a live successor a second writer registered under the same key
|
|
794
|
+
// mid-flight, after which the next caller starts a duplicate scan.
|
|
784
795
|
const promise = this.runScanProject(projectRoot, gen).finally(() => {
|
|
785
|
-
this.scanInFlight.
|
|
796
|
+
if (this.scanInFlight.get(projectRoot) === promise) {
|
|
797
|
+
this.scanInFlight.delete(projectRoot);
|
|
798
|
+
}
|
|
786
799
|
});
|
|
787
800
|
this.scanInFlight.set(projectRoot, promise);
|
|
788
801
|
return promise;
|
|
@@ -830,7 +843,11 @@ export class DependencyChecker {
|
|
|
830
843
|
const seen = new Set();
|
|
831
844
|
let output = `[Circular Deps] ${circular.length} cycle(s) found:\n`;
|
|
832
845
|
for (const dep of circular) {
|
|
833
|
-
|
|
846
|
+
// Copy before sorting: `dep.path` is rendered verbatim below (and by
|
|
847
|
+
// other CircularDep consumers, e.g. madge.ts's diagnostic renderer),
|
|
848
|
+
// so the dedupe key must not reorder — let alone mutate in place —
|
|
849
|
+
// the path a user reads.
|
|
850
|
+
const cycleKey = [...dep.path].sort(compareOrdinal).join("→");
|
|
834
851
|
if (seen.has(cycleKey))
|
|
835
852
|
continue;
|
|
836
853
|
seen.add(cycleKey);
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/** Observed-latency classification shared by dispatch and LSP tier seams. */
|
|
2
|
+
import { normalizeMapKey } from "../path-utils.js";
|
|
3
|
+
/** A runner gets this much time before the next run moves off the write path. */
|
|
4
|
+
export const COLLECT_LATER_THRESHOLD_MS = 5_000;
|
|
5
|
+
const slowRunners = new Map();
|
|
6
|
+
function key(projectRoot, runnerId) {
|
|
7
|
+
return `${normalizeMapKey(projectRoot)}\u0000${runnerId}`;
|
|
8
|
+
}
|
|
9
|
+
/** Read the current observed tier. Missing observations stay inline. */
|
|
10
|
+
export function classifyObservedRunner(projectRoot, runnerId) {
|
|
11
|
+
return slowRunners.has(key(projectRoot, runnerId))
|
|
12
|
+
? "collect-later"
|
|
13
|
+
: "inline";
|
|
14
|
+
}
|
|
15
|
+
/** Record one completed latency observation and return the resulting tier. */
|
|
16
|
+
export function observeRunnerLatency(options) {
|
|
17
|
+
const normalizedProjectRoot = normalizeMapKey(options.projectRoot);
|
|
18
|
+
const runnerKey = key(normalizedProjectRoot, options.runnerId);
|
|
19
|
+
if (options.timedOut === true ||
|
|
20
|
+
options.durationMs > COLLECT_LATER_THRESHOLD_MS) {
|
|
21
|
+
slowRunners.set(runnerKey, {
|
|
22
|
+
projectRoot: normalizedProjectRoot,
|
|
23
|
+
runnerId: options.runnerId,
|
|
24
|
+
});
|
|
25
|
+
return "collect-later";
|
|
26
|
+
}
|
|
27
|
+
// A fast completed run is affirmative recovery, not merely absence of a
|
|
28
|
+
// failure. This deliberately prevents a process-lifetime latch.
|
|
29
|
+
slowRunners.delete(runnerKey);
|
|
30
|
+
return "inline";
|
|
31
|
+
}
|
|
32
|
+
/** Session boundary reset. */
|
|
33
|
+
export function resetObservedRunnerLatency() {
|
|
34
|
+
slowRunners.clear();
|
|
35
|
+
}
|
|
36
|
+
/** Test-only inspection without exposing mutable state. */
|
|
37
|
+
export function observedRunnerTierForTests(projectRoot, runnerId) {
|
|
38
|
+
return classifyObservedRunner(projectRoot, runnerId);
|
|
39
|
+
}
|
|
@@ -17,6 +17,7 @@ import { logExtension } from "../extension-log.js";
|
|
|
17
17
|
import * as fs from "node:fs";
|
|
18
18
|
import * as path from "node:path";
|
|
19
19
|
import { recordRunner } from "../widget-state.js";
|
|
20
|
+
import { incrementDegradationCount } from "../degradation-ledger.js";
|
|
20
21
|
import { detectFileKind } from "../file-kinds.js";
|
|
21
22
|
import { detectFileRole } from "../file-role.js";
|
|
22
23
|
import { isTestFile } from "../file-utils.js";
|
|
@@ -24,19 +25,24 @@ import { getPrimaryDispatchGroup } from "../language-policy.js";
|
|
|
24
25
|
import { resolveLanguageRootForFile } from "../language-profile.js";
|
|
25
26
|
import { logLatency, phaseFinished, phaseStarted } from "../latency-logger.js";
|
|
26
27
|
import { isSpawnableCommand } from "../installer/index.js";
|
|
27
|
-
import { normalizeMapKey } from "../path-utils.js";
|
|
28
|
+
import { normalizeEphemeralMapKey, normalizeMapKey } from "../path-utils.js";
|
|
28
29
|
import { loadPiLensProjectConfig } from "../project-lens-config.js";
|
|
29
30
|
import { RUNTIME_CONFIG, getRunnerTimeoutFloorMs } from "../runtime-config.js";
|
|
30
31
|
import { safeSpawnAsync } from "../safe-spawn.js";
|
|
31
32
|
import { classifyDiagnostic } from "./diagnostic-taxonomy.js";
|
|
32
33
|
import { classifyProbeFailure, logAvailabilityDecision, startHostStallSampler, transientRetryDelayMs, } from "./runners/utils/availability-policy.js";
|
|
34
|
+
import { recordAvailabilityProbeOverrun, getDispatchAvailabilityGeneration, } from "./runners/utils/runner-helpers.js";
|
|
35
|
+
import { createAvailabilityProbeFlight } from "../availability-probe-flight.js";
|
|
33
36
|
import { applyDispositions } from "../diagnostic-dispositions.js";
|
|
34
37
|
import { applyInlineSuppressions } from "./inline-suppressions.js";
|
|
35
38
|
import { getToolPlan } from "./plan.js";
|
|
36
39
|
import { resolveRunnerPath } from "./runner-context.js";
|
|
40
|
+
import { classifyObservedRunner, COLLECT_LATER_THRESHOLD_MS, observeRunnerLatency, } from "./collect-later-tier.js";
|
|
41
|
+
import { deferRunnerFindings } from "./pending-runner-findings.js";
|
|
37
42
|
import { applyRulePolicy, rulePolicyMapFromConfig } from "./rule-policy.js";
|
|
38
43
|
import { getToolProfile } from "./tool-profile.js";
|
|
39
44
|
import { isRunnerSkipReason } from "./types.js";
|
|
45
|
+
const dispatcherProbeFlights = createAvailabilityProbeFlight({ generation: () => getDispatchAvailabilityGeneration() });
|
|
40
46
|
import { formatDiagnostics } from "./utils/format-utils.js";
|
|
41
47
|
// --- Runner Registry ---
|
|
42
48
|
export class RunnerRegistry {
|
|
@@ -134,15 +140,19 @@ export async function checkToolAvailability(command, facts) {
|
|
|
134
140
|
const startedAt = Date.now();
|
|
135
141
|
let result;
|
|
136
142
|
let hostStallMs;
|
|
143
|
+
let probeJoined = false;
|
|
137
144
|
try {
|
|
138
|
-
|
|
145
|
+
const shared = dispatcherProbeFlights.run(`dispatcher:${key}`, () => safeSpawnAsync(command, ["--version"], {
|
|
139
146
|
timeout: TOOL_PROBE_TIMEOUT_MS,
|
|
140
|
-
});
|
|
147
|
+
}));
|
|
148
|
+
probeJoined = shared.joined;
|
|
149
|
+
result = await shared.promise;
|
|
141
150
|
}
|
|
142
151
|
finally {
|
|
143
152
|
hostStallMs = sampler.stop();
|
|
144
153
|
}
|
|
145
154
|
const elapsedMs = Date.now() - startedAt;
|
|
155
|
+
recordAvailabilityProbeOverrun(command, key, elapsedMs, TOOL_PROBE_TIMEOUT_MS, result.failure);
|
|
146
156
|
if (result.status === 0) {
|
|
147
157
|
facts.setSessionFact(key, true);
|
|
148
158
|
// The tool answered: retire the cooldown facts rather than leaving a
|
|
@@ -156,6 +166,7 @@ export async function checkToolAvailability(command, facts) {
|
|
|
156
166
|
cause: "ok",
|
|
157
167
|
elapsedMs,
|
|
158
168
|
latched: true,
|
|
169
|
+
classifiedBy: probeJoined ? "joined" : "probe",
|
|
159
170
|
hostStallMs,
|
|
160
171
|
budgetMs: TOOL_PROBE_TIMEOUT_MS,
|
|
161
172
|
});
|
|
@@ -188,7 +199,7 @@ export async function checkToolAvailability(command, facts) {
|
|
|
188
199
|
hostStallMs,
|
|
189
200
|
...(retryAfterMs !== undefined && { retryAfterMs }),
|
|
190
201
|
budgetMs: TOOL_PROBE_TIMEOUT_MS,
|
|
191
|
-
classifiedBy: "probe",
|
|
202
|
+
classifiedBy: probeJoined ? "joined" : "probe",
|
|
192
203
|
evidence,
|
|
193
204
|
});
|
|
194
205
|
return false;
|
|
@@ -419,13 +430,19 @@ function buildCoverageNotice(ctx, runnerLatencies) {
|
|
|
419
430
|
// The marker describes this exact silent-scanner set. A scanner can
|
|
420
431
|
// recover while another goes dark on the same file, so the set belongs
|
|
421
432
|
// in the session dedupe identity rather than only kind and path.
|
|
433
|
+
// #2016: these are SCANNER IDS, not filesystem paths. `normalizeMapKey`
|
|
434
|
+
// would realpath each one; on Windows that fails, falls through to
|
|
435
|
+
// `resolveNonExisting`, and resolves the id against the CURRENT process
|
|
436
|
+
// cwd, so the dedupe key differed by platform and by cwd (the #2219
|
|
437
|
+
// non-path-sentinel class). The cheap syntactic fold is what this
|
|
438
|
+
// session-scoped dedupe key actually needs.
|
|
422
439
|
const silentScannerSet = [...new Set(unconfirmedServerIds)]
|
|
423
|
-
.map(
|
|
440
|
+
.map(normalizeEphemeralMapKey)
|
|
424
441
|
// Code-unit comparator: the sorted set is a dedupe KEY, so ordering
|
|
425
442
|
// must be deterministic across locales — localeCompare is not.
|
|
426
443
|
.sort((a, b) => Number(a > b) - Number(a < b))
|
|
427
444
|
.join(",");
|
|
428
|
-
const onceKey = `${ctx.kind}:${
|
|
445
|
+
const onceKey = `${ctx.kind}:${ctx.filePath}:${silentScannerSet}`;
|
|
429
446
|
if (coverageNoticeSeen.has(onceKey))
|
|
430
447
|
return undefined;
|
|
431
448
|
coverageNoticeSeen.add(onceKey);
|
|
@@ -467,7 +484,7 @@ function buildCoverageNotice(ctx, runnerLatencies) {
|
|
|
467
484
|
(r.status === "succeeded" || r.status === "failed"));
|
|
468
485
|
if (anyLinterHasCoverage)
|
|
469
486
|
return undefined;
|
|
470
|
-
const onceKey = `${ctx.kind}:${
|
|
487
|
+
const onceKey = `${ctx.kind}:${ctx.filePath}`;
|
|
471
488
|
if (coverageNoticeSeen.has(onceKey))
|
|
472
489
|
return undefined;
|
|
473
490
|
coverageNoticeSeen.add(onceKey);
|
|
@@ -603,10 +620,123 @@ async function runGroup(ctx, group, registry, onRunnerResult) {
|
|
|
603
620
|
});
|
|
604
621
|
continue;
|
|
605
622
|
}
|
|
623
|
+
const projectRoot = ctx.projectRoot ?? ctx.cwd;
|
|
624
|
+
// Only post-write dispatches participate. Project scans and direct API
|
|
625
|
+
// callers must retain their existing synchronous semantics.
|
|
626
|
+
const observedTier = ctx.writeIndex === undefined
|
|
627
|
+
? "inline"
|
|
628
|
+
: classifyObservedRunner(projectRoot, runner.id);
|
|
629
|
+
if (observedTier === "collect-later") {
|
|
630
|
+
const markedAtMs = Date.now();
|
|
631
|
+
const deferred = runRunner(ctx, runner, semantic).then((result) => {
|
|
632
|
+
const durationMs = Date.now() - markedAtMs;
|
|
633
|
+
const tier = observeRunnerLatency({
|
|
634
|
+
projectRoot,
|
|
635
|
+
runnerId: runner.id,
|
|
636
|
+
durationMs,
|
|
637
|
+
timedOut: result.failureKind === "timeout",
|
|
638
|
+
});
|
|
639
|
+
if (tier !== observedTier) {
|
|
640
|
+
logLatency({
|
|
641
|
+
type: "phase",
|
|
642
|
+
filePath: ctx.filePath,
|
|
643
|
+
phase: "runner_collect_later_tier_flip",
|
|
644
|
+
durationMs: 0,
|
|
645
|
+
metadata: {
|
|
646
|
+
runnerId: runner.id,
|
|
647
|
+
from: observedTier,
|
|
648
|
+
to: tier,
|
|
649
|
+
projectRoot,
|
|
650
|
+
},
|
|
651
|
+
});
|
|
652
|
+
}
|
|
653
|
+
if (tier === "collect-later") {
|
|
654
|
+
incrementDegradationCount({
|
|
655
|
+
kind: "runner-collect-later",
|
|
656
|
+
subject: `${projectRoot}:${runner.id}`,
|
|
657
|
+
reason: `observed ${durationMs}ms, threshold ${COLLECT_LATER_THRESHOLD_MS}ms`,
|
|
658
|
+
});
|
|
659
|
+
}
|
|
660
|
+
logLatency({
|
|
661
|
+
type: "runner",
|
|
662
|
+
filePath: ctx.filePath,
|
|
663
|
+
runnerId: runner.id,
|
|
664
|
+
startedAt: new Date(markedAtMs).toISOString(),
|
|
665
|
+
durationMs,
|
|
666
|
+
status: result.status,
|
|
667
|
+
diagnosticCount: result.diagnostics.length,
|
|
668
|
+
semantic: result.semantic ?? semantic,
|
|
669
|
+
metadata: { tier: "collect-later", delivered: "turn_end" },
|
|
670
|
+
});
|
|
671
|
+
return result;
|
|
672
|
+
});
|
|
673
|
+
deferRunnerFindings({
|
|
674
|
+
filePath: ctx.filePath,
|
|
675
|
+
cwd: ctx.cwd,
|
|
676
|
+
projectRoot,
|
|
677
|
+
runnerId: runner.id,
|
|
678
|
+
markedAtMs,
|
|
679
|
+
writeIndex: ctx.writeIndex,
|
|
680
|
+
promise: deferred,
|
|
681
|
+
});
|
|
682
|
+
// A deferred runner is still an observed runner. Keep it visible in
|
|
683
|
+
// both the edit latency report and the widget until its turn-end result
|
|
684
|
+
// replaces this pending state (#2122 F1).
|
|
685
|
+
latencies.push({
|
|
686
|
+
runnerId: runner.id,
|
|
687
|
+
startTime: runnerStart,
|
|
688
|
+
endTime: runnerStart,
|
|
689
|
+
durationMs: 0,
|
|
690
|
+
status: "pending",
|
|
691
|
+
diagnosticCount: 0,
|
|
692
|
+
semantic: semantic,
|
|
693
|
+
});
|
|
694
|
+
recordRunner(ctx.filePath, runner.id, "pending", 0, 0, ctx.writeIndex);
|
|
695
|
+
logLatency({
|
|
696
|
+
type: "runner",
|
|
697
|
+
filePath: ctx.filePath,
|
|
698
|
+
runnerId: runner.id,
|
|
699
|
+
durationMs: 0,
|
|
700
|
+
status: "pending",
|
|
701
|
+
diagnosticCount: 0,
|
|
702
|
+
semantic,
|
|
703
|
+
metadata: { tier: "collect-later", delivered: "turn_end" },
|
|
704
|
+
});
|
|
705
|
+
continue;
|
|
706
|
+
}
|
|
606
707
|
const result = await runRunner(ctx, runner, semantic);
|
|
607
708
|
onRunnerResult?.(runnerId, result);
|
|
608
709
|
const runnerEnd = Date.now();
|
|
609
710
|
const duration = runnerEnd - runnerStart;
|
|
711
|
+
const tier = ctx.writeIndex === undefined
|
|
712
|
+
? "inline"
|
|
713
|
+
: observeRunnerLatency({
|
|
714
|
+
projectRoot,
|
|
715
|
+
runnerId: runner.id,
|
|
716
|
+
durationMs: duration,
|
|
717
|
+
timedOut: result.failureKind === "timeout",
|
|
718
|
+
});
|
|
719
|
+
if (tier !== observedTier) {
|
|
720
|
+
logLatency({
|
|
721
|
+
type: "phase",
|
|
722
|
+
filePath: ctx.filePath,
|
|
723
|
+
phase: "runner_collect_later_tier_flip",
|
|
724
|
+
durationMs: 0,
|
|
725
|
+
metadata: {
|
|
726
|
+
runnerId: runner.id,
|
|
727
|
+
from: observedTier,
|
|
728
|
+
to: tier,
|
|
729
|
+
projectRoot,
|
|
730
|
+
},
|
|
731
|
+
});
|
|
732
|
+
}
|
|
733
|
+
if (tier === "collect-later") {
|
|
734
|
+
incrementDegradationCount({
|
|
735
|
+
kind: "runner-collect-later",
|
|
736
|
+
subject: `${projectRoot}:${runner.id}`,
|
|
737
|
+
reason: `observed ${duration}ms, threshold ${COLLECT_LATER_THRESHOLD_MS}ms`,
|
|
738
|
+
});
|
|
739
|
+
}
|
|
610
740
|
// Runner definitions are a typed API, but embedders/plugins can still
|
|
611
741
|
// return untyped objects at runtime. Admit only the closed taxonomy and
|
|
612
742
|
// only on actual skips so free text cannot enter durable latency metadata.
|
|
@@ -707,8 +837,13 @@ export async function dispatchForFile(ctx, groups, registry, onRunnerResult) {
|
|
|
707
837
|
const groupResults = await Promise.all(groups.map((group) => runGroup(ctx, group, registry, onRunnerResult)));
|
|
708
838
|
// Count baseline warnings before filtering (for delta count display)
|
|
709
839
|
const relativeKey = path.relative(ctx.cwd, ctx.filePath).replace(/\\/g, "/");
|
|
710
|
-
const baselineAbsKey = `session.baseline.${
|
|
711
|
-
|
|
840
|
+
const baselineAbsKey = `session.baseline.${ctx.filePath}`;
|
|
841
|
+
// #2016: `relativeKey` is relative to `ctx.cwd`. `normalizeMapKey` resolved
|
|
842
|
+
// it against the process cwd instead, so on Windows this "relative" key
|
|
843
|
+
// became an absolute path anchored on the wrong root while POSIX left it
|
|
844
|
+
// relative. Both the read here and the write below use this const, so the
|
|
845
|
+
// key stays self-consistent within a session.
|
|
846
|
+
const baselineRelKey = `session.baseline.${normalizeEphemeralMapKey(relativeKey)}`;
|
|
712
847
|
const previousBaseline = ctx.deltaMode
|
|
713
848
|
? (ctx.facts.getSessionFact(baselineAbsKey) ??
|
|
714
849
|
ctx.facts.getSessionFact(baselineRelKey))
|
|
@@ -819,6 +954,12 @@ export async function dispatchForFile(ctx, groups, registry, onRunnerResult) {
|
|
|
819
954
|
output += formatDiagnostics([coverageNotice], "warning", 1);
|
|
820
955
|
warnings.push(coverageNotice);
|
|
821
956
|
}
|
|
957
|
+
const pendingRunners = runnerLatencies
|
|
958
|
+
.filter((runner) => runner.status === "pending")
|
|
959
|
+
.map((runner) => runner.runnerId);
|
|
960
|
+
if (pendingRunners.length > 0) {
|
|
961
|
+
output += `\n⏳ Pending runners (reported at turn end): ${pendingRunners.join(", ")}\n`;
|
|
962
|
+
}
|
|
822
963
|
// Generate and store latency report
|
|
823
964
|
const overallEnd = Date.now();
|
|
824
965
|
const latencyReport = {
|