pi-lens 4.1.0 → 4.1.2
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/CHANGELOG.md +185 -0
- package/README.md +106 -0
- package/dist/clients/actionable-warnings-logger.js +2 -1
- package/dist/clients/actionable-warnings.js +14 -5
- package/dist/clients/advisory-provenance.js +48 -18
- package/dist/clients/ast-grep-client.js +1 -3
- package/dist/clients/atomic-write-staging.js +16 -4
- package/dist/clients/bash-file-access.js +99 -6
- package/dist/clients/biome-client.js +1 -3
- package/dist/clients/blocker-freshness.js +21 -8
- package/dist/clients/blocker-past-eof.js +16 -6
- package/dist/clients/bounded-cache.js +24 -8
- package/dist/clients/bounded-pid-file-lock.js +4 -3
- package/dist/clients/bounded-telemetry.js +14 -0
- package/dist/clients/cache-manager.js +6 -7
- package/dist/clients/cache-observability.js +782 -110
- package/dist/clients/call-graph.js +238 -63
- package/dist/clients/codebase-model.js +13 -5
- package/dist/clients/complexity-client.js +1 -3
- package/dist/clients/dead-code-client.js +19 -8
- package/dist/clients/dead-code-logger.js +1 -2
- package/dist/clients/deadline-utils.js +3 -1
- package/dist/clients/debug-heap.js +8 -1
- package/dist/clients/degradation-ledger.js +22 -1
- package/dist/clients/demoted-finding-render.js +155 -0
- package/dist/clients/dependency-checker.js +11 -7
- package/dist/clients/deps/pi-tui.js +8 -2
- package/dist/clients/deps/typebox.js +8 -3
- package/dist/clients/diagnostic-dispositions.js +39 -2
- package/dist/clients/diagnostic-line-freshness.js +5 -1
- package/dist/clients/dispatch/auxiliary-lsp.js +2 -3
- package/dist/clients/dispatch/dispatcher.js +13 -1
- package/dist/clients/dispatch/facts/function-facts.js +3 -1
- package/dist/clients/dispatch/facts/import-facts.js +2 -1
- package/dist/clients/dispatch/facts/tree-sitter-facts.js +1 -1
- package/dist/clients/dispatch/integration.js +9 -4
- package/dist/clients/dispatch/rules/error-swallowing.js +3 -1
- package/dist/clients/dispatch/rules/pass-through-wrappers.js +6 -2
- package/dist/clients/dispatch/rules/unsafe-boundary.js +31 -7
- package/dist/clients/dispatch/runners/actionlint.js +5 -2
- package/dist/clients/dispatch/runners/biome-check.js +10 -14
- package/dist/clients/dispatch/runners/cpp-check.js +5 -1
- package/dist/clients/dispatch/runners/detekt.js +9 -6
- package/dist/clients/dispatch/runners/dotnet-build.js +1 -1
- package/dist/clients/dispatch/runners/elixir-check.js +3 -1
- package/dist/clients/dispatch/runners/eslint.js +25 -6
- package/dist/clients/dispatch/runners/fish-indent.js +1 -1
- package/dist/clients/dispatch/runners/gleam-check.js +1 -1
- package/dist/clients/dispatch/runners/golangci-lint.js +3 -1
- package/dist/clients/dispatch/runners/hadolint.js +22 -15
- package/dist/clients/dispatch/runners/helm-lint.js +4 -1
- package/dist/clients/dispatch/runners/htmlhint.js +33 -16
- package/dist/clients/dispatch/runners/javac.js +1 -1
- package/dist/clients/dispatch/runners/ktlint.js +1 -1
- package/dist/clients/dispatch/runners/lsp.js +1 -1
- package/dist/clients/dispatch/runners/markdownlint.js +34 -12
- package/dist/clients/dispatch/runners/mypy.js +15 -17
- package/dist/clients/dispatch/runners/oxlint.js +231 -15
- package/dist/clients/dispatch/runners/php-lint.js +1 -1
- package/dist/clients/dispatch/runners/phpstan.js +52 -15
- package/dist/clients/dispatch/runners/psscriptanalyzer.js +10 -6
- package/dist/clients/dispatch/runners/pyright.js +23 -14
- package/dist/clients/dispatch/runners/rust-clippy.js +1 -1
- package/dist/clients/dispatch/runners/shellcheck.js +7 -9
- package/dist/clients/dispatch/runners/shfmt.js +5 -2
- package/dist/clients/dispatch/runners/spellcheck.js +10 -15
- package/dist/clients/dispatch/runners/sqlfluff.js +25 -17
- package/dist/clients/dispatch/runners/stylelint.js +40 -20
- package/dist/clients/dispatch/runners/swiftlint.js +8 -8
- package/dist/clients/dispatch/runners/taplo.js +110 -23
- package/dist/clients/dispatch/runners/terragrunt.js +13 -8
- package/dist/clients/dispatch/runners/tflint.js +25 -15
- package/dist/clients/dispatch/runners/tree-sitter.js +17 -3
- package/dist/clients/dispatch/runners/utils/candidate-probe.js +7 -1
- package/dist/clients/dispatch/runners/utils/runner-helpers.js +24 -1
- package/dist/clients/dispatch/runners/utils/tool-failure.js +152 -1
- package/dist/clients/dispatch/runners/vale.js +16 -14
- package/dist/clients/dispatch/runners/yaml-rule-parser.js +1 -3
- package/dist/clients/dispatch/runners/yamllint.js +13 -16
- package/dist/clients/dispatch/runners/zig-check.js +1 -1
- package/dist/clients/dispatch/suppress-writer.js +2 -1
- package/dist/clients/dispatch/types.js +6 -1
- package/dist/clients/dispatch/utils/lsp-diagnostics.js +6 -2
- package/dist/clients/extension-log.js +5 -3
- package/dist/clients/feature-hints.js +8 -1
- package/dist/clients/file-kinds.js +34 -129
- package/dist/clients/file-utils.js +17 -1
- package/dist/clients/finding-delivery-gate.js +79 -2
- package/dist/clients/formatters.js +144 -27
- package/dist/clients/freshness.js +38 -0
- package/dist/clients/git-guard.js +43 -13
- package/dist/clients/go-client.js +1 -3
- package/dist/clients/grammar-source.js +1 -1
- package/dist/clients/gzip-stage-write.js +30 -4
- package/dist/clients/host-edit-normalize.js +1 -3
- package/dist/clients/install-diagnostics.js +4 -2
- package/dist/clients/installer/index.js +86 -77
- package/dist/clients/instance-reaper.js +5 -2
- package/dist/clients/instance-registry.js +4 -2
- package/dist/clients/jscpd-client.js +11 -3
- package/dist/clients/knip-client.js +29 -15
- package/dist/clients/language-profile.js +11 -3
- package/dist/clients/latency-logger.js +13 -2
- package/dist/clients/lens-config.js +1 -1
- package/dist/clients/lens-map.js +8 -3
- package/dist/clients/live-bus-emitter.js +1 -1
- package/dist/clients/lsp/cascade-tier.js +27 -2
- package/dist/clients/lsp/client.js +144 -5
- package/dist/clients/lsp/config.js +4 -6
- package/dist/clients/lsp/edits.js +119 -32
- package/dist/clients/lsp/index.js +245 -41
- package/dist/clients/lsp/inferred-project.js +2 -1
- package/dist/clients/lsp/jvm-runtime.js +4 -1
- package/dist/clients/lsp/launch.js +11 -3
- package/dist/clients/lsp/pending-aux-coverage.js +152 -0
- package/dist/clients/lsp/server.js +19 -7
- package/dist/clients/lsp/tsserver-sync.js +10 -2
- package/dist/clients/lsp/workspace-diagnostics-cache.js +9 -4
- package/dist/clients/lsp-document-symbols.js +1 -3
- package/dist/clients/lsp-mutation.js +10 -20
- package/dist/clients/mcp/host-shim.js +1 -1
- package/dist/clients/memory-sampler.js +73 -7
- package/dist/clients/metrics-client.js +1 -3
- package/dist/clients/middle-man-analysis.js +8 -3
- package/dist/clients/module-report-lsp.js +7 -1
- package/dist/clients/module-report.js +28 -18
- package/dist/clients/ndjson-logger.js +117 -28
- package/dist/clients/opaque-mutation-scan.js +220 -0
- package/dist/clients/package-manager.js +6 -1
- package/dist/clients/pipeline.js +57 -9
- package/dist/clients/project-conventions.js +2 -8
- package/dist/clients/project-diagnostics/cache.js +10 -7
- package/dist/clients/project-diagnostics/runner-adapters/call-graph-impact.js +2 -1
- package/dist/clients/project-diagnostics/runner-adapters/gitleaks.js +3 -1
- package/dist/clients/project-diagnostics/runner-adapters/runner-findings.js +13 -9
- package/dist/clients/project-diagnostics/runner-adapters/trivy.js +3 -1
- package/dist/clients/project-diagnostics/scanner.js +22 -10
- package/dist/clients/project-lens-config.js +12 -5
- package/dist/clients/project-report.js +62 -27
- package/dist/clients/project-snapshot-fingerprint.js +48 -0
- package/dist/clients/project-snapshot-persist-worker.js +15 -1
- package/dist/clients/project-snapshot.js +472 -66
- package/dist/clients/read-expansion.js +12 -2
- package/dist/clients/read-guard-logger.js +12 -6
- package/dist/clients/read-guard-tool-lines.js +5 -2
- package/dist/clients/read-guard.js +88 -19
- package/dist/clients/resource-sampler.js +1 -1
- package/dist/clients/reverse-deps.js +6 -5
- package/dist/clients/review-graph/builder.js +236 -48
- package/dist/clients/review-graph/git-identity.js +8 -2
- package/dist/clients/review-graph/import-resolvers.js +10 -1
- package/dist/clients/review-graph/query.js +2 -1
- package/dist/clients/review-graph/revision-drift.js +21 -0
- package/dist/clients/review-graph/tsconfig-paths.js +4 -2
- package/dist/clients/review-graph/workspace-modules.js +8 -5
- package/dist/clients/ruff-client.js +6 -5
- package/dist/clients/runtime-agent-end.js +63 -31
- package/dist/clients/runtime-context.js +12 -4
- package/dist/clients/runtime-coordinator.js +127 -4
- package/dist/clients/runtime-session.js +55 -0
- package/dist/clients/runtime-tool-call.js +59 -3
- package/dist/clients/runtime-tool-result.js +148 -34
- package/dist/clients/runtime-turn.js +194 -8
- package/dist/clients/rust-client.js +1 -3
- package/dist/clients/safe-spawn.js +150 -11
- package/dist/clients/security-scan-client.js +1 -3
- package/dist/clients/session-event-guard.js +184 -0
- package/dist/clients/session-summary.js +3 -1
- package/dist/clients/sg-runner.js +1 -3
- package/dist/clients/source-filter.js +16 -8
- package/dist/clients/source-walker.js +1 -1
- package/dist/clients/spawn-timeout-cooldown.js +96 -0
- package/dist/clients/startup-scan.js +9 -3
- package/dist/clients/tool-definition.js +7 -0
- package/dist/clients/tool-policy.js +26 -11
- package/dist/clients/tool-render.js +2 -1
- package/dist/clients/tree-sitter-client.js +10 -4
- package/dist/clients/tree-sitter-logger.js +2 -1
- package/dist/clients/tree-sitter-symbol-extractor.js +12 -5
- package/dist/clients/warm-attach.js +12 -5
- package/dist/clients/widget-state.js +27 -11
- package/dist/clients/word-index.js +3 -1
- package/dist/clients/workspace-topology.js +8 -2
- package/dist/clients/zizmor-config.js +9 -1
- package/dist/i18n.js +13 -5
- package/dist/index.js +15811 -12810
- package/dist/mcp/server.js +3 -1
- package/dist/tools/ast-grep-replace.js +68 -12
- package/dist/tools/lens-diagnostic-mark.js +21 -7
- package/dist/tools/lens-diagnostics.js +174 -38
- package/dist/tools/lsp-diagnostics.js +24 -7
- package/dist/tools/lsp-navigation.js +3 -1
- package/dist/tools/module-report.js +1 -1
- package/dist/tools/project-report.js +1 -1
- package/dist/tools/symbol-search.js +5 -2
- package/docs/durable-store-audit-1202.md +1 -1
- package/docs/servercapabilities.md +1 -1
- package/package.json +160 -143
- package/rules/typos/_typos.toml +2 -2
- package/scripts/analyze-pi-lens-logs.mjs +31 -13
- package/scripts/download-grammars.js +9 -3
- package/scripts/install-selftest.mjs +99 -11
- package/scripts/lib/host-provided-deps.mjs +75 -0
- package/scripts/lib/warm-loader-cache.mjs +285 -0
- package/scripts/rpc-load-check.mjs +30 -9
- package/scripts/warm-loader-cache.mjs +233 -0
|
@@ -45,10 +45,10 @@ export function tokenizeShellCommand(command) {
|
|
|
45
45
|
word = "";
|
|
46
46
|
atTokenStart = true;
|
|
47
47
|
};
|
|
48
|
-
const flushSegment = () => {
|
|
48
|
+
const flushSegment = (terminator) => {
|
|
49
49
|
flushWord();
|
|
50
50
|
if (tokens.length > 0 || unsupported)
|
|
51
|
-
segments.push({ tokens, unsupported });
|
|
51
|
+
segments.push({ tokens, unsupported, terminator });
|
|
52
52
|
tokens = [];
|
|
53
53
|
unsupported = false;
|
|
54
54
|
atTokenStart = true;
|
|
@@ -122,13 +122,18 @@ export function tokenizeShellCommand(command) {
|
|
|
122
122
|
}
|
|
123
123
|
if (ch === ";" || ch === "\n" || ch === "|" || ch === "&") {
|
|
124
124
|
flushWord();
|
|
125
|
+
let terminator;
|
|
125
126
|
if (ch === "|" && next === "|")
|
|
126
127
|
i++;
|
|
127
128
|
else if (ch === "&" && next === "&")
|
|
128
129
|
i++;
|
|
129
|
-
else if (ch === "|"
|
|
130
|
+
else if (ch === "|") {
|
|
130
131
|
unsupported = true;
|
|
131
|
-
|
|
132
|
+
terminator = "pipe";
|
|
133
|
+
}
|
|
134
|
+
else if (ch === "&")
|
|
135
|
+
unsupported = true;
|
|
136
|
+
flushSegment(terminator);
|
|
132
137
|
continue;
|
|
133
138
|
}
|
|
134
139
|
if (ch === "<" || ch === ">") {
|
|
@@ -514,6 +519,83 @@ function parseGrepLineWithoutFile(line, file) {
|
|
|
514
519
|
return undefined;
|
|
515
520
|
return { file, startLine: lineNumber, endLine: lineNumber };
|
|
516
521
|
}
|
|
522
|
+
/**
|
|
523
|
+
* Verbs that DROP printed lines from a piped stream (#1908, review F4): a
|
|
524
|
+
* hit that survives such a tail cannot be trusted to still carry the
|
|
525
|
+
* -A/-B/-C context grep's flags declared, because that context may have
|
|
526
|
+
* been among the dropped lines. `head`/`tail` drop by position (even bare,
|
|
527
|
+
* since both default to a 10-line window); `sed` with a `q` command drops
|
|
528
|
+
* everything after the addressed line; `uniq` drops adjacent duplicate
|
|
529
|
+
* lines, which can include a context line that happens to repeat.
|
|
530
|
+
*
|
|
531
|
+
* The criterion is DROPPING, not "changes grep's output shape" — a review
|
|
532
|
+
* finding (#1913 F4) caught this file previously conflating the two:
|
|
533
|
+
* - `head`/`tail`/`sed q`/`uniq` downstream of a matching grep → some
|
|
534
|
+
* printed lines are genuinely gone, so credit falls back to
|
|
535
|
+
* match-line-only (the same conservative default already used for
|
|
536
|
+
* unparseable commands) rather than trusting the declared flags.
|
|
537
|
+
* - `sort` and other REORDER-ONLY filters keep every line grep printed,
|
|
538
|
+
* just in a different order — a surviving `file:line:` match still
|
|
539
|
+
* carries the full context grep actually printed, so no special case is
|
|
540
|
+
* needed (proven by the "still credits full context through a
|
|
541
|
+
* non-truncating pipe tail" test below).
|
|
542
|
+
* - `wc` and similar filters that REPLACE grep's output entirely (with a
|
|
543
|
+
* count, say) leave no `file:line:`-shaped line at all, so
|
|
544
|
+
* `parseGrepOutputSearchReads` already finds zero matches in the real
|
|
545
|
+
* captured stdout. Also no special case needed, but for the OPPOSITE
|
|
546
|
+
* reason from `sort`: nothing survives to credit, rather than
|
|
547
|
+
* everything surviving intact.
|
|
548
|
+
* - A pass-through filter (`cat`, `grep -v`, ...) between grep and a
|
|
549
|
+
* dropping tail still counts: the walk below follows the whole pipe
|
|
550
|
+
* chain, not just the immediate next segment.
|
|
551
|
+
* - Out of scope, deliberately: `sed -n 'Np'` range-address filtering
|
|
552
|
+
* without `q`, and `sort -u`, also drop lines but aren't in #1908's
|
|
553
|
+
* stated scope — revisit if they recur in the field. Also out of
|
|
554
|
+
* scope: a truncating tail hidden inside a subshell/brace-group
|
|
555
|
+
* (`( ... )`, `{ ...; }`) or reimplemented in `awk`/`perl` — the shared
|
|
556
|
+
* tokenizer this file uses doesn't parse inside those, matching its
|
|
557
|
+
* documented "small conservative shell lexer" scope.
|
|
558
|
+
*/
|
|
559
|
+
const LINE_DROPPING_TAIL_VERBS = new Set(["head", "tail", "uniq"]);
|
|
560
|
+
function isSedQuitCommand(args) {
|
|
561
|
+
for (const arg of args) {
|
|
562
|
+
const token = stripQuotes(arg);
|
|
563
|
+
if (token.startsWith("-"))
|
|
564
|
+
continue;
|
|
565
|
+
// A `q` command anchored at the start of the script or after a `;`/
|
|
566
|
+
// newline separator, optionally preceded by a numeric/`$` address
|
|
567
|
+
// (`q`, `1q`, `$q`, `1,3p;q`). Deliberately not a full sed parser.
|
|
568
|
+
if (/(^|[;\n])\s*(\d+|\$)?\s*q\b/.test(token))
|
|
569
|
+
return true;
|
|
570
|
+
}
|
|
571
|
+
return false;
|
|
572
|
+
}
|
|
573
|
+
function isLineDroppingTailCommand(tokens) {
|
|
574
|
+
const verb = path.basename(stripQuotes(tokens[0] ?? ""));
|
|
575
|
+
if (LINE_DROPPING_TAIL_VERBS.has(verb))
|
|
576
|
+
return true;
|
|
577
|
+
if (verb === "sed")
|
|
578
|
+
return isSedQuitCommand(tokens.slice(1));
|
|
579
|
+
return false;
|
|
580
|
+
}
|
|
581
|
+
/**
|
|
582
|
+
* True when the grep segment starting at `index` feeds — directly or through
|
|
583
|
+
* a chain of pipes — into a line-dropping tail (#1908). Only an unquoted `|`
|
|
584
|
+
* counts: a `;`/`&&`/`&`-separated command downstream never receives grep's
|
|
585
|
+
* stdout, so it must not downgrade credit (#1913 F2 — this was previously
|
|
586
|
+
* unverified by any test that could tell a real pipe from mere adjacency).
|
|
587
|
+
*/
|
|
588
|
+
function grepPipesIntoTruncatingTail(segments, index) {
|
|
589
|
+
if (segments[index]?.terminator !== "pipe")
|
|
590
|
+
return false;
|
|
591
|
+
for (let j = index + 1; j < segments.length; j++) {
|
|
592
|
+
if (isLineDroppingTailCommand(segments[j].tokens))
|
|
593
|
+
return true;
|
|
594
|
+
if (segments[j].terminator !== "pipe")
|
|
595
|
+
return false;
|
|
596
|
+
}
|
|
597
|
+
return false;
|
|
598
|
+
}
|
|
517
599
|
function collectGrepCommandFiles(command, cwd) {
|
|
518
600
|
const files = new Set();
|
|
519
601
|
let hasLineNumberGrep = false;
|
|
@@ -522,7 +604,14 @@ function collectGrepCommandFiles(command, cwd) {
|
|
|
522
604
|
// contributing grep printed, so no line is credited that a hit from the
|
|
523
605
|
// narrowest grep never delivered.
|
|
524
606
|
let context;
|
|
525
|
-
|
|
607
|
+
// #1908: if ANY contributing grep pipes into a truncating tail, fall back
|
|
608
|
+
// to match-line-only credit for the whole command — the parsed hits carry
|
|
609
|
+
// no segment identity, so a per-segment override isn't representable in
|
|
610
|
+
// the aggregate `context` this function returns.
|
|
611
|
+
let truncated = false;
|
|
612
|
+
const segments = tokenizeShellCommand(command);
|
|
613
|
+
for (let i = 0; i < segments.length; i++) {
|
|
614
|
+
const tokens = segments[i].tokens;
|
|
526
615
|
const verb = path.basename(stripQuotes(tokens[0] ?? ""));
|
|
527
616
|
if (verb !== "grep" && verb !== "egrep" && verb !== "fgrep")
|
|
528
617
|
continue;
|
|
@@ -539,11 +628,15 @@ function collectGrepCommandFiles(command, cwd) {
|
|
|
539
628
|
: segmentContext;
|
|
540
629
|
for (const file of extractGrepSearchFiles(args, cwd))
|
|
541
630
|
files.add(file);
|
|
631
|
+
if (grepPipesIntoTruncatingTail(segments, i))
|
|
632
|
+
truncated = true;
|
|
542
633
|
}
|
|
543
634
|
return {
|
|
544
635
|
hasLineNumberGrep,
|
|
545
636
|
files,
|
|
546
|
-
context:
|
|
637
|
+
context: truncated
|
|
638
|
+
? { before: 0, after: 0 }
|
|
639
|
+
: (context ?? { before: 0, after: 0 }),
|
|
547
640
|
};
|
|
548
641
|
}
|
|
549
642
|
function dedupePushSearchRead(out, seen, loc) {
|
|
@@ -58,9 +58,7 @@ export class BiomeClient {
|
|
|
58
58
|
ensureFlight = createSingleFlight();
|
|
59
59
|
log;
|
|
60
60
|
constructor(verbose = false) {
|
|
61
|
-
this.log = verbose
|
|
62
|
-
? createSubsystemLogger("biome")
|
|
63
|
-
: () => { };
|
|
61
|
+
this.log = verbose ? createSubsystemLogger("biome") : () => { };
|
|
64
62
|
}
|
|
65
63
|
/**
|
|
66
64
|
* Resolve the fastest available biome binary for `cwd`.
|
|
@@ -126,7 +126,11 @@ async function resolveForwardImportsMemoized(cwd, filePath, resolveForwardImport
|
|
|
126
126
|
catch {
|
|
127
127
|
imports = [];
|
|
128
128
|
}
|
|
129
|
-
importResolutionMemo.set(filePath, {
|
|
129
|
+
importResolutionMemo.set(filePath, {
|
|
130
|
+
mtimeMs: sig.mtimeMs,
|
|
131
|
+
size: sig.size,
|
|
132
|
+
imports,
|
|
133
|
+
});
|
|
130
134
|
return imports;
|
|
131
135
|
}
|
|
132
136
|
/** Test-only: clear the extractor and import-resolution memos. */
|
|
@@ -232,17 +236,26 @@ export async function collectForwardImportMtimes(cwd, filePath, resolveForwardIm
|
|
|
232
236
|
* +50ms). +50ms comfortably clears the measured skew while staying far below the
|
|
233
237
|
* gap between genuinely distinct edits.
|
|
234
238
|
*/
|
|
235
|
-
|
|
239
|
+
// Single implementation lives in the freshness kernel (#1739); this
|
|
240
|
+
// re-export preserves every existing importer's path.
|
|
241
|
+
export { MTIME_DRIFT_TOLERANCE_MS } from "./freshness.js";
|
|
242
|
+
import { freshnessFromMtime } from "./freshness.js";
|
|
236
243
|
async function detectDrift(cwd, filePath, recordedAtMs, resolveForwardImports, turnIndex) {
|
|
237
244
|
const drifted = [];
|
|
238
|
-
const
|
|
239
|
-
|
|
245
|
+
const ownFreshness = freshnessFromMtime({
|
|
246
|
+
mtimeMs: await statMtimeMs(filePath),
|
|
247
|
+
referenceMs: recordedAtMs,
|
|
248
|
+
});
|
|
249
|
+
if (ownFreshness.verdict === "stale")
|
|
240
250
|
drifted.push(filePath);
|
|
241
|
-
}
|
|
242
251
|
const { mtimes, truncated } = await collectForwardImportMtimes(cwd, filePath, resolveForwardImports, turnIndex);
|
|
243
|
-
for (const { path, mtimeMs } of mtimes) {
|
|
244
|
-
|
|
245
|
-
|
|
252
|
+
for (const { path: depPath, mtimeMs } of mtimes) {
|
|
253
|
+
const verdict = freshnessFromMtime({
|
|
254
|
+
mtimeMs,
|
|
255
|
+
referenceMs: recordedAtMs,
|
|
256
|
+
});
|
|
257
|
+
if (verdict.verdict === "stale")
|
|
258
|
+
drifted.push(depPath);
|
|
246
259
|
}
|
|
247
260
|
return { drifted, truncated };
|
|
248
261
|
}
|
|
@@ -56,7 +56,7 @@ export function sweepInlineBlockerPastEof(runtime, cwd, options) {
|
|
|
56
56
|
total: 0,
|
|
57
57
|
checked: 0,
|
|
58
58
|
demoted: 0,
|
|
59
|
-
|
|
59
|
+
deadLinesByPath: new Map(),
|
|
60
60
|
};
|
|
61
61
|
const resync = options?.resync ?? resyncDocumentOnPastEof;
|
|
62
62
|
let entries;
|
|
@@ -101,15 +101,25 @@ export function sweepInlineBlockerPastEof(runtime, cwd, options) {
|
|
|
101
101
|
// The record cites any of its lines being past EOF as a whole-record
|
|
102
102
|
// demotion — one summary string, not one flag per line.
|
|
103
103
|
const isPastEof = diagnostics.some((d) => d.stale);
|
|
104
|
+
if (isPastEof) {
|
|
105
|
+
// #1944: record WHICH cited lines died, before the transition check
|
|
106
|
+
// below short-circuits. A record demoted on an earlier turn does not
|
|
107
|
+
// transition, yet its dead lines are exactly what the delivery
|
|
108
|
+
// surface must annotate and retire on.
|
|
109
|
+
counts.deadLinesByPath.set(entry.filePath, diagnostics
|
|
110
|
+
.filter((d) => d.stale)
|
|
111
|
+
.map((d) => d.line)
|
|
112
|
+
.filter((line) => typeof line === "number"));
|
|
113
|
+
}
|
|
104
114
|
const transitioned = runtime.setInlineBlockerPastEofStale(entry.filePath, isPastEof);
|
|
105
115
|
if (!transitioned)
|
|
106
116
|
continue;
|
|
107
|
-
|
|
117
|
+
// Only the rising edge is countable here — see `BlockerPastEofCounts`
|
|
118
|
+
// for why the falling edge cannot occur on this store anymore. The
|
|
119
|
+
// call below still un-demotes, so the STORE stays honest even though
|
|
120
|
+
// nothing can observe the transition.
|
|
121
|
+
if (isPastEof)
|
|
108
122
|
counts.demoted += 1;
|
|
109
|
-
}
|
|
110
|
-
else {
|
|
111
|
-
counts.healed += 1;
|
|
112
|
-
}
|
|
113
123
|
}
|
|
114
124
|
catch {
|
|
115
125
|
// Per-entry failure: leave the entry as-is.
|
|
@@ -13,7 +13,9 @@ export class BoundedLruCache {
|
|
|
13
13
|
}
|
|
14
14
|
return value;
|
|
15
15
|
}
|
|
16
|
-
has(key) {
|
|
16
|
+
has(key) {
|
|
17
|
+
return this.entries.has(key);
|
|
18
|
+
}
|
|
17
19
|
set(key, value) {
|
|
18
20
|
this.entries.delete(key);
|
|
19
21
|
this.entries.set(key, value);
|
|
@@ -24,11 +26,25 @@ export class BoundedLruCache {
|
|
|
24
26
|
this.entries.delete(oldest);
|
|
25
27
|
}
|
|
26
28
|
}
|
|
27
|
-
delete(key) {
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
29
|
+
delete(key) {
|
|
30
|
+
return this.entries.delete(key);
|
|
31
|
+
}
|
|
32
|
+
clear() {
|
|
33
|
+
this.entries.clear();
|
|
34
|
+
}
|
|
35
|
+
get size() {
|
|
36
|
+
return this.entries.size;
|
|
37
|
+
}
|
|
38
|
+
keys() {
|
|
39
|
+
return this.entries.keys();
|
|
40
|
+
}
|
|
41
|
+
values() {
|
|
42
|
+
return this.entries.values();
|
|
43
|
+
}
|
|
44
|
+
entriesArray() {
|
|
45
|
+
return [...this.entries.entries()];
|
|
46
|
+
}
|
|
47
|
+
[Symbol.iterator]() {
|
|
48
|
+
return this.entries[Symbol.iterator]();
|
|
49
|
+
}
|
|
34
50
|
}
|
|
@@ -35,8 +35,7 @@ async function releaseQuarantineLock(lockPath, token) {
|
|
|
35
35
|
break;
|
|
36
36
|
}
|
|
37
37
|
catch (error) {
|
|
38
|
-
if (error.code !== "ENOENT" ||
|
|
39
|
-
attempt === 2)
|
|
38
|
+
if (error.code !== "ENOENT" || attempt === 2)
|
|
40
39
|
return;
|
|
41
40
|
await new Promise((resolve) => setImmediate(resolve));
|
|
42
41
|
}
|
|
@@ -111,7 +110,9 @@ async function tryAcquireQuarantineLock(lockPath, staleMs) {
|
|
|
111
110
|
await fsp.writeFile(path.join(lockPath, "owner.json"), JSON.stringify(owner), "utf8");
|
|
112
111
|
}
|
|
113
112
|
catch (error) {
|
|
114
|
-
await fsp
|
|
113
|
+
await fsp
|
|
114
|
+
.rm(lockPath, { recursive: true, force: true })
|
|
115
|
+
.catch(() => { });
|
|
115
116
|
throw error;
|
|
116
117
|
}
|
|
117
118
|
return () => releaseQuarantineLock(lockPath, owner.token);
|
|
@@ -77,12 +77,26 @@ export const BOUNDED_TELEMETRY_PHASES = [
|
|
|
77
77
|
"lsp_client_skipped_broken",
|
|
78
78
|
/** #1743 review: the same skip, for a temporarily unavailable command. */
|
|
79
79
|
"lsp_client_skipped_unavailable_command",
|
|
80
|
+
/**
|
|
81
|
+
* #1934: `getWarmClientForFile` found no live client for a file that has at
|
|
82
|
+
* least one language server with a resolvable root. Its callers run per
|
|
83
|
+
* file (the cascade quiet window, read expansion, `hasWarmLSP`), so it is
|
|
84
|
+
* rising-edge per candidate (server, root) set, with the ledger holding the
|
|
85
|
+
* exact count.
|
|
86
|
+
*/
|
|
87
|
+
"lsp_warm_client_missing",
|
|
80
88
|
/**
|
|
81
89
|
* #1723: an event-loop block at or above the floor. Not a degradation, so
|
|
82
90
|
* no ledger kind; bounded by call cadence (one `turn_end` runs it once per
|
|
83
91
|
* turn) rather than by an option here.
|
|
84
92
|
*/
|
|
85
93
|
"loop_block",
|
|
94
|
+
/**
|
|
95
|
+
* #1925: a session event skipped because its ctx was invalidated by a
|
|
96
|
+
* session replacement or reload. A replaced session can drain a whole
|
|
97
|
+
* queue of them, so it is rising-edge per event name.
|
|
98
|
+
*/
|
|
99
|
+
"session_event_stale_ctx_skip",
|
|
86
100
|
];
|
|
87
101
|
const REGISTERED_PHASES = new Set(BOUNDED_TELEMETRY_PHASES);
|
|
88
102
|
/** Report whether a phase name is in the registry (used by the sweep). */
|
|
@@ -39,9 +39,7 @@ function getTurnStatePath(cwd) {
|
|
|
39
39
|
export class CacheManager {
|
|
40
40
|
log;
|
|
41
41
|
constructor(verbose = false) {
|
|
42
|
-
this.log = verbose
|
|
43
|
-
? createSubsystemLogger("cache")
|
|
44
|
-
: () => { };
|
|
42
|
+
this.log = verbose ? createSubsystemLogger("cache") : () => { };
|
|
45
43
|
}
|
|
46
44
|
/**
|
|
47
45
|
* Convert a file path to a stable turn-state key.
|
|
@@ -226,11 +224,12 @@ export class CacheManager {
|
|
|
226
224
|
// stale-session eviction behavior for this legacy shape.
|
|
227
225
|
return "available";
|
|
228
226
|
}
|
|
229
|
-
if (state.owner.kind === owner.kind &&
|
|
230
|
-
state.owner.id === owner.id) {
|
|
227
|
+
if (state.owner.kind === owner.kind && state.owner.id === owner.id) {
|
|
231
228
|
return "owned";
|
|
232
229
|
}
|
|
233
|
-
return this.isTurnStateOwnerStale(state.owner)
|
|
230
|
+
return this.isTurnStateOwnerStale(state.owner)
|
|
231
|
+
? "available"
|
|
232
|
+
: "foreign-live";
|
|
234
233
|
}
|
|
235
234
|
isTurnStateOwnerStale(owner) {
|
|
236
235
|
if (owner.pid > 0 && owner.pid !== process.pid) {
|
|
@@ -243,7 +242,7 @@ export class CacheManager {
|
|
|
243
242
|
}
|
|
244
243
|
}
|
|
245
244
|
const lastSeen = Date.parse(owner.lastSeen);
|
|
246
|
-
return !Number.isFinite(lastSeen) || Date.now() - lastSeen > TURN_OWNER_STALE_MS;
|
|
245
|
+
return (!Number.isFinite(lastSeen) || Date.now() - lastSeen > TURN_OWNER_STALE_MS);
|
|
247
246
|
}
|
|
248
247
|
/**
|
|
249
248
|
* Add or update a file's modified ranges in turn state.
|