@herbertgao/pi-extensions 2026.9.15 → 2026.9.16
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 +5 -0
- package/THIRD_PARTY_NOTICES.md +26 -0
- package/node_modules/@herbertgao/pi-cc-extensions/package.json +1 -1
- package/node_modules/@narumitw/pi-btw/dist/index.ts +12 -6
- package/node_modules/@narumitw/pi-btw/dist/index.ts.map +3 -3
- package/node_modules/@narumitw/pi-btw/package.json +4 -4
- package/node_modules/@narumitw/pi-btw/src/main-tree-picker.ts +2 -0
- package/node_modules/@narumitw/pi-btw/src/side-thread.ts +16 -4
- package/node_modules/pi-lens/CHANGELOG.md +498 -0
- package/node_modules/pi-lens/README.md +45 -2
- package/node_modules/pi-lens/dist/clients/actionable-warnings.js +23 -7
- package/node_modules/pi-lens/dist/clients/advisory-provenance.js +187 -51
- package/node_modules/pi-lens/dist/clients/cache-manager.js +64 -5
- package/node_modules/pi-lens/dist/clients/child-unref.js +17 -8
- package/node_modules/pi-lens/dist/clients/config-diagnostic-codes.js +2 -0
- package/node_modules/pi-lens/dist/clients/config-locations.js +237 -9
- package/node_modules/pi-lens/dist/clients/config-resolve.js +7 -1
- package/node_modules/pi-lens/dist/clients/degradation-ledger.js +33 -3
- package/node_modules/pi-lens/dist/clients/dependency-checker.js +87 -64
- package/node_modules/pi-lens/dist/clients/diagnostic-dispositions.js +39 -3
- package/node_modules/pi-lens/dist/clients/dispatch/finding-policy.js +118 -1
- package/node_modules/pi-lens/dist/clients/dispatch/indent-detect.js +8 -2
- package/node_modules/pi-lens/dist/clients/dispatch/inline-suppressions.js +23 -4
- package/node_modules/pi-lens/dist/clients/dispatch/rule-ignores.js +14 -7
- package/node_modules/pi-lens/dist/clients/dispatch/runners/actionlint.js +26 -28
- package/node_modules/pi-lens/dist/clients/dispatch/runners/biome-check.js +38 -25
- package/node_modules/pi-lens/dist/clients/dispatch/runners/cpp-check.js +32 -49
- package/node_modules/pi-lens/dist/clients/dispatch/runners/credo.js +16 -14
- package/node_modules/pi-lens/dist/clients/dispatch/runners/cue-vet.js +25 -7
- package/node_modules/pi-lens/dist/clients/dispatch/runners/dart-analyze.js +28 -47
- package/node_modules/pi-lens/dist/clients/dispatch/runners/detekt.js +11 -4
- package/node_modules/pi-lens/dist/clients/dispatch/runners/dotnet-build.js +27 -22
- package/node_modules/pi-lens/dist/clients/dispatch/runners/elixir-check.js +31 -46
- package/node_modules/pi-lens/dist/clients/dispatch/runners/eslint.js +29 -46
- package/node_modules/pi-lens/dist/clients/dispatch/runners/gleam-check.js +91 -11
- package/node_modules/pi-lens/dist/clients/dispatch/runners/go-vet.js +16 -2
- package/node_modules/pi-lens/dist/clients/dispatch/runners/golangci-lint.js +38 -23
- package/node_modules/pi-lens/dist/clients/dispatch/runners/hadolint.js +27 -15
- package/node_modules/pi-lens/dist/clients/dispatch/runners/htmlhint.js +17 -8
- package/node_modules/pi-lens/dist/clients/dispatch/runners/javac.js +31 -21
- package/node_modules/pi-lens/dist/clients/dispatch/runners/ktlint.js +34 -60
- package/node_modules/pi-lens/dist/clients/dispatch/runners/lsp.js +6 -5
- package/node_modules/pi-lens/dist/clients/dispatch/runners/markdownlint.js +12 -5
- package/node_modules/pi-lens/dist/clients/dispatch/runners/mypy.js +2 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/oxlint.js +22 -6
- package/node_modules/pi-lens/dist/clients/dispatch/runners/php-lint.js +39 -16
- package/node_modules/pi-lens/dist/clients/dispatch/runners/phpstan.js +1 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/rubocop.js +27 -18
- package/node_modules/pi-lens/dist/clients/dispatch/runners/ruff.js +18 -19
- package/node_modules/pi-lens/dist/clients/dispatch/runners/rust-clippy.js +6 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/shellcheck.js +6 -2
- package/node_modules/pi-lens/dist/clients/dispatch/runners/spellcheck.js +10 -3
- package/node_modules/pi-lens/dist/clients/dispatch/runners/sqlfluff.js +11 -2
- package/node_modules/pi-lens/dist/clients/dispatch/runners/stylelint.js +10 -2
- package/node_modules/pi-lens/dist/clients/dispatch/runners/swiftlint.js +11 -2
- package/node_modules/pi-lens/dist/clients/dispatch/runners/taplo.js +12 -6
- package/node_modules/pi-lens/dist/clients/dispatch/runners/tflint.js +30 -15
- package/node_modules/pi-lens/dist/clients/dispatch/runners/tree-sitter.js +23 -0
- package/node_modules/pi-lens/dist/clients/dispatch/runners/trivy-config.js +8 -2
- package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/diagnostic-parsers.js +11 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/vale.js +15 -3
- package/node_modules/pi-lens/dist/clients/dispatch/runners/yamllint.js +12 -2
- package/node_modules/pi-lens/dist/clients/dispatch/runners/zig-check.js +27 -39
- package/node_modules/pi-lens/dist/clients/dispatch/suppress-writer.js +13 -3
- package/node_modules/pi-lens/dist/clients/effective-config.js +3 -1
- package/node_modules/pi-lens/dist/clients/finding-delivery-gate.js +42 -11
- package/node_modules/pi-lens/dist/clients/formatters.js +49 -11
- package/node_modules/pi-lens/dist/clients/git-guard.js +247 -34
- package/node_modules/pi-lens/dist/clients/gradle-ktfmt-style.js +121 -25
- package/node_modules/pi-lens/dist/clients/inline-blocker-dispositions.js +186 -0
- package/node_modules/pi-lens/dist/clients/install-diagnostics.js +19 -6
- package/node_modules/pi-lens/dist/clients/installer/index.js +281 -32
- package/node_modules/pi-lens/dist/clients/jscpd-client.js +8 -0
- package/node_modules/pi-lens/dist/clients/latency-logger.js +8 -0
- package/node_modules/pi-lens/dist/clients/lens-config.js +66 -8
- package/node_modules/pi-lens/dist/clients/lsp/client.js +139 -11
- package/node_modules/pi-lens/dist/clients/lsp/config.js +9 -7
- package/node_modules/pi-lens/dist/clients/lsp/content-limits.js +41 -0
- package/node_modules/pi-lens/dist/clients/lsp/index.js +1 -1
- package/node_modules/pi-lens/dist/clients/lsp/launch.js +23 -2
- package/node_modules/pi-lens/dist/clients/lsp/server.js +106 -25
- package/node_modules/pi-lens/dist/clients/lsp/sync-kind.js +14 -0
- package/node_modules/pi-lens/dist/clients/lsp/wait-policy/strategies.js +105 -16
- package/node_modules/pi-lens/dist/clients/mcp/ipc.js +232 -44
- package/node_modules/pi-lens/dist/clients/mcp/review.js +53 -7
- package/node_modules/pi-lens/dist/clients/opengrep-client.js +11 -6
- package/node_modules/pi-lens/dist/clients/pipeline.js +121 -66
- package/node_modules/pi-lens/dist/clients/project-diagnostics/fresh-fetch.js +21 -3
- package/node_modules/pi-lens/dist/clients/project-lens-config.js +11 -3
- package/node_modules/pi-lens/dist/clients/review-graph/builder.js +34 -26
- package/node_modules/pi-lens/dist/clients/review-graph/git-identity.js +16 -10
- package/node_modules/pi-lens/dist/clients/ruff-client.js +31 -4
- package/node_modules/pi-lens/dist/clients/runtime-coordinator.js +38 -4
- package/node_modules/pi-lens/dist/clients/runtime-tool-call.js +16 -5
- package/node_modules/pi-lens/dist/clients/runtime-tool-result.js +27 -12
- package/node_modules/pi-lens/dist/clients/runtime-turn.js +447 -280
- package/node_modules/pi-lens/dist/clients/safe-spawn.js +256 -65
- package/node_modules/pi-lens/dist/clients/spawn-output-cap.js +84 -1
- package/node_modules/pi-lens/dist/clients/tree-sitter-client.js +119 -58
- package/node_modules/pi-lens/dist/clients/turn-end/lane.js +51 -0
- package/node_modules/pi-lens/dist/clients/turn-end/lanes/govulncheck.js +112 -0
- package/node_modules/pi-lens/dist/clients/turn-end/lanes/secrets.js +204 -0
- package/node_modules/pi-lens/dist/clients/warm-attach.js +59 -0
- package/node_modules/pi-lens/dist/clients/widget-state.js +145 -14
- package/node_modules/pi-lens/dist/clients/workspace-topology.js +57 -3
- package/node_modules/pi-lens/dist/clients/zizmor-config.js +26 -13
- package/node_modules/pi-lens/dist/index.js +5695 -3733
- package/node_modules/pi-lens/dist/mcp/analyze-cli.js +4 -0
- package/node_modules/pi-lens/dist/mcp/server.js +33 -21
- package/node_modules/pi-lens/dist/scripts/lib/web-tree-sitter-dir.mjs +123 -0
- package/node_modules/pi-lens/dist/tools/lens-diagnostics.js +43 -16
- package/node_modules/pi-lens/dist/tools/lsp-diagnostics.js +118 -7
- package/node_modules/pi-lens/docs/adr/0001-stale-advisory-live-arm.md +29 -0
- package/node_modules/pi-lens/docs/adr/0002-workspace-hash-rendezvous.md +30 -0
- package/node_modules/pi-lens/docs/adr/0003-git-guard-latch-writer.md +203 -0
- package/node_modules/pi-lens/docs/adr/0004-disposition-policy-seam.md +38 -0
- package/node_modules/pi-lens/docs/adr/0005-tool-availability-enforcement-seam.md +29 -0
- package/node_modules/pi-lens/docs/adr/0006-derived-state-benchmark-first.md +31 -0
- package/node_modules/pi-lens/docs/adr/0007-end-to-end-witness-per-seam-slice.md +38 -0
- package/node_modules/pi-lens/docs/adr/0008-turn-end-lane-interface.md +165 -0
- package/node_modules/pi-lens/docs/adr/0009-reported-path-attribution.md +114 -0
- package/node_modules/pi-lens/docs/adr/README.md +26 -0
- package/node_modules/pi-lens/docs/agent-tools.md +18 -1
- package/node_modules/pi-lens/docs/ast-grep_rules_catalog.md +4 -3
- package/node_modules/pi-lens/docs/configuration.md +55 -1
- package/node_modules/pi-lens/docs/engineering-principles.md +202 -0
- package/node_modules/pi-lens/docs/environment-variables.md +61 -4
- package/node_modules/pi-lens/docs/features.md +2 -2
- package/node_modules/pi-lens/docs/globalconfig.md +6 -1
- package/node_modules/pi-lens/docs/language-coverage.md +17 -0
- package/node_modules/pi-lens/docs/lsp-capability-matrix.md +98 -31
- package/node_modules/pi-lens/docs/pi-lens-fixer.md +57 -3
- package/node_modules/pi-lens/docs/pi-lens-investigator.md +9 -0
- package/node_modules/pi-lens/docs/pi-lens-reviewer.md +16 -0
- package/node_modules/pi-lens/docs/pi-lens-subagent.md +9 -0
- package/node_modules/pi-lens/docs/public-api-stability.md +1 -0
- package/node_modules/pi-lens/docs/release-qa-baseline.md +2 -0
- package/node_modules/pi-lens/docs/servercapabilities.md +21 -11
- package/node_modules/pi-lens/docs/settings.md +12 -3
- package/node_modules/pi-lens/docs/subagent-compat.md +1 -1
- package/node_modules/pi-lens/package.json +5 -3
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-assert-tuple-test.yml +13 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-boolean-in-except-test.yml +26 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-http-headers-bracket-access-test.yml +10 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/unchecked-numeric-parse-python-test.yml +18 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/unchecked-throwing-call-python-test.yml +4 -3
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/go-no-fmt-println.yml +2 -2
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-assert-tuple.yml +23 -13
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-boolean-in-except.yml +7 -3
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-console-except-error-js.yml +2 -2
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-console-except-error.yml +2 -2
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-http-headers-bracket-access.yml +12 -7
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/unchecked-numeric-parse-python.yml +44 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/unchecked-throwing-call-python.yml +5 -3
- package/node_modules/pi-lens/rules/tree-sitter-queries/typescript/console-statement.yml +2 -2
- package/node_modules/pi-lens/scripts/install-selftest.mjs +21 -17
- package/node_modules/pi-lens/scripts/lib/web-tree-sitter-dir.mjs +123 -0
- package/node_modules/pi-mcp-adapter/CHANGELOG.md +29 -0
- package/node_modules/pi-mcp-adapter/OAUTH.md +369 -0
- package/node_modules/pi-mcp-adapter/README.md +12 -5
- package/node_modules/pi-mcp-adapter/config.ts +31 -8
- package/node_modules/pi-mcp-adapter/direct-tool-surface.ts +1 -1
- package/node_modules/pi-mcp-adapter/dist/config.js +33 -8
- package/node_modules/pi-mcp-adapter/dist/config.js.map +1 -1
- package/node_modules/pi-mcp-adapter/dist/json-schema-validator.js +15 -2
- package/node_modules/pi-mcp-adapter/dist/json-schema-validator.js.map +1 -1
- package/node_modules/pi-mcp-adapter/dist/mcp-auth.d.ts +2 -0
- package/node_modules/pi-mcp-adapter/dist/mcp-auth.js +27 -2
- package/node_modules/pi-mcp-adapter/dist/mcp-auth.js.map +1 -1
- package/node_modules/pi-mcp-adapter/dist/metadata-cache.js +1 -1
- package/node_modules/pi-mcp-adapter/dist/metadata-cache.js.map +1 -1
- package/node_modules/pi-mcp-adapter/dist/server-manager.js +6 -4
- package/node_modules/pi-mcp-adapter/dist/server-manager.js.map +1 -1
- package/node_modules/pi-mcp-adapter/dist/types.d.ts +2 -2
- package/node_modules/pi-mcp-adapter/dist/utils.d.ts +2 -0
- package/node_modules/pi-mcp-adapter/dist/utils.js +10 -3
- package/node_modules/pi-mcp-adapter/dist/utils.js.map +1 -1
- package/node_modules/pi-mcp-adapter/index.ts +37 -20
- package/node_modules/pi-mcp-adapter/init.ts +14 -15
- package/node_modules/pi-mcp-adapter/json-schema-validator.ts +16 -2
- package/node_modules/pi-mcp-adapter/mcp-auth.ts +27 -2
- package/node_modules/pi-mcp-adapter/metadata-cache.ts +1 -1
- package/node_modules/pi-mcp-adapter/package.json +3 -2
- package/node_modules/pi-mcp-adapter/proxy-modes.ts +1 -1
- package/node_modules/pi-mcp-adapter/server-manager.ts +6 -3
- package/node_modules/pi-mcp-adapter/tool-result-renderer.ts +99 -6
- package/node_modules/pi-mcp-adapter/types.ts +2 -2
- package/node_modules/pi-mcp-adapter/ui-session.ts +60 -27
- package/node_modules/pi-mcp-adapter/utils.ts +11 -3
- package/node_modules/pi-next-cue/.github/workflows/publish.yml +20 -0
- package/node_modules/pi-next-cue/LICENSE +21 -0
- package/node_modules/pi-next-cue/README.md +81 -0
- package/node_modules/pi-next-cue/index.ts +358 -0
- package/node_modules/pi-next-cue/package.json +28 -0
- package/package.json +8 -5
|
@@ -30,7 +30,7 @@ import { getSharedTreeSitterClient } from "../tree-sitter-shared.js";
|
|
|
30
30
|
import { symbolExtractionGrammar, TreeSitterSymbolExtractor, } from "../tree-sitter-symbol-extractor.js";
|
|
31
31
|
import { withTreeSitterRoot } from "../tree-sitter-shared.js";
|
|
32
32
|
import { incrementDegradationCount } from "../degradation-ledger.js";
|
|
33
|
-
import { resolveGitIdentity } from "./git-identity.js";
|
|
33
|
+
import { createGitIdentityResolver, resolveGitIdentity, } from "./git-identity.js";
|
|
34
34
|
import { formatReviewGraphRevisionDriftNote, } from "./revision-drift.js";
|
|
35
35
|
import { buildSymbolId } from "./symbol-id.js";
|
|
36
36
|
import { clearReviewGraphFileIr, getFreshReviewGraphFileIr, reviewGraphIrContentHash, } from "./shared-extraction-ir.js";
|
|
@@ -670,18 +670,18 @@ export function getReviewGraphCacheIdentity(cwd, graph) {
|
|
|
670
670
|
// path, that assumption silently breaks. This doesn't hard-fail (the issue
|
|
671
671
|
// is explicit: log-once observability is enough) — it just makes the
|
|
672
672
|
// assumption visible. The Set records every cwd whose check has RUN (not just
|
|
673
|
-
// mismatches), so
|
|
674
|
-
//
|
|
673
|
+
// mismatches), so a build-local resolver keeps repeated checks in one build
|
|
674
|
+
// bounded without retaining repository presence across builds.
|
|
675
675
|
const _cwdWorktreeCheckedCwds = new Set();
|
|
676
676
|
export function _resetCwdWorktreeMismatchLogForTests() {
|
|
677
677
|
_cwdWorktreeCheckedCwds.clear();
|
|
678
678
|
}
|
|
679
|
-
function logCwdWorktreeMismatchOnce(cwd) {
|
|
679
|
+
function logCwdWorktreeMismatchOnce(cwd, resolveIdentity = resolveGitIdentity) {
|
|
680
680
|
const key = normalizeMapKey(cwd);
|
|
681
681
|
if (_cwdWorktreeCheckedCwds.has(key))
|
|
682
682
|
return;
|
|
683
683
|
_cwdWorktreeCheckedCwds.add(key);
|
|
684
|
-
const identity =
|
|
684
|
+
const identity = resolveIdentity(cwd);
|
|
685
685
|
if (!identity)
|
|
686
686
|
return; // not a git repo — nothing to compare against
|
|
687
687
|
if (identity.worktreeRoot === normalizeFilePath(path.resolve(cwd)))
|
|
@@ -1345,7 +1345,7 @@ function logSnapshotReadVerdict(cwd, phase, reason) {
|
|
|
1345
1345
|
return;
|
|
1346
1346
|
logReviewGraph({ cwd, phase, reason });
|
|
1347
1347
|
}
|
|
1348
|
-
function loadPersistedGraph(cwd, opts) {
|
|
1348
|
+
function loadPersistedGraph(cwd, opts, resolveIdentity = resolveGitIdentity) {
|
|
1349
1349
|
const cacheDir = path.join(getProjectDataDir(cwd), "cache");
|
|
1350
1350
|
const cachePath = path.join(cacheDir, GRAPH_CACHE_FILENAME);
|
|
1351
1351
|
const legacyPath = path.join(cacheDir, LEGACY_GRAPH_CACHE_FILENAME);
|
|
@@ -1382,7 +1382,7 @@ function loadPersistedGraph(cwd, opts) {
|
|
|
1382
1382
|
// "can't verify," not a mismatch, so it does not drop the snapshot.
|
|
1383
1383
|
let stampedHead;
|
|
1384
1384
|
if (opts?.verifyWorktreeIdentity && data.gitStamp) {
|
|
1385
|
-
const current =
|
|
1385
|
+
const current = resolveIdentity(cwd);
|
|
1386
1386
|
if (current && current.worktreeRoot !== data.gitStamp.worktreeRoot) {
|
|
1387
1387
|
logSnapshotReadVerdict(cwd, "snapshot_read_dropped", "worktree_mismatch");
|
|
1388
1388
|
return null;
|
|
@@ -2198,11 +2198,11 @@ function persistGraph(cwd, signature, fileSignatures, fileHashes, graph, options
|
|
|
2198
2198
|
const cacheDir = path.join(getProjectDataDir(cwd), "cache");
|
|
2199
2199
|
const cachePath = path.join(cacheDir, GRAPH_CACHE_FILENAME);
|
|
2200
2200
|
sweepStaleStageFiles(cacheDir);
|
|
2201
|
-
// #300: resolve the git stamp
|
|
2202
|
-
//
|
|
2203
|
-
//
|
|
2201
|
+
// #300: resolve the git stamp at persist time. The build-local resolver
|
|
2202
|
+
// deduplicates repeated checks within this build, while a later build gets a
|
|
2203
|
+
// fresh lifecycle view. undefined for
|
|
2204
2204
|
// non-git cwds, which serializes as `gitStamp: undefined` → omitted key.
|
|
2205
|
-
const gitStamp = resolveGitIdentity(cwd);
|
|
2205
|
+
const gitStamp = (options.resolveIdentity ?? resolveGitIdentity)(cwd);
|
|
2206
2206
|
// Retain the immutable snapshot inputs and build their O(graph) serializable
|
|
2207
2207
|
// arrays only after the quiet window. Replacing a pending entry during an edit
|
|
2208
2208
|
// burst now avoids both serialization and the pre-serialization full copies.
|
|
@@ -2353,7 +2353,7 @@ function hashIgnoredIds(ignoredIds) {
|
|
|
2353
2353
|
}
|
|
2354
2354
|
/** Assemble the checkpoint DTO from the current PRE-resolution graph. Isolated
|
|
2355
2355
|
* so both the offloaded and synchronous writers serialize identical bytes. */
|
|
2356
|
-
function buildReviewGraphCheckpointData(cwd, graph, processedHashes, targetFileCount, ignoredIds) {
|
|
2356
|
+
function buildReviewGraphCheckpointData(cwd, graph, processedHashes, targetFileCount, ignoredIds, resolveIdentity = resolveGitIdentity) {
|
|
2357
2357
|
return {
|
|
2358
2358
|
version: REVIEW_GRAPH_VERSION,
|
|
2359
2359
|
builtAt: new Date().toISOString(),
|
|
@@ -2363,7 +2363,7 @@ function buildReviewGraphCheckpointData(cwd, graph, processedHashes, targetFileC
|
|
|
2363
2363
|
ignoredIdsHash: hashIgnoredIds(ignoredIds),
|
|
2364
2364
|
nodes: Array.from(graph.nodes.entries()),
|
|
2365
2365
|
edges: graph.edges,
|
|
2366
|
-
gitStamp:
|
|
2366
|
+
gitStamp: resolveIdentity(cwd),
|
|
2367
2367
|
};
|
|
2368
2368
|
}
|
|
2369
2369
|
/**
|
|
@@ -2375,12 +2375,12 @@ function buildReviewGraphCheckpointData(cwd, graph, processedHashes, targetFileC
|
|
|
2375
2375
|
* resurrect one over a completed build; it falls back to a synchronous write
|
|
2376
2376
|
* when the worker is unavailable. A lost checkpoint only costs a cold rebuild.
|
|
2377
2377
|
*/
|
|
2378
|
-
function writeReviewGraphCheckpoint(cwd, graph, processedHashes, targetFileCount, ignoredIds) {
|
|
2378
|
+
function writeReviewGraphCheckpoint(cwd, graph, processedHashes, targetFileCount, ignoredIds, resolveIdentity = resolveGitIdentity) {
|
|
2379
2379
|
const generation = (_checkpointGenerations.get(cwd) ?? 0) + 1;
|
|
2380
2380
|
_checkpointGenerations.set(cwd, generation);
|
|
2381
2381
|
let data;
|
|
2382
2382
|
try {
|
|
2383
|
-
data = buildReviewGraphCheckpointData(cwd, graph, processedHashes, targetFileCount, ignoredIds);
|
|
2383
|
+
data = buildReviewGraphCheckpointData(cwd, graph, processedHashes, targetFileCount, ignoredIds, resolveIdentity);
|
|
2384
2384
|
}
|
|
2385
2385
|
catch {
|
|
2386
2386
|
return; // best-effort — building the DTO failed, skip this stride
|
|
@@ -2488,7 +2488,7 @@ function deleteReviewGraphCheckpoint(cwd) {
|
|
|
2488
2488
|
* The returned graph carries `persistCoverage.inProgress` so it can never be
|
|
2489
2489
|
* mistaken for a complete graph if it escapes the resume path.
|
|
2490
2490
|
*/
|
|
2491
|
-
function loadReviewGraphCheckpoint(cwd) {
|
|
2491
|
+
function loadReviewGraphCheckpoint(cwd, resolveIdentity = resolveGitIdentity) {
|
|
2492
2492
|
const checkpointPath = reviewGraphCheckpointPath(cwd);
|
|
2493
2493
|
let data;
|
|
2494
2494
|
try {
|
|
@@ -2515,7 +2515,7 @@ function loadReviewGraphCheckpoint(cwd) {
|
|
|
2515
2515
|
return null;
|
|
2516
2516
|
}
|
|
2517
2517
|
if (data.gitStamp) {
|
|
2518
|
-
const current =
|
|
2518
|
+
const current = resolveIdentity(cwd);
|
|
2519
2519
|
if (current && current.worktreeRoot !== data.gitStamp.worktreeRoot) {
|
|
2520
2520
|
logReviewGraph({
|
|
2521
2521
|
cwd,
|
|
@@ -2598,8 +2598,8 @@ function pruneOrphanNonFileNodes(graph) {
|
|
|
2598
2598
|
* - The ignored-id fingerprint must match (import-edge resolution depends on
|
|
2599
2599
|
* it), else fail open.
|
|
2600
2600
|
*/
|
|
2601
|
-
async function tryResumeFromCheckpoint(cwd, filesToBuild, ignoredIds) {
|
|
2602
|
-
const loaded = loadReviewGraphCheckpoint(cwd);
|
|
2601
|
+
async function tryResumeFromCheckpoint(cwd, filesToBuild, ignoredIds, resolveIdentity = resolveGitIdentity) {
|
|
2602
|
+
const loaded = loadReviewGraphCheckpoint(cwd, resolveIdentity);
|
|
2603
2603
|
if (!loaded)
|
|
2604
2604
|
return null;
|
|
2605
2605
|
if (loaded.ignoredIdsHash !== hashIgnoredIds(ignoredIds)) {
|
|
@@ -4153,6 +4153,7 @@ async function tryIncrementalFromCache(cached, ctx) {
|
|
|
4153
4153
|
projectSeq: ctx.seqAtBuildStart,
|
|
4154
4154
|
seqHint: ctx.seqHint,
|
|
4155
4155
|
mode: ctx.mode,
|
|
4156
|
+
resolveIdentity: ctx.resolveIdentity,
|
|
4156
4157
|
});
|
|
4157
4158
|
setGraphBuildInfo(graph, {
|
|
4158
4159
|
reused: true,
|
|
@@ -4179,7 +4180,7 @@ function hasGraphKindExtension(file) {
|
|
|
4179
4180
|
* `_lastGraphBuildInfo` (the caller's full sweep sets the mode and stamps the
|
|
4180
4181
|
* fallback reason). Correctness bar is HIGH: any doubt ⇒ fall back.
|
|
4181
4182
|
*/
|
|
4182
|
-
async function trySeqFastpath(cwd, buildId, normalizedCwd, normalizedChanged, facts, seqHint, seqAtBuildStart, ignoredIds, cacheEpoch) {
|
|
4183
|
+
async function trySeqFastpath(cwd, buildId, normalizedCwd, normalizedChanged, facts, seqHint, seqAtBuildStart, ignoredIds, cacheEpoch, resolveIdentity) {
|
|
4183
4184
|
const cached = _workspaceGraphCache.get(normalizedCwd);
|
|
4184
4185
|
// Condition 2: need an in-process entry that recorded a build seq and whose
|
|
4185
4186
|
// SOURCE WALK was complete. An entry-budget-truncated or checkpoint graph has
|
|
@@ -4326,6 +4327,7 @@ async function trySeqFastpath(cwd, buildId, normalizedCwd, normalizedChanged, fa
|
|
|
4326
4327
|
projectSeq: seqAtBuildStart,
|
|
4327
4328
|
seqHint: true,
|
|
4328
4329
|
mode: "seq-fastpath",
|
|
4330
|
+
resolveIdentity,
|
|
4329
4331
|
});
|
|
4330
4332
|
// #459: filesToUpdate was non-empty — this fastpath re-extracted real files,
|
|
4331
4333
|
// so (unlike the no-op branch above) the graph object did change.
|
|
@@ -4343,6 +4345,7 @@ async function trySeqFastpath(cwd, buildId, normalizedCwd, normalizedChanged, fa
|
|
|
4343
4345
|
return { graph };
|
|
4344
4346
|
}
|
|
4345
4347
|
async function _doBuildGraph(cwd, changedFiles, facts, seqHint, buildId) {
|
|
4348
|
+
const resolveIdentity = createGitIdentityResolver();
|
|
4346
4349
|
const normalizedCwd = normalizeMapKey(cwd);
|
|
4347
4350
|
const cacheEpoch = workspaceCacheEpoch(normalizedCwd);
|
|
4348
4351
|
// `await undefined` still yields a microtask, which reorders overlapping
|
|
@@ -4352,7 +4355,7 @@ async function _doBuildGraph(cwd, changedFiles, facts, seqHint, buildId) {
|
|
|
4352
4355
|
await _reviewGraphBuildGateForTests();
|
|
4353
4356
|
const normalizedChanged = changedFiles.map((file) => normalizeMapKey(file));
|
|
4354
4357
|
const normalizedChangedSet = new Set(normalizedChanged);
|
|
4355
|
-
logCwdWorktreeMismatchOnce(cwd);
|
|
4358
|
+
logCwdWorktreeMismatchOnce(cwd, resolveIdentity);
|
|
4356
4359
|
// #622: reject a cwd that IS (or is an ancestor of) $HOME before any walk is
|
|
4357
4360
|
// attempted. The 3 real per-edit callers (dispatch/integration.ts's
|
|
4358
4361
|
// computeCascadeForFile, mcp/analyze.ts, tree-sitter.ts's
|
|
@@ -4405,7 +4408,7 @@ async function _doBuildGraph(cwd, changedFiles, facts, seqHint, buildId) {
|
|
|
4405
4408
|
// cascade.log can watch the fast-path hit/miss rate.
|
|
4406
4409
|
let seqFastpathFallback;
|
|
4407
4410
|
if (seqHint && seqAtBuildStart !== undefined && seqFastpathEnabled()) {
|
|
4408
|
-
const fast = await trySeqFastpath(cwd, buildId, normalizedCwd, normalizedChanged, facts, seqHint, seqAtBuildStart, await ignoredIdsPromise, cacheEpoch);
|
|
4411
|
+
const fast = await trySeqFastpath(cwd, buildId, normalizedCwd, normalizedChanged, facts, seqHint, seqAtBuildStart, await ignoredIdsPromise, cacheEpoch, resolveIdentity);
|
|
4409
4412
|
if ("graph" in fast)
|
|
4410
4413
|
return fast.graph;
|
|
4411
4414
|
seqFastpathFallback = fast.fallback;
|
|
@@ -4550,6 +4553,7 @@ async function _doBuildGraph(cwd, changedFiles, facts, seqHint, buildId) {
|
|
|
4550
4553
|
seqAtBuildStart,
|
|
4551
4554
|
ignoredIds,
|
|
4552
4555
|
cacheEpoch,
|
|
4556
|
+
resolveIdentity,
|
|
4553
4557
|
});
|
|
4554
4558
|
if (incremental) {
|
|
4555
4559
|
updateGraphBuildInfo(incremental, { seqFastpathFallback });
|
|
@@ -4561,7 +4565,9 @@ async function _doBuildGraph(cwd, changedFiles, facts, seqHint, buildId) {
|
|
|
4561
4565
|
// #202 content-hash confirm below already content-verify the load, and
|
|
4562
4566
|
// dropping on every HEAD move would force a full whole-repo rebuild after
|
|
4563
4567
|
// each plain `git commit` (HEAD moves, files unchanged).
|
|
4564
|
-
const diskCached = sourceFilesTruncated
|
|
4568
|
+
const diskCached = sourceFilesTruncated
|
|
4569
|
+
? null
|
|
4570
|
+
: loadPersistedGraph(cwd, undefined, resolveIdentity);
|
|
4565
4571
|
if (diskCached?.signature === signature) {
|
|
4566
4572
|
const graph = cloneGraph(diskCached.graph);
|
|
4567
4573
|
rebuildIndexes(graph);
|
|
@@ -4613,6 +4619,7 @@ async function _doBuildGraph(cwd, changedFiles, facts, seqHint, buildId) {
|
|
|
4613
4619
|
facts,
|
|
4614
4620
|
seqAtBuildStart,
|
|
4615
4621
|
ignoredIds,
|
|
4622
|
+
resolveIdentity,
|
|
4616
4623
|
});
|
|
4617
4624
|
if (incremental) {
|
|
4618
4625
|
updateGraphBuildInfo(incremental, { seqFastpathFallback });
|
|
@@ -4623,7 +4630,7 @@ async function _doBuildGraph(cwd, changedFiles, facts, seqHint, buildId) {
|
|
|
4623
4630
|
// present and still current (#936 limit 2), else cold from an empty graph.
|
|
4624
4631
|
const resumed = sourceFilesTruncated
|
|
4625
4632
|
? null
|
|
4626
|
-
: await tryResumeFromCheckpoint(cwd, filesToBuild, ignoredIds);
|
|
4633
|
+
: await tryResumeFromCheckpoint(cwd, filesToBuild, ignoredIds, resolveIdentity);
|
|
4627
4634
|
const graph = resumed?.graph ?? createEmptyGraph();
|
|
4628
4635
|
const filesToExtract = resumed?.remaining ?? filesToBuild;
|
|
4629
4636
|
const treeSitterClient = getSharedTreeSitterClient();
|
|
@@ -4674,7 +4681,7 @@ async function _doBuildGraph(cwd, changedFiles, facts, seqHint, buildId) {
|
|
|
4674
4681
|
if (Number.isFinite(testStopAfter) &&
|
|
4675
4682
|
testStopAfter > 0 &&
|
|
4676
4683
|
extractedCount >= testStopAfter) {
|
|
4677
|
-
writeReviewGraphCheckpointSync(cwd, buildReviewGraphCheckpointData(cwd, graph, fileHashes, filesToBuild.length, ignoredIds), {
|
|
4684
|
+
writeReviewGraphCheckpointSync(cwd, buildReviewGraphCheckpointData(cwd, graph, fileHashes, filesToBuild.length, ignoredIds, resolveIdentity), {
|
|
4678
4685
|
nodes: graph.nodes.size,
|
|
4679
4686
|
edges: graph.edges.length,
|
|
4680
4687
|
processed: fileHashes.size,
|
|
@@ -4686,7 +4693,7 @@ async function _doBuildGraph(cwd, changedFiles, facts, seqHint, buildId) {
|
|
|
4686
4693
|
if (filesSinceCheckpoint >= checkpointEvery &&
|
|
4687
4694
|
remainingAfter >= checkpointEvery &&
|
|
4688
4695
|
Date.now() - lastCheckpointMs >= checkpointMinIntervalMs) {
|
|
4689
|
-
writeReviewGraphCheckpoint(cwd, graph, fileHashes, filesToBuild.length, ignoredIds);
|
|
4696
|
+
writeReviewGraphCheckpoint(cwd, graph, fileHashes, filesToBuild.length, ignoredIds, resolveIdentity);
|
|
4690
4697
|
filesSinceCheckpoint = 0;
|
|
4691
4698
|
lastCheckpointMs = Date.now();
|
|
4692
4699
|
}
|
|
@@ -4776,6 +4783,7 @@ async function _doBuildGraph(cwd, changedFiles, facts, seqHint, buildId) {
|
|
|
4776
4783
|
mode: "full",
|
|
4777
4784
|
sourceFileCount,
|
|
4778
4785
|
sourceFilesTruncated,
|
|
4786
|
+
resolveIdentity,
|
|
4779
4787
|
}); // fire-and-forget
|
|
4780
4788
|
setGraphBuildInfo(graph, {
|
|
4781
4789
|
reused: false,
|
|
@@ -14,14 +14,24 @@
|
|
|
14
14
|
*/
|
|
15
15
|
import * as fs from "node:fs";
|
|
16
16
|
import * as path from "node:path";
|
|
17
|
-
import { BoundedLruCache } from "../bounded-cache.js";
|
|
18
17
|
import { normalizeFilePath, walkUpDirs } from "../path-utils.js";
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
18
|
+
/** A build-local memo: it is discarded when the build settles. */
|
|
19
|
+
export function createGitIdentityResolver() {
|
|
20
|
+
const memo = new Map();
|
|
21
|
+
return (cwd) => {
|
|
22
|
+
if (memo.has(cwd))
|
|
23
|
+
return memo.get(cwd);
|
|
24
|
+
const identity = resolveGitIdentity(cwd);
|
|
25
|
+
memo.set(cwd, identity);
|
|
26
|
+
return identity;
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
// Kept as a compatibility no-op for tests that reset the former memo. Git
|
|
30
|
+
// repository creation/removal is a process-lifecycle change, so the lookup
|
|
31
|
+
// below must walk the filesystem on every call rather than cache presence or
|
|
32
|
+
// absence by cwd.
|
|
23
33
|
export function _resetGitIdentityCacheForTests() {
|
|
24
|
-
|
|
34
|
+
return;
|
|
25
35
|
}
|
|
26
36
|
function readCommonDir(gitDir) {
|
|
27
37
|
try {
|
|
@@ -84,9 +94,6 @@ function resolveGitDir(cwd) {
|
|
|
84
94
|
return null;
|
|
85
95
|
}
|
|
86
96
|
function getResolvedGitDir(cwd) {
|
|
87
|
-
const key = normalizeFilePath(path.resolve(cwd));
|
|
88
|
-
if (_gitDirCache.has(key))
|
|
89
|
-
return _gitDirCache.get(key) ?? null;
|
|
90
97
|
let resolved;
|
|
91
98
|
try {
|
|
92
99
|
resolved = resolveGitDir(cwd);
|
|
@@ -94,7 +101,6 @@ function getResolvedGitDir(cwd) {
|
|
|
94
101
|
catch {
|
|
95
102
|
resolved = null;
|
|
96
103
|
}
|
|
97
|
-
_gitDirCache.set(key, resolved);
|
|
98
104
|
return resolved;
|
|
99
105
|
}
|
|
100
106
|
function resolveRefToSha(commonDir, ref) {
|
|
@@ -12,6 +12,7 @@ import * as fs from "node:fs";
|
|
|
12
12
|
import * as path from "node:path";
|
|
13
13
|
import { createAvailabilityChecker, resolveAvailableOrInstall, } from "./dispatch/runners/utils/runner-helpers.js";
|
|
14
14
|
import { isFileKind } from "./file-kinds.js";
|
|
15
|
+
import { pathsEqual } from "./path-utils.js";
|
|
15
16
|
import { safeSpawnAsync } from "./safe-spawn.js";
|
|
16
17
|
import { resolveToolCwd } from "./tool-cwd.js";
|
|
17
18
|
import { ruffConfigArgs } from "./tool-policy.js";
|
|
@@ -95,7 +96,7 @@ export class RuffClient {
|
|
|
95
96
|
absolutePath,
|
|
96
97
|
], { timeout: 10000, cwd: ruffCwd });
|
|
97
98
|
const beforeDiags = pre.stdout?.trim()
|
|
98
|
-
? this.parseOutput(pre.stdout, absolutePath)
|
|
99
|
+
? this.parseOutput(pre.stdout, ruffCwd, absolutePath)
|
|
99
100
|
: [];
|
|
100
101
|
const fixableCount = beforeDiags.filter((d) => d.fixable).length;
|
|
101
102
|
const fix = await safeSpawnAsync(this.ruffCommand, ["check", "--fix", ...configArgs, absolutePath], { timeout: 15000, cwd: ruffCwd });
|
|
@@ -119,15 +120,41 @@ export class RuffClient {
|
|
|
119
120
|
}
|
|
120
121
|
}
|
|
121
122
|
// --- Internal ---
|
|
122
|
-
|
|
123
|
+
/**
|
|
124
|
+
* `cwd` is the directory ruff RAN in, and it is REQUIRED: a default would
|
|
125
|
+
* hide the wrong base at a call site that forgot it (#3284's D11).
|
|
126
|
+
*
|
|
127
|
+
* ruff's JSON `filename` is the `SourceFile`'s name verbatim — the JSON
|
|
128
|
+
* renderer reads `span.file().path(resolver)` (ruff 0.16.8
|
|
129
|
+
* `crates/ruff_db/src/diagnostic/render/json.rs:58,120`), which for a ruff
|
|
130
|
+
* (not `ty`) diagnostic is `file.name()` (`crates/ruff_db/src/diagnostic/
|
|
131
|
+
* mod.rs:1187-1193`), NOT the cwd-relative `relative_path` the text
|
|
132
|
+
* renderers use (`mod.rs:1195-1204`). That name is
|
|
133
|
+
* `SourceFileBuilder::new(path.to_string_lossy(), …)`
|
|
134
|
+
* (`crates/ruff_linter/src/linter.rs:1049`) over a path the resolver has
|
|
135
|
+
* already absolutized ("Normalize every path (e.g., convert from relative to
|
|
136
|
+
* absolute)", `crates/ruff_workspace/src/resolver.rs:484`). So ruff echoes
|
|
137
|
+
* the absolute path we hand it as argv: the BASE was already right here and
|
|
138
|
+
* the fold deletes a `path.resolve` with no base — which silently meant the
|
|
139
|
+
* EXTENSION's `process.cwd()`, not ruff's — rather than leaving it for a
|
|
140
|
+
* future relative invocation to re-enable.
|
|
141
|
+
*/
|
|
142
|
+
parseOutput(output, cwd, filterFile) {
|
|
123
143
|
if (!output.trim())
|
|
124
144
|
return [];
|
|
125
145
|
try {
|
|
126
146
|
const items = JSON.parse(output);
|
|
127
147
|
const diagnostics = [];
|
|
128
148
|
for (const item of items) {
|
|
129
|
-
// Filter to single file if requested
|
|
130
|
-
|
|
149
|
+
// Filter to single file if requested. #3278/#3286: one seam answers
|
|
150
|
+
// "is this reported diagnostic about the file I ran for?" — resolve
|
|
151
|
+
// the tool's spelling against the cwd the tool RAN in and compare
|
|
152
|
+
// through `pathsEqual`, the repo's on-disk identity predicate. A bare
|
|
153
|
+
// `!==` treats a spelling that differs only in case (the SAME file on
|
|
154
|
+
// Windows and on a case-folding POSIX mount) as a different file and
|
|
155
|
+
// drops every finding for the edited file (#209, #3277).
|
|
156
|
+
if (filterFile &&
|
|
157
|
+
!pathsEqual(path.resolve(cwd, item.filename), filterFile))
|
|
131
158
|
continue;
|
|
132
159
|
diagnostics.push({
|
|
133
160
|
line: item.location.row - 1, // ruff is 1-indexed
|
|
@@ -184,12 +184,45 @@ export class RuntimeCoordinator {
|
|
|
184
184
|
this._cascadeSessionStats.diagnosticsSurfaced += diagnosticsSurfaced;
|
|
185
185
|
this._cascadeSessionStats.coldSnapshotTouches += coldSnapshotTouches;
|
|
186
186
|
}
|
|
187
|
+
/**
|
|
188
|
+
* Record the turn-end policy verdict on every live inline-blocker record
|
|
189
|
+
* (#3248): `true` for a file whose every blocker the policy suppressed,
|
|
190
|
+
* `false` for every other record in the map.
|
|
191
|
+
*
|
|
192
|
+
* ONE writer, rewriting the WHOLE axis each turn end, so the verdict can
|
|
193
|
+
* never outlive the pass that made it: a record demoted since, or one whose
|
|
194
|
+
* survivors came back because the file's bytes moved under a content-bound
|
|
195
|
+
* anchor, is cleared by the same call that sets its sibling. A fresh
|
|
196
|
+
* dispatch replaces the record wholesale (`recordInlineBlockers`), so a NEW
|
|
197
|
+
* blocker is never born pre-suppressed (#1198 ordering).
|
|
198
|
+
*
|
|
199
|
+
* @returns how many records this call CHANGED — 0 means the gate's view of
|
|
200
|
+
* the map is already what the policy just decided.
|
|
201
|
+
*/
|
|
202
|
+
applyInlineBlockerPolicyVerdicts(suppressedFilePaths) {
|
|
203
|
+
const suppressed = new Set(suppressedFilePaths.map((filePath) => path.resolve(filePath)));
|
|
204
|
+
let changed = 0;
|
|
205
|
+
for (const [key, entry] of this._pendingInlineBlockers.entries()) {
|
|
206
|
+
const policySuppressed = suppressed.has(path.resolve(entry.filePath));
|
|
207
|
+
if (!!entry.policySuppressed === policySuppressed)
|
|
208
|
+
continue;
|
|
209
|
+
this._pendingInlineBlockers.set(key, { ...entry, policySuppressed });
|
|
210
|
+
changed += 1;
|
|
211
|
+
}
|
|
212
|
+
return changed;
|
|
213
|
+
}
|
|
187
214
|
updateGitGuardStatus(hasBlockers, output) {
|
|
188
215
|
// The status is an aggregate over the current per-file map. A clean B
|
|
189
216
|
// result must not erase an unresolved A result; the pipeline records/clears
|
|
190
217
|
// the edited file immediately before this method runs.
|
|
191
|
-
|
|
192
|
-
|
|
218
|
+
// #3248: every live record except one whose whole blocker set the
|
|
219
|
+
// turn-end disposition policy suppressed. The record itself stays in the
|
|
220
|
+
// map — the policy is content-bound and re-derived from the record's
|
|
221
|
+
// diagnostics at every turn end, so dropping it there would be silencing
|
|
222
|
+
// rather than filtering (AGENTS.md shape 10); it just stops counting for
|
|
223
|
+
// the gate the agent was shown nothing for.
|
|
224
|
+
const blocking = this.getInlineBlockersSnapshot().filter((entry) => !entry.policySuppressed);
|
|
225
|
+
this._gitGuardHasBlockers = hasBlockers || blocking.length > 0;
|
|
193
226
|
if (!this._gitGuardHasBlockers) {
|
|
194
227
|
this._gitGuardSummary = "";
|
|
195
228
|
return;
|
|
@@ -198,7 +231,7 @@ export class RuntimeCoordinator {
|
|
|
198
231
|
.split("\n")
|
|
199
232
|
.map((line) => line.trim())
|
|
200
233
|
.find((line) => line.length > 0);
|
|
201
|
-
const summaries =
|
|
234
|
+
const summaries = blocking
|
|
202
235
|
.map((entry) => entry.summary.trim())
|
|
203
236
|
.filter(Boolean);
|
|
204
237
|
this._gitGuardSummary = (summaries[0] ??
|
|
@@ -665,7 +698,7 @@ export class RuntimeCoordinator {
|
|
|
665
698
|
// that actually settles it delivers it.
|
|
666
699
|
return this._cascadeRuns.length > 0;
|
|
667
700
|
}
|
|
668
|
-
recordInlineBlockers(filePath, summary, writeIndex, sources, lines, contentBaseline) {
|
|
701
|
+
recordInlineBlockers(filePath, summary, writeIndex, sources, lines, contentBaseline, diagnostics) {
|
|
669
702
|
const recordedAtMs = Date.now();
|
|
670
703
|
this._pendingInlineBlockers.set(path.resolve(filePath), {
|
|
671
704
|
filePath,
|
|
@@ -673,6 +706,7 @@ export class RuntimeCoordinator {
|
|
|
673
706
|
writeIndex,
|
|
674
707
|
sources,
|
|
675
708
|
lines,
|
|
709
|
+
diagnostics,
|
|
676
710
|
recordedAtMs,
|
|
677
711
|
stale: false,
|
|
678
712
|
...(contentBaseline
|
|
@@ -8,7 +8,7 @@ import { evaluateGitGuard, isGitCommitOrPushAttempt } from "./git-guard.js";
|
|
|
8
8
|
import { dropHashlineAnchorMemo } from "./hashline-anchor.js";
|
|
9
9
|
import { evaluateSharedCheckoutGuard } from "./shared-checkout-guard.js";
|
|
10
10
|
import { logLatency } from "./latency-logger.js";
|
|
11
|
-
import { normalizeMapKey } from "./path-utils.js";
|
|
11
|
+
import { normalizeMapKey, toPosix } from "./path-utils.js";
|
|
12
12
|
import { captureFileStats, getOpaqueBaselineStore, isGitWorktree, } from "./opaque-mutation-scan.js";
|
|
13
13
|
import { normalizeForGuardMatch } from "./host-edit-normalize.js";
|
|
14
14
|
import { retargetReplacementIndentation } from "./indent-retarget.js";
|
|
@@ -133,7 +133,12 @@ function isLspCapableFile(filePath) {
|
|
|
133
133
|
return LANGUAGE_POLICY[kind]?.lspCapable !== false;
|
|
134
134
|
}
|
|
135
135
|
function shouldSkipLspAutoTouch(filePath, projectRoot) {
|
|
136
|
-
|
|
136
|
+
// #1193 P3: the separator fold is `toPosix`, not a fourth inline copy of
|
|
137
|
+
// `.replace(/\\/g, "/")`. The `toLowerCase` stays and is NOT a path-key case
|
|
138
|
+
// fold: `normalized` is never a map key, only the haystack for the
|
|
139
|
+
// lowercase marker substrings below, which must match a mis-cased spelling
|
|
140
|
+
// of the same marker directory on a case-insensitive filesystem.
|
|
141
|
+
const normalized = toPosix(path.resolve(filePath)).toLowerCase();
|
|
137
142
|
const base = path.basename(filePath).toLowerCase();
|
|
138
143
|
if (normalized.includes("/.pi-lens/"))
|
|
139
144
|
return true;
|
|
@@ -792,9 +797,15 @@ async function handleToolCallImpl(deps) {
|
|
|
792
797
|
// budgeted families rather than leaving this site to spell its
|
|
793
798
|
// own axis value (#2557 review F7).
|
|
794
799
|
hook: "tool_call",
|
|
795
|
-
// The ambient slot is populated by tool_result,
|
|
796
|
-
// already run
|
|
797
|
-
// this await (#2523 AC4).
|
|
800
|
+
// The ambient slot is populated by tool_result, AFTER this hook has
|
|
801
|
+
// already run, so the live `tool_call` signal is the only one that
|
|
802
|
+
// can release this await when the user presses Escape (#2523 AC4).
|
|
803
|
+
// #2939 round 2 restored this after round 2's own measurement: with
|
|
804
|
+
// the signal absent, an aborted caller waits the demand's whole
|
|
805
|
+
// `BOOTSTRAP_LOAD_TIMEOUT_MS` out and the cancel then surfaces on the
|
|
806
|
+
// ledger as a `timeout` degradation — the exact inversion
|
|
807
|
+
// `requestBootstrapClients`'s `unavailableReason !== "aborted"` guard
|
|
808
|
+
// exists to prevent.
|
|
798
809
|
signal: deps.ctx.signal,
|
|
799
810
|
}))?.complexityClient;
|
|
800
811
|
const baseline = await complexityClient?.analyzeFile(filePath);
|
|
@@ -484,7 +484,7 @@ function refreshCachedExports(runtime, filePath) {
|
|
|
484
484
|
* pipeline AND record" block could not express.
|
|
485
485
|
*/
|
|
486
486
|
async function dispatchPipelineAnalysis(args) {
|
|
487
|
-
const { deps, runtime, filePath, dispatchCwd, turnStateCwd, autofixMode, modifiedRanges, writeIndex, initialStateHash, readGuardCorrelationId, requestedEditIndexes, requestedEditTotal, isPartialApplyResult, toolResultStart, nativeAppliedPairs,
|
|
487
|
+
const { deps, runtime, filePath, dispatchCwd, turnStateCwd, autofixMode, modifiedRanges, writeIndex, initialStateHash, readGuardCorrelationId, requestedEditIndexes, requestedEditTotal, isPartialApplyResult, toolResultStart, nativeAppliedPairs, allowAutonomousWriters, } = args;
|
|
488
488
|
const { event, getFlag, getFlagSource, dbg, biomeClient, ruffClient, metricsClient, resetLSPService, } = deps;
|
|
489
489
|
const pipelinePromise = runPipeline({
|
|
490
490
|
signal: deps.signal,
|
|
@@ -493,6 +493,7 @@ async function dispatchPipelineAnalysis(args) {
|
|
|
493
493
|
projectRoot: turnStateCwd,
|
|
494
494
|
toolName: event.toolName,
|
|
495
495
|
autofixMode,
|
|
496
|
+
allowAutonomousWriters,
|
|
496
497
|
modifiedRanges,
|
|
497
498
|
telemetry: {
|
|
498
499
|
model: runtime.telemetryModel,
|
|
@@ -693,7 +694,7 @@ async function dispatchPipelineAnalysis(args) {
|
|
|
693
694
|
// The model's write/edit and pi-lens' own immediate format/autofix are now
|
|
694
695
|
// reflected on disk. Refresh read-guard staleness stamps so a follow-up edit
|
|
695
696
|
// is judged by read-range coverage, not by our own previous write.
|
|
696
|
-
if (!getFlag("no-read-guard") &&
|
|
697
|
+
if (!getFlag("no-read-guard") && allowAutonomousWriters) {
|
|
697
698
|
const changedForReadGuard = new Set([
|
|
698
699
|
path.resolve(filePath),
|
|
699
700
|
...(result.changedFiles ?? []).map((changedFile) => path.resolve(changedFile)),
|
|
@@ -710,7 +711,9 @@ export async function handleToolResult(deps) {
|
|
|
710
711
|
const { event, getFlag, dbg, runtime, cacheManager, agentBehaviorRecord, formatBehaviorWarnings, } = deps;
|
|
711
712
|
const rawFilePath = event.input.path;
|
|
712
713
|
const workspaceRoot = runtime.projectRoot || process.cwd();
|
|
713
|
-
let bashAuthorshipConfirmed = deps.
|
|
714
|
+
let bashAuthorshipConfirmed = deps._allowAutonomousWriters ??
|
|
715
|
+
deps._readGuardAuthorship ??
|
|
716
|
+
event.toolName !== "bash";
|
|
714
717
|
// #1642: a gitignored worktree edit got re-attributed onto a
|
|
715
718
|
// same-relative-path file in the parent checkout because this handler
|
|
716
719
|
// used to always resolve a relative path against `workspaceRoot`
|
|
@@ -975,11 +978,9 @@ export async function handleToolResult(deps) {
|
|
|
975
978
|
const opaqueSet = new Set(opaquePaths);
|
|
976
979
|
const written = [...recognizedWritten, ...opaquePaths];
|
|
977
980
|
const recognizedAuthoredSet = new Set(recognizedAuthored);
|
|
978
|
-
bashAuthorshipConfirmed =
|
|
979
|
-
recognizedAuthored.length > 0 || opaquePaths.length > 0;
|
|
981
|
+
bashAuthorshipConfirmed = recognizedAuthored.length > 0;
|
|
980
982
|
for (const wp of written) {
|
|
981
|
-
if (!getFlag("no-read-guard") &&
|
|
982
|
-
(opaqueSet.has(wp) || recognizedAuthoredSet.has(wp)))
|
|
983
|
+
if (!getFlag("no-read-guard") && recognizedAuthoredSet.has(wp))
|
|
983
984
|
deps.readGuard?.recordWritten(wp);
|
|
984
985
|
else if (!getFlag("no-read-guard") && recognizedWritten.includes(wp))
|
|
985
986
|
deps.readGuard?.recordUnchanged?.(wp);
|
|
@@ -1013,7 +1014,11 @@ export async function handleToolResult(deps) {
|
|
|
1013
1014
|
_autofixMode: autofixMode,
|
|
1014
1015
|
_attachmentBudget: syntheticAttachmentBudget,
|
|
1015
1016
|
_mutationSourceOverride: isOpaque ? "opaque-script" : undefined,
|
|
1016
|
-
_readGuardAuthorship:
|
|
1017
|
+
_readGuardAuthorship: recognizedAuthoredSet.has(wp),
|
|
1018
|
+
// Opaque recovery is mutation evidence only. The synthetic call still
|
|
1019
|
+
// records freshness and runs diagnostics, but it cannot format/autofix
|
|
1020
|
+
// or issue an edit-directed blocker/actionable instruction.
|
|
1021
|
+
_allowAutonomousWriters: recognizedAuthoredSet.has(wp),
|
|
1017
1022
|
});
|
|
1018
1023
|
if (syntheticResult) {
|
|
1019
1024
|
// #1590: forward verbatim. The synthetic call already charged the
|
|
@@ -1476,7 +1481,7 @@ export async function handleToolResult(deps) {
|
|
|
1476
1481
|
// bridge, so the dispatch's read-guard write refresh
|
|
1477
1482
|
// carries evidence and stays enabled on every
|
|
1478
1483
|
// per-path dispatch.
|
|
1479
|
-
|
|
1484
|
+
allowAutonomousWriters: true,
|
|
1480
1485
|
}), {
|
|
1481
1486
|
ms: HOOK_WALL_BUDGET_MS.tool_result_edit,
|
|
1482
1487
|
signal: observedDispatchSignal,
|
|
@@ -1787,7 +1792,7 @@ export async function handleToolResult(deps) {
|
|
|
1787
1792
|
participantTotal,
|
|
1788
1793
|
toolResultStart,
|
|
1789
1794
|
nativeAppliedPairs,
|
|
1790
|
-
|
|
1795
|
+
allowAutonomousWriters: bashAuthorshipConfirmed,
|
|
1791
1796
|
}), {
|
|
1792
1797
|
ms: HOOK_WALL_BUDGET_MS.tool_result_edit,
|
|
1793
1798
|
signal: deps.signal || undefined,
|
|
@@ -1808,6 +1813,7 @@ export async function handleToolResult(deps) {
|
|
|
1808
1813
|
const result = dispatchOutcome.result;
|
|
1809
1814
|
let autofixNewlyQueued = false;
|
|
1810
1815
|
if (!result.isError &&
|
|
1816
|
+
bashAuthorshipConfirmed &&
|
|
1811
1817
|
autofixMode === "deferred" &&
|
|
1812
1818
|
nodeFs.existsSync(filePath)) {
|
|
1813
1819
|
autofixNewlyQueued =
|
|
@@ -1816,6 +1822,7 @@ export async function handleToolResult(deps) {
|
|
|
1816
1822
|
}
|
|
1817
1823
|
let formatQueued = false;
|
|
1818
1824
|
if (!result.isError &&
|
|
1825
|
+
bashAuthorshipConfirmed &&
|
|
1819
1826
|
!getFlag("no-autoformat", filePath) &&
|
|
1820
1827
|
(autofixMode === "deferred" || !getFlag("immediate-format")) &&
|
|
1821
1828
|
nodeFs.existsSync(filePath)) {
|
|
@@ -1869,7 +1876,11 @@ export async function handleToolResult(deps) {
|
|
|
1869
1876
|
source: "autofix",
|
|
1870
1877
|
dbg,
|
|
1871
1878
|
});
|
|
1872
|
-
|
|
1879
|
+
// Workspace-edit paths come from fileURLToPath and are normally absolute,
|
|
1880
|
+
// so the turn-state cwd is inert for those values. Keep it in the resolve
|
|
1881
|
+
// call because the producer's contract is cwd-relative URI resolution, and
|
|
1882
|
+
// let pathsEqual ask the filesystem-aware path identity question (#3294).
|
|
1883
|
+
if (pathsEqual(path.resolve(turnStateCwd, changedFile), path.resolve(filePath)))
|
|
1873
1884
|
continue;
|
|
1874
1885
|
try {
|
|
1875
1886
|
const content = nodeFs.readFileSync(resolvedChanged, "utf-8");
|
|
@@ -1929,7 +1940,11 @@ export async function handleToolResult(deps) {
|
|
|
1929
1940
|
// #1561: stamp the verdict with THIS dispatch's write token — the same
|
|
1930
1941
|
// counter `lsp_diagnostics`' reconciliation seam draws from — so a later
|
|
1931
1942
|
// confirmed-clean result can be ordered against it instead of racing it.
|
|
1932
|
-
runtime.recordInlineBlockers(filePath, result.inlineBlockerSummary, writeIndex, result.inlineBlockerSources, result.inlineBlockerLines, result.inlineBlockerFileContent
|
|
1943
|
+
runtime.recordInlineBlockers(filePath, result.inlineBlockerSummary, writeIndex, result.inlineBlockerSources, result.inlineBlockerLines, result.inlineBlockerFileContent,
|
|
1944
|
+
// #3246: the structured blockers the summary was rendered from, so a
|
|
1945
|
+
// later `lens_diagnostic_mark` can be applied to this record at turn
|
|
1946
|
+
// end instead of replaying pre-mark text.
|
|
1947
|
+
result.inlineBlockerDiagnostics);
|
|
1933
1948
|
}
|
|
1934
1949
|
else {
|
|
1935
1950
|
runtime.clearInlineBlockers(filePath);
|