@herbertgao/pi-extensions 2026.9.6 → 2026.9.7
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 +3 -8
- package/THIRD_PARTY_NOTICES.md +0 -4
- package/node_modules/@narumitw/pi-btw/README.md +2 -0
- package/node_modules/@narumitw/pi-btw/dist/index.ts +284 -365
- package/node_modules/@narumitw/pi-btw/dist/index.ts.map +3 -3
- package/node_modules/@narumitw/pi-btw/package.json +52 -53
- package/node_modules/@narumitw/pi-btw/src/bring-to-main.ts +494 -547
- package/node_modules/@narumitw/pi-btw/src/btw.ts +814 -856
- package/node_modules/@narumitw/pi-btw/src/fullscreen-ui.ts +673 -674
- package/node_modules/@narumitw/pi-btw/src/keybindings.ts +228 -270
- package/node_modules/@narumitw/pi-btw/src/main-tree-picker.ts +309 -318
- package/node_modules/@narumitw/pi-btw/src/menu.ts +416 -454
- package/node_modules/@narumitw/pi-btw/src/settings.ts +203 -219
- package/node_modules/@narumitw/pi-btw/src/side-thread.ts +173 -201
- package/node_modules/@narumitw/pi-btw/src/text.ts +21 -23
- package/node_modules/@narumitw/pi-btw/src/transcript-markdown.ts +65 -0
- package/node_modules/@narumitw/pi-btw/src/transcript-pager.ts +611 -662
- package/node_modules/pi-lens/CHANGELOG.md +239 -0
- package/node_modules/pi-lens/config/dependency-cruiser-eager-allowlist.json +3 -0
- package/node_modules/pi-lens/dist/clients/actionable-warnings.js +30 -4
- package/node_modules/pi-lens/dist/clients/agent-behavior-client.js +2 -0
- package/node_modules/pi-lens/dist/clients/bash-file-access.js +26 -0
- package/node_modules/pi-lens/dist/clients/biome-client.js +1 -1
- package/node_modules/pi-lens/dist/clients/blocker-freshness.js +403 -35
- package/node_modules/pi-lens/dist/clients/bootstrap.js +6 -1
- package/node_modules/pi-lens/dist/clients/cascade-format.js +200 -4
- package/node_modules/pi-lens/dist/clients/code-quality-warnings.js +10 -2
- package/node_modules/pi-lens/dist/clients/degradation-ledger.js +16 -1
- package/node_modules/pi-lens/dist/clients/diagnostic-dispositions.js +12 -1
- package/node_modules/pi-lens/dist/clients/dispatch/ast-grep-catalog.js +65 -0
- package/node_modules/pi-lens/dist/clients/dispatch/auxiliary-lsp.js +28 -0
- package/node_modules/pi-lens/dist/clients/dispatch/dispatcher.js +9 -2
- package/node_modules/pi-lens/dist/clients/dispatch/finding-policy.js +222 -0
- package/node_modules/pi-lens/dist/clients/dispatch/indent-detect.js +389 -13
- package/node_modules/pi-lens/dist/clients/dispatch/integration.js +175 -25
- package/node_modules/pi-lens/dist/clients/dispatch/rule-ignores.js +62 -0
- package/node_modules/pi-lens/dist/clients/dispatch/runners/ast-grep-napi.js +21 -41
- package/node_modules/pi-lens/dist/clients/dispatch/runners/biome-check.js +6 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/rust-clippy.js +7 -7
- package/node_modules/pi-lens/dist/clients/dispatch/runners/shfmt.js +2 -12
- package/node_modules/pi-lens/dist/clients/dispatch/runners/tree-sitter.js +2 -21
- package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/runner-helpers.js +1 -1
- package/node_modules/pi-lens/dist/clients/dispatch/types.js +4 -1
- package/node_modules/pi-lens/dist/clients/disposition-publish.js +1 -0
- package/node_modules/pi-lens/dist/clients/effective-config.js +1 -1
- package/node_modules/pi-lens/dist/clients/file-kinds.js +26 -1
- package/node_modules/pi-lens/dist/clients/file-role.js +21 -21
- package/node_modules/pi-lens/dist/clients/file-utils.js +156 -10
- package/node_modules/pi-lens/dist/clients/finding-delivery-gate.js +56 -9
- package/node_modules/pi-lens/dist/clients/format-service.js +39 -21
- package/node_modules/pi-lens/dist/clients/formatters.js +60 -13
- package/node_modules/pi-lens/dist/clients/generation-guard.js +2 -2
- package/node_modules/pi-lens/dist/clients/git-guard.js +11 -3
- package/node_modules/pi-lens/dist/clients/indent-retarget.js +78 -8
- package/node_modules/pi-lens/dist/clients/installer/index.js +333 -114
- package/node_modules/pi-lens/dist/clients/instance-reaper-state.js +7 -0
- package/node_modules/pi-lens/dist/clients/instance-reaper.js +20 -10
- package/node_modules/pi-lens/dist/clients/language-profile.js +23 -1
- package/node_modules/pi-lens/dist/clients/language-registry.js +5 -1
- package/node_modules/pi-lens/dist/clients/latency-logger.js +4 -1
- package/node_modules/pi-lens/dist/clients/lens-engine.js +12 -3
- package/node_modules/pi-lens/dist/clients/lens-events.js +2 -1
- package/node_modules/pi-lens/dist/clients/lens-flag-registry.js +79 -0
- package/node_modules/pi-lens/dist/clients/live-bus-emitter.js +2 -1
- package/node_modules/pi-lens/dist/clients/lsp/client.js +25 -21
- package/node_modules/pi-lens/dist/clients/lsp/config.js +2 -2
- package/node_modules/pi-lens/dist/clients/lsp/index.js +7 -0
- package/node_modules/pi-lens/dist/clients/lsp/launch.js +16 -4
- package/node_modules/pi-lens/dist/clients/lsp/server.js +19 -2
- package/node_modules/pi-lens/dist/clients/lsp/workspace-diagnostics-cache.js +2 -1
- package/node_modules/pi-lens/dist/clients/lsp-mutation.js +5 -3
- package/node_modules/pi-lens/dist/clients/map-with-concurrency.js +12 -8
- package/node_modules/pi-lens/dist/clients/mcp/session.js +36 -4
- package/node_modules/pi-lens/dist/clients/ndjson-logger.js +24 -7
- package/node_modules/pi-lens/dist/clients/observed-mutation.js +40 -2
- package/node_modules/pi-lens/dist/clients/opaque-mutation-scan.js +23 -3
- package/node_modules/pi-lens/dist/clients/opengrep-client.js +110 -15
- package/node_modules/pi-lens/dist/clients/path-utils.js +123 -3
- package/node_modules/pi-lens/dist/clients/performance-report.js +5 -1
- package/node_modules/pi-lens/dist/clients/php-cs-fixer-config.js +1 -1
- package/node_modules/pi-lens/dist/clients/pipeline.js +74 -5
- package/node_modules/pi-lens/dist/clients/project-diagnostics/cache.js +37 -5
- package/node_modules/pi-lens/dist/clients/project-diagnostics/fresh-fetch.js +145 -18
- package/node_modules/pi-lens/dist/clients/project-diagnostics/runner-adapters/dead-code.js +4 -1
- package/node_modules/pi-lens/dist/clients/project-diagnostics/scanner.js +59 -3
- package/node_modules/pi-lens/dist/clients/project-lens-config.js +45 -2
- package/node_modules/pi-lens/dist/clients/read-guard.js +70 -11
- package/node_modules/pi-lens/dist/clients/resource-sampler.js +147 -9
- package/node_modules/pi-lens/dist/clients/ruff-client.js +1 -1
- package/node_modules/pi-lens/dist/clients/runtime-agent-end.js +33 -5
- package/node_modules/pi-lens/dist/clients/runtime-context.js +6 -2
- package/node_modules/pi-lens/dist/clients/runtime-coordinator.js +77 -7
- package/node_modules/pi-lens/dist/clients/runtime-session.js +21 -2
- package/node_modules/pi-lens/dist/clients/runtime-tool-call.js +43 -12
- package/node_modules/pi-lens/dist/clients/runtime-tool-result.js +330 -85
- package/node_modules/pi-lens/dist/clients/runtime-turn.js +147 -17
- package/node_modules/pi-lens/dist/clients/safe-spawn.js +258 -5
- package/node_modules/pi-lens/dist/clients/scratch-tree-policy.js +6 -0
- package/node_modules/pi-lens/dist/clients/session-event-guard.js +50 -5
- package/node_modules/pi-lens/dist/clients/sg-runner.js +23 -11
- package/node_modules/pi-lens/dist/clients/sgconfig.js +79 -2
- package/node_modules/pi-lens/dist/clients/test-runner-client.js +6 -41
- package/node_modules/pi-lens/dist/clients/test-runner-delivery.js +43 -0
- package/node_modules/pi-lens/dist/clients/tool-agreement.js +423 -0
- package/node_modules/pi-lens/dist/clients/tool-config.js +50 -0
- package/node_modules/pi-lens/dist/clients/tool-cwd.js +47 -76
- package/node_modules/pi-lens/dist/clients/tool-policy.js +163 -7
- package/node_modules/pi-lens/dist/clients/tree-sitter-client.js +120 -0
- package/node_modules/pi-lens/dist/clients/tree-sitter-query-loader.js +127 -189
- package/node_modules/pi-lens/dist/clients/widget-state.js +98 -0
- package/node_modules/pi-lens/dist/clients/word-index.js +5 -2
- package/node_modules/pi-lens/dist/clients/write-ordering-guard.js +7 -0
- package/node_modules/pi-lens/dist/index.js +27416 -24153
- package/node_modules/pi-lens/dist/mcp/server.js +5 -2
- package/node_modules/pi-lens/dist/tools/lens-diagnostic-mark.js +7 -1
- package/node_modules/pi-lens/dist/tools/lens-diagnostics.js +168 -53
- package/node_modules/pi-lens/dist/tools/lsp-diagnostics.js +244 -72
- package/node_modules/pi-lens/docs/audit1.md +5 -5
- package/node_modules/pi-lens/docs/dependencies.md +11 -0
- package/node_modules/pi-lens/docs/dispositions.md +52 -0
- package/node_modules/pi-lens/docs/environment-variables.md +2 -2
- package/node_modules/pi-lens/docs/features.md +4 -4
- package/node_modules/pi-lens/docs/globalconfig.md +3 -1
- package/node_modules/pi-lens/docs/language-coverage.md +1 -0
- package/node_modules/pi-lens/docs/pi-lens-fixer.md +53 -94
- package/node_modules/pi-lens/docs/pi-lens-investigator.md +43 -38
- package/node_modules/pi-lens/docs/pi-lens-retro.md +97 -0
- package/node_modules/pi-lens/docs/pi-lens-reviewer.md +53 -66
- package/node_modules/pi-lens/docs/release-qa-baseline.md +22 -0
- package/node_modules/pi-lens/docs/settings.md +7 -4
- package/node_modules/pi-lens/docs/tree-sitter_rules_catalog.md +1 -1
- package/node_modules/pi-lens/docs/usage.md +2 -0
- package/node_modules/pi-lens/package.json +1 -1
- package/node_modules/pi-lens/rules/tree-sitter-queries/typescript/sql-injection.yml +24 -12
- package/node_modules/pi-lens/scripts/analyze-pi-lens-logs.mjs +40 -0
- package/node_modules/pi-lens/skills/pi-lens-ast-grep/SKILL.md +15 -0
- package/node_modules/pi-lens/skills/pi-lens-lsp-navigation/SKILL.md +7 -0
- package/package.json +5 -11
- package/node_modules/@luxusai/pi-hindsight/CHANGELOG.md +0 -376
- package/node_modules/@luxusai/pi-hindsight/README.md +0 -92
- package/node_modules/@luxusai/pi-hindsight/docs/adr/001-memory-lifecycle-and-scope.md +0 -56
- package/node_modules/@luxusai/pi-hindsight/docs/adr/002-explicit-routing-strategy-seam.md +0 -169
- package/node_modules/@luxusai/pi-hindsight/docs/adr/003-tui-memory-mode-vocabulary.md +0 -107
- package/node_modules/@luxusai/pi-hindsight/docs/adr/004-lifeos-dual-bank-design.md +0 -89
- package/node_modules/@luxusai/pi-hindsight/docs/adr/005-domain-banks-and-agent-first-surface.md +0 -189
- package/node_modules/@luxusai/pi-hindsight/docs/assets/logos/pi-hindsight-logo-dark.webp +0 -0
- package/node_modules/@luxusai/pi-hindsight/docs/assets/logos/pi-hindsight-logo-dark@2x.webp +0 -0
- package/node_modules/@luxusai/pi-hindsight/docs/coding-memory-evaluation.md +0 -54
- package/node_modules/@luxusai/pi-hindsight/docs/compatibility.md +0 -75
- package/node_modules/@luxusai/pi-hindsight/docs/hindsight-core-functions.md +0 -300
- package/node_modules/@luxusai/pi-hindsight/docs/mission-and-mental-model-quality.md +0 -158
- package/node_modules/@luxusai/pi-hindsight/docs/next-opt-out-design.md +0 -164
- package/node_modules/@luxusai/pi-hindsight/docs/risky-memory-modes.md +0 -139
- package/node_modules/@luxusai/pi-hindsight/docs/starter-mental-model-suggestions.md +0 -74
- package/node_modules/@luxusai/pi-hindsight/docs/surface-reference.md +0 -261
- package/node_modules/@luxusai/pi-hindsight/extensions/banks/bank-operations.ts +0 -151
- package/node_modules/@luxusai/pi-hindsight/extensions/banks/bank-selection.ts +0 -18
- package/node_modules/@luxusai/pi-hindsight/extensions/banks/bank-settings-presenter.ts +0 -46
- package/node_modules/@luxusai/pi-hindsight/extensions/banks/bank-templates.ts +0 -383
- package/node_modules/@luxusai/pi-hindsight/extensions/banks/banking.ts +0 -240
- package/node_modules/@luxusai/pi-hindsight/extensions/banks/knowledge-page-seed.ts +0 -176
- package/node_modules/@luxusai/pi-hindsight/extensions/banks/retain-strategies.ts +0 -178
- package/node_modules/@luxusai/pi-hindsight/extensions/client/client-retry.ts +0 -41
- package/node_modules/@luxusai/pi-hindsight/extensions/client/client.ts +0 -386
- package/node_modules/@luxusai/pi-hindsight/extensions/client/fetch-compat.ts +0 -39
- package/node_modules/@luxusai/pi-hindsight/extensions/client/timeout.ts +0 -34
- package/node_modules/@luxusai/pi-hindsight/extensions/config/config-defaults.ts +0 -140
- package/node_modules/@luxusai/pi-hindsight/extensions/config/config-editing-model.ts +0 -61
- package/node_modules/@luxusai/pi-hindsight/extensions/config/config-editing-registry.ts +0 -925
- package/node_modules/@luxusai/pi-hindsight/extensions/config/config-field-paths.ts +0 -247
- package/node_modules/@luxusai/pi-hindsight/extensions/config/config-normalize.ts +0 -547
- package/node_modules/@luxusai/pi-hindsight/extensions/config/config-writer.ts +0 -480
- package/node_modules/@luxusai/pi-hindsight/extensions/config/config.ts +0 -182
- package/node_modules/@luxusai/pi-hindsight/extensions/config/setup-gate.ts +0 -86
- package/node_modules/@luxusai/pi-hindsight/extensions/imports/import-execute.ts +0 -1010
- package/node_modules/@luxusai/pi-hindsight/extensions/imports/import-parse.ts +0 -175
- package/node_modules/@luxusai/pi-hindsight/extensions/imports/import-plan.ts +0 -425
- package/node_modules/@luxusai/pi-hindsight/extensions/imports/import-presentation.ts +0 -210
- package/node_modules/@luxusai/pi-hindsight/extensions/imports/import-sessions.ts +0 -817
- package/node_modules/@luxusai/pi-hindsight/extensions/index.ts +0 -25
- package/node_modules/@luxusai/pi-hindsight/extensions/lifecycle/git-seed.ts +0 -319
- package/node_modules/@luxusai/pi-hindsight/extensions/lifecycle/memory-lifecycle-recall.ts +0 -196
- package/node_modules/@luxusai/pi-hindsight/extensions/lifecycle/memory-lifecycle-retain.ts +0 -189
- package/node_modules/@luxusai/pi-hindsight/extensions/lifecycle/memory-lifecycle-runtime.ts +0 -65
- package/node_modules/@luxusai/pi-hindsight/extensions/lifecycle/memory-lifecycle.ts +0 -260
- package/node_modules/@luxusai/pi-hindsight/extensions/lifecycle/mental-models.ts +0 -244
- package/node_modules/@luxusai/pi-hindsight/extensions/lifecycle/observation-scopes.ts +0 -59
- package/node_modules/@luxusai/pi-hindsight/extensions/lifecycle/recall-cleanup.ts +0 -82
- package/node_modules/@luxusai/pi-hindsight/extensions/lifecycle/recall-visibility.ts +0 -45
- package/node_modules/@luxusai/pi-hindsight/extensions/lifecycle/recall.ts +0 -331
- package/node_modules/@luxusai/pi-hindsight/extensions/lifecycle/retain-cursor.ts +0 -354
- package/node_modules/@luxusai/pi-hindsight/extensions/lifecycle/retain-job-builder.ts +0 -66
- package/node_modules/@luxusai/pi-hindsight/extensions/lifecycle/retain-receipts.ts +0 -132
- package/node_modules/@luxusai/pi-hindsight/extensions/lifecycle/retain.ts +0 -228
- package/node_modules/@luxusai/pi-hindsight/extensions/operations/memory-bank-template-operations.ts +0 -74
- package/node_modules/@luxusai/pi-hindsight/extensions/operations/memory-config-operations.ts +0 -16
- package/node_modules/@luxusai/pi-hindsight/extensions/operations/memory-control-operations.ts +0 -648
- package/node_modules/@luxusai/pi-hindsight/extensions/operations/memory-diagnostics-operations.ts +0 -105
- package/node_modules/@luxusai/pi-hindsight/extensions/operations/memory-identity.ts +0 -68
- package/node_modules/@luxusai/pi-hindsight/extensions/operations/memory-operation-service.ts +0 -71
- package/node_modules/@luxusai/pi-hindsight/extensions/operations/memory-operation-types.ts +0 -13
- package/node_modules/@luxusai/pi-hindsight/extensions/operations/memory-recall-operations.ts +0 -162
- package/node_modules/@luxusai/pi-hindsight/extensions/operations/memory-retain-operations.ts +0 -131
- package/node_modules/@luxusai/pi-hindsight/extensions/operations/memory-scope.ts +0 -104
- package/node_modules/@luxusai/pi-hindsight/extensions/operations/memory-session-operations.ts +0 -37
- package/node_modules/@luxusai/pi-hindsight/extensions/operations/operation-catalog.ts +0 -893
- package/node_modules/@luxusai/pi-hindsight/extensions/operations/reflect-presenter.ts +0 -35
- package/node_modules/@luxusai/pi-hindsight/extensions/operations/scope-migrate.ts +0 -177
- package/node_modules/@luxusai/pi-hindsight/extensions/operations/tools.ts +0 -7
- package/node_modules/@luxusai/pi-hindsight/extensions/queue/flush-presenter.ts +0 -21
- package/node_modules/@luxusai/pi-hindsight/extensions/queue/jsonl-queue-store.ts +0 -112
- package/node_modules/@luxusai/pi-hindsight/extensions/queue/queue-delivery.ts +0 -111
- package/node_modules/@luxusai/pi-hindsight/extensions/queue/queue-lock.ts +0 -214
- package/node_modules/@luxusai/pi-hindsight/extensions/queue/queue-operations.ts +0 -70
- package/node_modules/@luxusai/pi-hindsight/extensions/queue/queue.ts +0 -392
- package/node_modules/@luxusai/pi-hindsight/extensions/tui/bank-template-presentation.ts +0 -45
- package/node_modules/@luxusai/pi-hindsight/extensions/tui/commands.ts +0 -9
- package/node_modules/@luxusai/pi-hindsight/extensions/tui/guided-setup.ts +0 -860
- package/node_modules/@luxusai/pi-hindsight/extensions/tui/prefill-input.ts +0 -105
- package/node_modules/@luxusai/pi-hindsight/extensions/tui/setup-flow.ts +0 -189
- package/node_modules/@luxusai/pi-hindsight/extensions/tui/setup-server-probe.ts +0 -299
- package/node_modules/@luxusai/pi-hindsight/extensions/tui/setup-tui-actions.ts +0 -201
- package/node_modules/@luxusai/pi-hindsight/extensions/tui/setup-tui-facts.ts +0 -47
- package/node_modules/@luxusai/pi-hindsight/extensions/tui/setup-tui-render.ts +0 -239
- package/node_modules/@luxusai/pi-hindsight/extensions/tui/setup-tui-types.ts +0 -50
- package/node_modules/@luxusai/pi-hindsight/extensions/tui/setup-tui.ts +0 -259
- package/node_modules/@luxusai/pi-hindsight/extensions/tui/tool-presenters.ts +0 -77
- package/node_modules/@luxusai/pi-hindsight/extensions/types.ts +0 -534
- package/node_modules/@luxusai/pi-hindsight/extensions/utils/diagnostics.ts +0 -319
- package/node_modules/@luxusai/pi-hindsight/extensions/utils/messages.ts +0 -325
- package/node_modules/@luxusai/pi-hindsight/extensions/utils/sanitize.ts +0 -42
- package/node_modules/@luxusai/pi-hindsight/extensions/utils/session-memory-meta.ts +0 -244
- package/node_modules/@luxusai/pi-hindsight/extensions/utils/session-operations.ts +0 -56
- package/node_modules/@luxusai/pi-hindsight/extensions/utils/session.ts +0 -50
- package/node_modules/@luxusai/pi-hindsight/extensions/utils/status-fields.ts +0 -167
- package/node_modules/@luxusai/pi-hindsight/extensions/utils/status-health.ts +0 -207
- package/node_modules/@luxusai/pi-hindsight/extensions/utils/status.ts +0 -120
- package/node_modules/@luxusai/pi-hindsight/extensions/version.ts +0 -8
- package/node_modules/@luxusai/pi-hindsight/package.json +0 -118
- package/node_modules/@luxusai/pi-hindsight/skills/hindsight-memory-doctor/SKILL.md +0 -75
|
@@ -1,3 +1,8 @@
|
|
|
1
|
+
import * as fs from "node:fs";
|
|
2
|
+
import { retagAuxiliaryDiagnostics } from "./dispatch/auxiliary-lsp.js";
|
|
3
|
+
import { applyFindingPolicy, loadProjectRulePolicyMap, renderedRuleIdentities, } from "./dispatch/finding-policy.js";
|
|
4
|
+
import { detectFileRole } from "./file-role.js";
|
|
5
|
+
import { logLatency } from "./latency-logger.js";
|
|
1
6
|
import { convertLspDiagnostics } from "./dispatch/utils/lsp-diagnostics.js";
|
|
2
7
|
import { toRunnerDisplayPath } from "./dispatch/runner-context.js";
|
|
3
8
|
export function formatCascadeNeighborDiagnostics(cwd, neighbors, options = {}) {
|
|
@@ -60,20 +65,64 @@ export function formatCascadeNeighborDiagnostics(cwd, neighbors, options = {}) {
|
|
|
60
65
|
* Lives here rather than inline in the quiet-window callback so the delivery
|
|
61
66
|
* path (reconcile → run → turn_end) is testable end to end; index.ts only wires
|
|
62
67
|
* it to `runtime.appendCascadeRun`.
|
|
68
|
+
*
|
|
69
|
+
* #3102: the survivors are what the agent READS, so they go through the same
|
|
70
|
+
* `clients/dispatch/finding-policy.ts` stack — inline `pi-lens-ignore` → stored
|
|
71
|
+
* dispositions → the project's `.pi-lens.json` rule policy — that the per-edit
|
|
72
|
+
* dispatcher, `mode=full` and the `source=lsp` probe lane apply. Without it a
|
|
73
|
+
* cold-neighbour ERROR the agent already marked `false-positive` came back on
|
|
74
|
+
* every quiet-window reconcile.
|
|
63
75
|
*/
|
|
64
76
|
export function buildResolvedFoundCascadeRun(cwd, neighbor) {
|
|
65
77
|
const { filePath } = neighbor;
|
|
66
|
-
const
|
|
67
|
-
if (
|
|
78
|
+
const errors = neighbor.diagnostics.filter((d) => d.severity === 1);
|
|
79
|
+
if (errors.length === 0)
|
|
68
80
|
return undefined;
|
|
81
|
+
const policyStart = Date.now();
|
|
82
|
+
// This lane's roots are one and the same: index.ts hands `runtime.projectRoot`
|
|
83
|
+
// in as `cwd`, so the retag's config lookup and the disposition/rule-policy
|
|
84
|
+
// store read the same directory. The in-lane cascade's do NOT — see
|
|
85
|
+
// `applyCascadeDisplayPolicy`.
|
|
86
|
+
const { diagnostics, suppressed, auxSuppressed, total } = applyCascadeDisplayPolicy(errors, {
|
|
87
|
+
cwd,
|
|
88
|
+
policyRoot: cwd,
|
|
89
|
+
filePath,
|
|
90
|
+
});
|
|
91
|
+
// One bounded record per RUN, never one per finding (AGENTS.md "bounded
|
|
92
|
+
// observability"). This is a PUSH surface: silence after a mark is the mark
|
|
93
|
+
// working, not a clean verdict, so the count is recorded here rather than
|
|
94
|
+
// re-announced to the agent on every reconcile. `durationMs` covers the
|
|
95
|
+
// content read too — the latency this fold added to the quiet-window
|
|
96
|
+
// callback, measurable in the same per-phase record as every other cost.
|
|
97
|
+
recordCascadeFindingPolicy({
|
|
98
|
+
filePath,
|
|
99
|
+
durationMs: Date.now() - policyStart,
|
|
100
|
+
suppressed,
|
|
101
|
+
total,
|
|
102
|
+
auxSuppressed,
|
|
103
|
+
});
|
|
104
|
+
// No zero-length early return here: `formatCascadeNeighborDiagnostics`
|
|
105
|
+
// renders "" for a neighbour with no diagnostics and the `!formatted` guard
|
|
106
|
+
// below already returns `undefined` for it — a second check was mutation-
|
|
107
|
+
// inert (M7: deleting it left all 7 cascade cases green).
|
|
69
108
|
const neighbors = [
|
|
70
109
|
{ filePath, reason: "references", diagnostics, lspTouched: true },
|
|
71
110
|
];
|
|
72
|
-
const
|
|
111
|
+
const rendered = formatCascadeNeighborDiagnostics(cwd, neighbors, {
|
|
73
112
|
noun: "cold neighbor",
|
|
74
113
|
});
|
|
75
|
-
if (!
|
|
114
|
+
if (!rendered)
|
|
76
115
|
return undefined;
|
|
116
|
+
// #1616 / #3102 AC 4, round 2 F2: a delivery that still has something to say
|
|
117
|
+
// states what it dropped, once per delivery — the same sentence the
|
|
118
|
+
// late-auxiliary advisory renders (`clients/runtime-turn.ts`). Policy drops
|
|
119
|
+
// only: an aux drop is the file's own suppression comment, which the
|
|
120
|
+
// per-edit dispatch path honours silently too, and it stays in the record
|
|
121
|
+
// above. A delivery with NOTHING left says nothing at all — silence on a
|
|
122
|
+
// push surface is not a claim that the neighbour is clean.
|
|
123
|
+
const formatted = suppressed > 0
|
|
124
|
+
? `${rendered}\nsuppressed by disposition: ${suppressed} finding(s) (marked false-positive or won't-fix).`
|
|
125
|
+
: rendered;
|
|
77
126
|
return {
|
|
78
127
|
filePath,
|
|
79
128
|
result: {
|
|
@@ -93,3 +142,150 @@ export function buildResolvedFoundCascadeRun(cwd, neighbor) {
|
|
|
93
142
|
diagnosticCount: diagnostics.length,
|
|
94
143
|
};
|
|
95
144
|
}
|
|
145
|
+
/**
|
|
146
|
+
* The ONE convert → retag → `applyFindingPolicy` derivation every cascade
|
|
147
|
+
* DISPLAY lane runs before its diagnostics reach the agent (#3102/#3157).
|
|
148
|
+
*
|
|
149
|
+
* Five lanes render `CascadeNeighborResult.diagnostics` through
|
|
150
|
+
* `formatCascadeNeighborDiagnostics`: the quiet-window builder above, and the
|
|
151
|
+
* four in-lane sites in `clients/dispatch/integration.ts` (passive cold
|
|
152
|
+
* snapshot, fresh touch, touch-error fallback, degraded fallback). Each one
|
|
153
|
+
* used to open-code `convertLspDiagnostics(...)` and render the result raw, so
|
|
154
|
+
* a neighbour ERROR the agent had marked `false-positive` was hidden by
|
|
155
|
+
* `mode=delta`/`mode=full`/the per-edit dispatcher/the probe lane and STILL
|
|
156
|
+
* re-reported by the cascade block on every edit that cascaded to it (#3157).
|
|
157
|
+
*
|
|
158
|
+
* TWO ROOTS, deliberately separate (the #1030 rule):
|
|
159
|
+
* - `cwd` is the LANGUAGE root, and only `retagAuxiliaryDiagnostics` sees it
|
|
160
|
+
* — `profile.allowBlocking(cwd)` looks for a local opengrep/zizmor/typos
|
|
161
|
+
* config, exactly as the per-edit runner (`runners/lsp.ts`) passes
|
|
162
|
+
* `ctx.cwd`.
|
|
163
|
+
* - `policyRoot` is the PROJECT root, and the disposition store and the
|
|
164
|
+
* `.pi-lens.json` rule map are read from it — `lens_diagnostic_mark` writes
|
|
165
|
+
* under `runtime.projectRoot` (`index.ts`), so reading from a nested
|
|
166
|
+
* language root in a monorepo opens a different
|
|
167
|
+
* `diagnostic-dispositions.json` and silently no-ops every mark. This is
|
|
168
|
+
* what `dispatcher.ts` already does with `ctx.projectRoot ?? ctx.cwd`.
|
|
169
|
+
*
|
|
170
|
+
* The auxiliary drop set (native `# nosemgrep`-style comments, `skipTestFiles`)
|
|
171
|
+
* is KEPT: every caller's diagnostics come straight off a client cache or a
|
|
172
|
+
* `touchFile` result, so nothing upstream ran `applyAuxiliarySuppressions`
|
|
173
|
+
* (only the `mode=full` workspace sweep does, `clients/lsp/index.ts`) and this
|
|
174
|
+
* is the FIRST application, not a double-apply — which is why the probe lane
|
|
175
|
+
* ignores it and these lanes do not.
|
|
176
|
+
*
|
|
177
|
+
* No `range.start.line` pre-partition here, unlike the late-auxiliary drain,
|
|
178
|
+
* whose own comment gives the alignment reason (`clients/runtime-turn.ts`,
|
|
179
|
+
* above its `anchored` filter): `convertLspDiagnostics` drops line-less
|
|
180
|
+
* entries, which would break the 1:1 index pairing `retagAuxiliaryDiagnostics`
|
|
181
|
+
* needs. That reason holds here too — it is simply already satisfied. Every
|
|
182
|
+
* caller reads the same `client.getAllDiagnostics()` / `touchFile` map, whose
|
|
183
|
+
* `mergeDiagnosticLists` (`clients/lsp/client.ts`) dereferences
|
|
184
|
+
* `diagnostic.range.start.line` unguarded, so a line-less entry throws long
|
|
185
|
+
* before any builder sees it and `converted` is always 1:1 with `errors`.
|
|
186
|
+
*
|
|
187
|
+
* Reads nothing when there is nothing to render, and never logs: the RECORD is
|
|
188
|
+
* the caller's decision, because the in-lane lane walks up to
|
|
189
|
+
* `CASCADE_NEIGHBOUR_BUDGET` neighbours per edit and must aggregate one row per
|
|
190
|
+
* run rather than one per neighbour.
|
|
191
|
+
*/
|
|
192
|
+
export function applyCascadeDisplayPolicy(
|
|
193
|
+
/** ERROR-severity raw diagnostics, already capped by the caller's own
|
|
194
|
+
* display cap (`MAX_PER_FILE` in-lane; uncapped for the single-neighbour
|
|
195
|
+
* quiet-window run). */
|
|
196
|
+
errors, options) {
|
|
197
|
+
if (errors.length === 0) {
|
|
198
|
+
return { diagnostics: [], suppressed: 0, auxSuppressed: 0, total: 0 };
|
|
199
|
+
}
|
|
200
|
+
const { cwd, policyRoot, filePath } = options;
|
|
201
|
+
// `undefined` → the fail-open empty string: inline suppression becomes a
|
|
202
|
+
// no-op and a STRICT `false-positive` anchor hashes an empty line, so a
|
|
203
|
+
// finding is never hidden by an I/O error (AGENTS.md shape 48).
|
|
204
|
+
const content = options.content ?? readNeighborContent(filePath);
|
|
205
|
+
const converted = convertLspDiagnostics(errors, filePath);
|
|
206
|
+
// #692/#3046: identity comes from the ONE shared derivation every other
|
|
207
|
+
// surface anchors a mark against — never a hardcoded `tool: "lsp"`.
|
|
208
|
+
const retained = retagAuxiliaryDiagnostics(converted, errors, content ?? "", {
|
|
209
|
+
cwd,
|
|
210
|
+
fileRole: detectFileRole(filePath, content),
|
|
211
|
+
});
|
|
212
|
+
const { kept } = applyFindingPolicy(retained, {
|
|
213
|
+
cwd: policyRoot,
|
|
214
|
+
filePath,
|
|
215
|
+
content: content ?? "",
|
|
216
|
+
policyMap: loadProjectRulePolicyMap(policyRoot),
|
|
217
|
+
identities: renderedRuleIdentities,
|
|
218
|
+
});
|
|
219
|
+
return {
|
|
220
|
+
diagnostics: options.displayCap === undefined
|
|
221
|
+
? kept
|
|
222
|
+
: kept.slice(0, options.displayCap),
|
|
223
|
+
// Policy drops only, DISJOINT from `auxSuppressed` — the same split the
|
|
224
|
+
// `late_auxiliary_findings` record uses, so one operator reading both
|
|
225
|
+
// records does not have to know that one nests and the other does not.
|
|
226
|
+
suppressed: retained.length - kept.length,
|
|
227
|
+
auxSuppressed: converted.length - retained.length,
|
|
228
|
+
total: converted.length,
|
|
229
|
+
};
|
|
230
|
+
}
|
|
231
|
+
/**
|
|
232
|
+
* The ONE `cascade_finding_policy` row both cascade display lanes write.
|
|
233
|
+
*
|
|
234
|
+
* Gated on EITHER counter (#3102 round 2, F1): gating on the policy count alone
|
|
235
|
+
* made an aux-only drop — an ERROR master rendered, removed by the profile's own
|
|
236
|
+
* `# nosemgrep` / `skipTestFiles` rule — vanish with no row at all, the
|
|
237
|
+
* silent-drop shape this record exists to prevent (AGENTS.md shape 10). The
|
|
238
|
+
* `late_auxiliary_findings` twin reports both counters every drain.
|
|
239
|
+
*/
|
|
240
|
+
export function recordCascadeFindingPolicy(entry) {
|
|
241
|
+
const inputTruncated = entry.inputTruncated ?? 0;
|
|
242
|
+
if (entry.suppressed === 0 &&
|
|
243
|
+
entry.auxSuppressed === 0 &&
|
|
244
|
+
inputTruncated === 0)
|
|
245
|
+
return;
|
|
246
|
+
logLatency({
|
|
247
|
+
type: "phase",
|
|
248
|
+
toolName: "cascade",
|
|
249
|
+
filePath: entry.filePath,
|
|
250
|
+
phase: "cascade_finding_policy",
|
|
251
|
+
durationMs: entry.durationMs,
|
|
252
|
+
metadata: {
|
|
253
|
+
suppressed: entry.suppressed,
|
|
254
|
+
total: entry.total,
|
|
255
|
+
auxSuppressed: entry.auxSuppressed,
|
|
256
|
+
...(inputTruncated > 0 && { inputTruncated }),
|
|
257
|
+
},
|
|
258
|
+
});
|
|
259
|
+
}
|
|
260
|
+
/**
|
|
261
|
+
* The neighbour's current bytes, for the two content-bound halves of the
|
|
262
|
+
* policy stack (inline `pi-lens-ignore` and the STRICT `false-positive`
|
|
263
|
+
* anchor). One synchronous read, paid only once a neighbour actually has ERROR
|
|
264
|
+
* diagnostics to render — the same read `mode=full` pays per flagged file and
|
|
265
|
+
* the probe lane pays per cache replay.
|
|
266
|
+
*
|
|
267
|
+
* SYNC here, while the in-lane cascade's own neighbour read at
|
|
268
|
+
* `clients/dispatch/integration.ts` (the active touch, under its "A6: async
|
|
269
|
+
* read to avoid blocking event loop on network-mounted drives" comment) is
|
|
270
|
+
* ASYNC. The asymmetry is deliberate and measured, not an oversight: that read
|
|
271
|
+
* happens for EVERY actively-touched neighbour in a parallel fan-out, while
|
|
272
|
+
* this one is paid at most once per DELIVERING neighbour — 0.0125 ms per
|
|
273
|
+
* neighbour measured on 40 real 15.6 KiB sources (#3157 AC 3), on loops that
|
|
274
|
+
* already do synchronous fs work (`nodeFs.existsSync` in the degraded
|
|
275
|
+
* fallback, and the `boundToCurrentDisk` verify whenever the producer attached
|
|
276
|
+
* a content hash and its (path, mtime, size) memo misses —
|
|
277
|
+
* `clients/lsp/diagnostic-binding.ts`). An async read here would force the
|
|
278
|
+
* quiet-window builder's synchronous callback to become async for no
|
|
279
|
+
* measurable gain.
|
|
280
|
+
*
|
|
281
|
+
* `undefined` on any failure: the caller degrades to the content-free half
|
|
282
|
+
* rather than hiding a finding it could not identify.
|
|
283
|
+
*/
|
|
284
|
+
function readNeighborContent(filePath) {
|
|
285
|
+
try {
|
|
286
|
+
return fs.readFileSync(filePath, "utf-8");
|
|
287
|
+
}
|
|
288
|
+
catch {
|
|
289
|
+
return undefined;
|
|
290
|
+
}
|
|
291
|
+
}
|
|
@@ -4,6 +4,7 @@ import { toRunnerDisplayPath } from "./dispatch/runner-context.js";
|
|
|
4
4
|
import { displayProjectDataPath, getProjectDataDir } from "./file-utils.js";
|
|
5
5
|
import { normalizeMessage, stableFindingId } from "./finding-identity.js";
|
|
6
6
|
import { normalizeMapKey } from "./path-utils.js";
|
|
7
|
+
import { resolveLensToolName } from "./tool-config.js";
|
|
7
8
|
/**
|
|
8
9
|
* Report-budget precedence (#1777). A hint is an opinion; a warning is a
|
|
9
10
|
* finding. When the report is capped, warnings must be spent first, or an
|
|
@@ -239,7 +240,7 @@ export function appendCodeQualityWarningsHistory(cwd, report) {
|
|
|
239
240
|
* {@link formatActionableWarningsAdvisory} (#2521): this advisory carried the
|
|
240
241
|
* identical hardcoded-legacy-path defect, one cache key over.
|
|
241
242
|
*/
|
|
242
|
-
export function formatCodeQualityWarningsAdvisory(report, cwd) {
|
|
243
|
+
export function formatCodeQualityWarningsAdvisory(report, cwd, host = "pi") {
|
|
243
244
|
if (report.summary.warnings === 0)
|
|
244
245
|
return undefined;
|
|
245
246
|
const topRules = report.summary.topRules
|
|
@@ -261,6 +262,9 @@ export function formatCodeQualityWarningsAdvisory(report, cwd) {
|
|
|
261
262
|
.filter(Boolean)
|
|
262
263
|
.join(", ")
|
|
263
264
|
: "";
|
|
265
|
+
// #2535 F3: a known tool with no mapping on this host resolves to
|
|
266
|
+
// undefined — omit the instruction rather than naming a dead tool.
|
|
267
|
+
const diagnosticsTool = resolveLensToolName("lens_diagnostics", host);
|
|
264
268
|
return [
|
|
265
269
|
`Code-quality warnings introduced/touched this turn: ${report.summary.warnings} across ${report.summary.files} file(s).`,
|
|
266
270
|
tiers
|
|
@@ -269,7 +273,11 @@ export function formatCodeQualityWarningsAdvisory(report, cwd) {
|
|
|
269
273
|
topRules ? `Top rules: ${topRules}` : undefined,
|
|
270
274
|
// #2521: tool route first (`mode=delta` covers the code-quality cache
|
|
271
275
|
// as well as the actionable one), resolved path second.
|
|
272
|
-
|
|
276
|
+
// #2535 F3: omit when unmapped on this host, never name a dead tool
|
|
277
|
+
// (pinned available on both hosts; defensive only).
|
|
278
|
+
diagnosticsTool
|
|
279
|
+
? `Use ${diagnosticsTool} with mode=delta to inspect these warnings.`
|
|
280
|
+
: undefined,
|
|
273
281
|
"No action required unless you are already refactoring these areas.",
|
|
274
282
|
`Raw report (only if you need the JSON): ${displayProjectDataPath(cwd, "cache", "code-quality-warnings.json")}`,
|
|
275
283
|
]
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import { logExtension } from "./extension-log.js";
|
|
3
3
|
import { DEGRADATION_ENTRIES_PER_KIND, LEDGER_FIELD_MAX, truncateForLedger, } from "./ledger-bounds.js";
|
|
4
4
|
import { logLatency } from "./latency-logger.js";
|
|
5
|
-
import { getSinkRotations, getSinkWriteFailures, resetSinkRotations, resetSinkWriteFailures, } from "./ndjson-logger.js";
|
|
5
|
+
import { getSinkRotations, getSinkOptionConflicts, getSinkWriteFailures, resetSinkRotations, resetSinkWriteFailures, } from "./ndjson-logger.js";
|
|
6
6
|
// #2146: pulled at READ time, never pushed. `process-singletons.ts` is a
|
|
7
7
|
// dependency leaf on purpose — it cannot import this module without closing a
|
|
8
8
|
// no-client-cycles cycle through instance-registry/instance-reaper — so the
|
|
@@ -233,6 +233,18 @@ export function getDegradationSummary() {
|
|
|
233
233
|
})),
|
|
234
234
|
});
|
|
235
235
|
}
|
|
236
|
+
const optionConflicts = getSinkOptionConflicts();
|
|
237
|
+
if (optionConflicts.length > 0) {
|
|
238
|
+
summary.push({
|
|
239
|
+
kind: "log-sink-option-conflict",
|
|
240
|
+
count: optionConflicts.length,
|
|
241
|
+
droppedCount: 0,
|
|
242
|
+
latestReasons: optionConflicts.map((sink) => ({
|
|
243
|
+
subject: truncateForLedger(sink.file),
|
|
244
|
+
reason: truncateForLedger("shared writer rotation options differed across module graphs; first writer retained ownership"),
|
|
245
|
+
})),
|
|
246
|
+
});
|
|
247
|
+
}
|
|
236
248
|
// #2146, same read-time fold: process-singleton resets live in the leaf
|
|
237
249
|
// module's own bounded log. One entry per family, so this group's count is
|
|
238
250
|
// the number of families this build could not adopt, never an event tally.
|
|
@@ -310,6 +322,9 @@ const INFORMATIONAL_DEGRADATION_KINDS = new Set([
|
|
|
310
322
|
// doc comment above) and is frequent/self-healing by design — a `⚠` would
|
|
311
323
|
// cry wolf on the sampler's ordinary best-effort data loss.
|
|
312
324
|
"resource-sampler-scanner-escalated",
|
|
325
|
+
// #2874: a successful legacy-directory migration is an upgrade tally, not
|
|
326
|
+
// a call to action. The hash-only subject avoids exposing the project path.
|
|
327
|
+
"data_dir_migrated",
|
|
313
328
|
]);
|
|
314
329
|
export function renderDegradationLines(summary = getDegradationSummary()) {
|
|
315
330
|
if (!isRenderableSummary(summary))
|
|
@@ -78,6 +78,11 @@ import { lineContentHash } from "./read-guard.js";
|
|
|
78
78
|
// below goes through `deferredKey`, which folds the resolved cwd into the Set
|
|
79
79
|
// key, so the two projects can no longer collide.
|
|
80
80
|
const deferredThisSession = new Set();
|
|
81
|
+
let widgetDispositionReconciler = null;
|
|
82
|
+
/** Register the widget's mirror of the durable disposition state. */
|
|
83
|
+
export function registerWidgetDispositionReconciler(reconciler) {
|
|
84
|
+
widgetDispositionReconciler = reconciler;
|
|
85
|
+
}
|
|
81
86
|
function deferredKey(cwd, anchor) {
|
|
82
87
|
return `${normalizeMapKey(cwd)}::${anchor}`;
|
|
83
88
|
}
|
|
@@ -97,7 +102,8 @@ export const normalizeMessage = sharedNormalizeMessage;
|
|
|
97
102
|
// case, symlink/realpath, or slash difference between the two forms silently
|
|
98
103
|
// orphans the agent's own false-positive/flagged mark (a #533 dropped-signal).
|
|
99
104
|
// Canonicalize BOTH inputs through `normalizeMapKey` (the SAME normalizer the
|
|
100
|
-
// read side already relies on — realpathSync.native on Windows
|
|
105
|
+
// read side already relies on — realpathSync.native on Windows, and on POSIX
|
|
106
|
+
// too since #3098) BEFORE computing
|
|
101
107
|
// the relative path, so write and read produce identical anchors regardless of
|
|
102
108
|
// the form the caller held. `normalizeMapKey` is idempotent, so the already-
|
|
103
109
|
// canonicalized read side is unaffected; the realpath I/O is acceptable here
|
|
@@ -357,6 +363,7 @@ function emitMarkTelemetry(cwd, target, disposition, anchor, reason, existing, i
|
|
|
357
363
|
publishDisposition({
|
|
358
364
|
cwd,
|
|
359
365
|
filePath: target.filePath,
|
|
366
|
+
message: target.message,
|
|
360
367
|
disposition,
|
|
361
368
|
tool: target.tool,
|
|
362
369
|
rule: target.rule,
|
|
@@ -390,6 +397,7 @@ export function markDisposition(cwd, target, disposition, reason, identity) {
|
|
|
390
397
|
const existing = readState(cwd).dispositions?.[anchor];
|
|
391
398
|
if (disposition === "defer") {
|
|
392
399
|
deferredThisSession.add(deferredKey(cwd, anchor));
|
|
400
|
+
widgetDispositionReconciler?.(cwd, target, disposition);
|
|
393
401
|
emitMarkTelemetry(cwd, target, disposition, anchor, reason, existing, identity);
|
|
394
402
|
return anchor;
|
|
395
403
|
}
|
|
@@ -407,6 +415,9 @@ export function markDisposition(cwd, target, disposition, reason, identity) {
|
|
|
407
415
|
lineText,
|
|
408
416
|
};
|
|
409
417
|
commitDisposition(cwd, anchor, entry);
|
|
418
|
+
// The durable commit is the guard for this mirror: a failed commit must not
|
|
419
|
+
// make the in-memory widget claim that a mark exists.
|
|
420
|
+
widgetDispositionReconciler?.(cwd, target, disposition);
|
|
410
421
|
emitMarkTelemetry(cwd, target, disposition, anchor, reason, existing, identity);
|
|
411
422
|
return anchor;
|
|
412
423
|
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The effective ast-grep rule catalog for a workspace root (#3053).
|
|
3
|
+
*
|
|
4
|
+
* Before this module, `clients/dispatch/runners/ast-grep-napi.ts` (the NAPI
|
|
5
|
+
* runner) and `clients/dispatch/rule-ignores.ts` (the LSP-seam matcher, #3041)
|
|
6
|
+
* each walked `getAstGrepRuleSources(root)` and resolved rule-id precedence
|
|
7
|
+
* independently — two implementations of the same derivation. They had
|
|
8
|
+
* already drifted once by the time #3053 was filed: #3046 round 2 found the
|
|
9
|
+
* LSP seam claiming an id only when the winning document declared `ignores`,
|
|
10
|
+
* so a project rule that redefines a bundled id with NO `ignores` fired
|
|
11
|
+
* per-edit (the runner) but stayed carved out over LSP (the seam) — the same
|
|
12
|
+
* defect the whole PR existed to remove, just inverted. This module is the
|
|
13
|
+
* one place that walk and that precedence rule now live.
|
|
14
|
+
*
|
|
15
|
+
* Precedence: sources are walked in `getAstGrepRuleSources`'s order (project
|
|
16
|
+
* primary, project secondary, bundled primary, bundled secondary), each
|
|
17
|
+
* loaded with the same loader the runner always used (`loadYamlRulesFresh`
|
|
18
|
+
* for mutable project trees, `loadYamlRules`'s mtime-cached path for
|
|
19
|
+
* immutable bundled catalogs). The FIRST rule sighted for an id — scanning
|
|
20
|
+
* sources in order, and each source's own rules in file order — claims it;
|
|
21
|
+
* no later document for the same id is ever consulted.
|
|
22
|
+
*
|
|
23
|
+
* Deliberately does NOT special-case an id that recurs WITHIN one source's
|
|
24
|
+
* own rule list. That is `duplicateRuleIds` in ast-grep-napi.ts, a
|
|
25
|
+
* runner-only concern layered on top of this catalog's plain result: the
|
|
26
|
+
* runner drops such an id entirely (one Duplicate-rule-id diagnostic, no
|
|
27
|
+
* rule fires for it), but `materializeMergedRuleDir` (clients/sgconfig.ts) —
|
|
28
|
+
* what ast-grep's own LSP binary actually runs — keeps every document an
|
|
29
|
+
* EARLIER source hasn't already claimed, so both copies of a within-source
|
|
30
|
+
* duplicate still survive into what ast-grep publishes over LSP. Baking the
|
|
31
|
+
* runner's drop into this catalog would leave those published findings with
|
|
32
|
+
* no registered `ignores` at all, silently unsuppressed — reopening #3041 for
|
|
33
|
+
* that id (#3046 round 2 F7). So the catalog resolves a within-source
|
|
34
|
+
* duplicate to its first-sighted document like any other collision, and it
|
|
35
|
+
* is each consumer's job to decide what a within-source duplicate means for
|
|
36
|
+
* it — the runner still computes `duplicateRuleIds` itself and skips those
|
|
37
|
+
* ids before ever consulting this catalog's answer for them.
|
|
38
|
+
*/
|
|
39
|
+
import { getAstGrepRuleSources } from "../sgconfig.js";
|
|
40
|
+
import { loadYamlRules, loadYamlRulesFresh, } from "./runners/yaml-rule-parser.js";
|
|
41
|
+
/**
|
|
42
|
+
* Build the effective ast-grep catalog for `root`. See the module docstring
|
|
43
|
+
* for the precedence rule and the within-source-duplicate decision.
|
|
44
|
+
*
|
|
45
|
+
* No try/catch around either loader: every I/O path inside them already
|
|
46
|
+
* swallows its own failure (missing dir, unreadable readdir, unreadable file
|
|
47
|
+
* all yield an empty list — `yaml-rule-parser.ts`'s `findYamlRuleFiles` and
|
|
48
|
+
* `loadYamlRuleFiles`), so a catch here would be a guard no mutation can red.
|
|
49
|
+
*/
|
|
50
|
+
export function buildEffectiveAstGrepCatalog(root) {
|
|
51
|
+
const sources = [];
|
|
52
|
+
const effectiveRules = new Map();
|
|
53
|
+
for (const source of getAstGrepRuleSources(root)) {
|
|
54
|
+
const rules = source.origin === "project"
|
|
55
|
+
? loadYamlRulesFresh(source.dir)
|
|
56
|
+
: loadYamlRules(source.dir);
|
|
57
|
+
sources.push({ source, rules });
|
|
58
|
+
for (const rule of rules) {
|
|
59
|
+
if (effectiveRules.has(rule.id))
|
|
60
|
+
continue;
|
|
61
|
+
effectiveRules.set(rule.id, { rule, source });
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
return { sources, effectiveRules };
|
|
65
|
+
}
|
|
@@ -22,6 +22,7 @@ import { findLocalOpengrepConfig } from "../opengrep-config.js";
|
|
|
22
22
|
import { findLocalTyposConfig } from "../typos-config.js";
|
|
23
23
|
import { findLocalZizmorConfig } from "../zizmor-config.js";
|
|
24
24
|
import { classifyDefect } from "./diagnostic-taxonomy.js";
|
|
25
|
+
import { isRuleIgnoredForPath, loadRuleIgnorePatterns, } from "./rule-ignores.js";
|
|
25
26
|
/**
|
|
26
27
|
* Semgrep/opengrep `# nosemgrep` / `# nosemgrep: <rule-id>[,<rule-id>]` inline
|
|
27
28
|
* suppression (#441). A bare `# nosemgrep` drops every finding on its line; the
|
|
@@ -248,8 +249,22 @@ export function isAuxiliaryDiagnosticSuppressed(d, content) {
|
|
|
248
249
|
* files that are suppressed per-edit reappeared wholesale in every
|
|
249
250
|
* `mode=full` sweep. Omitting `opts` (or `opts.fileRole`) keeps every existing
|
|
250
251
|
* 2-arg call site's behavior byte-for-byte unchanged.
|
|
252
|
+
*
|
|
253
|
+
* #3041: `opts.filePath` + `opts.scanRoot` additionally apply each rule's own
|
|
254
|
+
* `ignores` path carve-out (#965) — the THIRD member of the same defect class
|
|
255
|
+
* as #586 and #692 above. `ast-grep scan` applies `ignores` during its own
|
|
256
|
+
* project walk, but `ast-grep lsp` publishes per-document diagnostics WITHOUT
|
|
257
|
+
* applying them (measured against ast-grep 0.45.3), so a rule the NAPI runner
|
|
258
|
+
* correctly skips on `scripts/**` came back over LSP on every `source=lsp`
|
|
259
|
+
* query and every `mode=full` sweep. Both routes reach this function, so the
|
|
260
|
+
* carve-out belongs here rather than in either route's own output merge.
|
|
251
261
|
*/
|
|
252
262
|
export function applyAuxiliarySuppressions(diagnostics, content, opts) {
|
|
263
|
+
// One catalog read per call, not per diagnostic. Both rule loaders behind it
|
|
264
|
+
// are cached (~0.09 ms per call on pi-lens's own catalog), so a per-file call
|
|
265
|
+
// from a sweep costs the same order as the NAPI runner's own per-file load.
|
|
266
|
+
const { filePath, scanRoot } = opts ?? {};
|
|
267
|
+
const ruleIgnores = filePath && scanRoot ? loadRuleIgnorePatterns(scanRoot) : undefined;
|
|
253
268
|
return diagnostics.filter((d) => {
|
|
254
269
|
if (isAuxiliaryDiagnosticSuppressed(d, content))
|
|
255
270
|
return false;
|
|
@@ -258,6 +273,19 @@ export function applyAuxiliarySuppressions(diagnostics, content, opts) {
|
|
|
258
273
|
if (profile?.skipTestFiles)
|
|
259
274
|
return false;
|
|
260
275
|
}
|
|
276
|
+
// Keyed by the diagnostic's own rule id — ast-grep's LSP reports the bare
|
|
277
|
+
// catalog id as `code`. Gated on the producing tool as well: the map is
|
|
278
|
+
// built from the AST-GREP catalog, so without the gate any auxiliary whose
|
|
279
|
+
// own rule id happened to collide with a catalog id (several are generic —
|
|
280
|
+
// `no-raw-types`, `no-string-concat-in-loop`) would be silently dropped on
|
|
281
|
+
// a path an unrelated rule carved out. Losing a finding silently is the
|
|
282
|
+
// harm this whole change exists to stop, so the collision is gated, not
|
|
283
|
+
// accepted (#3041 r2 F4).
|
|
284
|
+
if (ruleIgnores !== undefined &&
|
|
285
|
+
findAuxiliaryProfileForSource(d.source)?.tool === "ast-grep" &&
|
|
286
|
+
isRuleIgnoredForPath(filePath, scanRoot, ruleIgnores.get(String(d.code)))) {
|
|
287
|
+
return false;
|
|
288
|
+
}
|
|
261
289
|
return true;
|
|
262
290
|
});
|
|
263
291
|
}
|
|
@@ -26,7 +26,7 @@ import { getPrimaryDispatchGroup } from "../language-policy.js";
|
|
|
26
26
|
import { resolveLanguageRootForFile } from "../language-profile.js";
|
|
27
27
|
import { logLatency, phaseFinished, phaseStarted } from "../latency-logger.js";
|
|
28
28
|
import { isSpawnableCommand } from "../installer/index.js";
|
|
29
|
-
import { isAbsoluteFilePath, normalizeEphemeralMapKey, normalizeMapKey, } from "../path-utils.js";
|
|
29
|
+
import { isAbsoluteFilePath, isWindowsPath, normalizeEphemeralMapKey, normalizeMapKey, } from "../path-utils.js";
|
|
30
30
|
import { loadPiLensProjectConfig } from "../project-lens-config.js";
|
|
31
31
|
import { RUNTIME_CONFIG, getRunnerTimeoutFloorMs } from "../runtime-config.js";
|
|
32
32
|
import { probeToolAsync } from "../tool-probe.js";
|
|
@@ -249,7 +249,13 @@ telemetryModel, telemetryProvider) {
|
|
|
249
249
|
const normalizedProjectRoot = normalizeMapKey(path.resolve(projectRoot ?? cwd));
|
|
250
250
|
const normalizedCwd = normalizeMapKey(resolveLanguageRootForFile(absoluteFilePath, cwd));
|
|
251
251
|
const normalizedFilePath = normalizeMapKey(absoluteFilePath);
|
|
252
|
-
|
|
252
|
+
// `resolveRunnerPath` intentionally normalizes map keys, which folds a
|
|
253
|
+
// literal POSIX backslash into a separator. Preserve that one proven
|
|
254
|
+
// classifier distinction while still resolving relative inputs from `cwd`.
|
|
255
|
+
const kindFilePath = !isWindowsPath(filePath) && filePath.includes("\\")
|
|
256
|
+
? path.resolve(cwd, filePath)
|
|
257
|
+
: absoluteFilePath;
|
|
258
|
+
const kind = detectFileKind(kindFilePath);
|
|
253
259
|
const contentPrefix = readFilePrefix(normalizedFilePath);
|
|
254
260
|
const fileRole = detectFileRole(normalizedFilePath, contentPrefix);
|
|
255
261
|
// Captured once here so the generated short-circuit below can emit a
|
|
@@ -282,6 +288,7 @@ telemetryModel, telemetryProvider) {
|
|
|
282
288
|
writeIndex,
|
|
283
289
|
telemetryModel,
|
|
284
290
|
telemetryProvider,
|
|
291
|
+
toolCwdMemo: {},
|
|
285
292
|
async hasTool(command) {
|
|
286
293
|
return checkToolAvailability(command, facts);
|
|
287
294
|
},
|