@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
|
@@ -19,7 +19,7 @@ import * as nodeFs from "node:fs";
|
|
|
19
19
|
import * as nodePath from "node:path";
|
|
20
20
|
import { fileURLToPath } from "node:url";
|
|
21
21
|
import { CASCADE_NEIGHBOUR_BUDGET, deriveCascadeNeighbourBudget, } from "../cascade-budget.js";
|
|
22
|
-
import { formatCascadeNeighborDiagnostics } from "../cascade-format.js";
|
|
22
|
+
import { applyCascadeDisplayPolicy, formatCascadeNeighborDiagnostics, recordCascadeFindingPolicy, } from "../cascade-format.js";
|
|
23
23
|
import { logCascade } from "../cascade-logger.js";
|
|
24
24
|
import { getDiagnosticTracker } from "../diagnostic-tracker.js";
|
|
25
25
|
import { classifyCascadeWaitTier, isTierAwareCascadeEnabled, recordOutstandingCascadeTouch, } from "../lsp/cascade-tier.js";
|
|
@@ -434,6 +434,25 @@ function ensureCascadeTurnScope(turnSeq) {
|
|
|
434
434
|
}
|
|
435
435
|
const MAX_PER_FILE = RUNTIME_CONFIG.pipeline.cascadeMaxDiagnosticsPerFile;
|
|
436
436
|
const MAX_FILES = RUNTIME_CONFIG.pipeline.cascadeMaxFiles;
|
|
437
|
+
/**
|
|
438
|
+
* How many of ONE neighbour's ERRORs the display policy may look at (#3157
|
|
439
|
+
* round 2, F1). `MAX_PER_FILE` is the DISPLAY cap and now applies to the
|
|
440
|
+
* survivors, so this is the separate, deliberately generous bound on the
|
|
441
|
+
* policy's INPUT — four full display caps of headroom, so a neighbour would
|
|
442
|
+
* need 80 suppressed findings before a genuine one could fall off the end
|
|
443
|
+
* again.
|
|
444
|
+
*
|
|
445
|
+
* It exists because the policy's cost is linear in the finding count once a
|
|
446
|
+
* project has ANY disposition mark (the zero-I/O hoist in
|
|
447
|
+
* `finding-policy.ts` stops firing and every finding expands into its
|
|
448
|
+
* candidate identities): measured on 40 real neighbours, 1.31 ms/neighbour at
|
|
449
|
+
* 20 findings, 4.74 ms at 80, and 28.29 ms at 500 — that last one is 1131
|
|
450
|
+
* ms/edit across a full 40-neighbour walk, a quarter of the `cascadeSettleWaitMs`
|
|
451
|
+
* window, for a neighbour nobody can read anyway. Truncation here is COUNTED
|
|
452
|
+
* (`inputTruncated` on the `cascade_finding_policy` row), because an input
|
|
453
|
+
* bound whose loss is invisible is exactly the defect F1 found.
|
|
454
|
+
*/
|
|
455
|
+
const MAX_POLICY_INPUT_PER_FILE = MAX_PER_FILE * 4;
|
|
437
456
|
/**
|
|
438
457
|
* The genuine language-server ERROR diagnostics from a cascade neighbor
|
|
439
458
|
* re-check, ready to reconcile into the footer widget (#1093).
|
|
@@ -455,8 +474,14 @@ const MAX_FILES = RUNTIME_CONFIG.pipeline.cascadeMaxFiles;
|
|
|
455
474
|
* entries untouched. This keeps `isLspErrorEntry`'s "tool === 'lsp' uniquely
|
|
456
475
|
* identifies a genuine language-server entry" contract true for what we write.
|
|
457
476
|
*
|
|
458
|
-
* The DISPLAY list
|
|
459
|
-
*
|
|
477
|
+
* The DISPLAY list is no longer "left as-is" as it was for #1093: since #3157
|
|
478
|
+
* every display site runs `cascadeDisplay` → `applyCascadeDisplayPolicy`, which
|
|
479
|
+
* applies the finding-policy stack AND keeps the auxiliary retag's own drop set
|
|
480
|
+
* — so the display drops natively-suppressed aux findings while still RENDERING
|
|
481
|
+
* the aux findings this widget writer excludes wholesale. The two remain
|
|
482
|
+
* deliberately different: this one protects `isLspErrorEntry`'s "tool === 'lsp'"
|
|
483
|
+
* contract for PERSISTED widget state, which the ephemeral display list never
|
|
484
|
+
* touches.
|
|
460
485
|
*/
|
|
461
486
|
function cascadeReconcilableLspErrors(rawDiags, neighborPath) {
|
|
462
487
|
return convertLspDiagnostics(rawDiags
|
|
@@ -732,7 +757,7 @@ export async function computeCascadeForFile(filePath, cwd, options = {}) {
|
|
|
732
757
|
// time that elapsed before turn_end began waiting.
|
|
733
758
|
const cascadeStart = Date.now();
|
|
734
759
|
try {
|
|
735
|
-
const { hasBlockers = false, dbg, turnSeq = 0, writeSeq, seqState, turnEndCascadeSettleStart, fileContent, wordIndex, onWordIndexUpdated, } = options;
|
|
760
|
+
const { hasBlockers = false, dbg, turnSeq = 0, writeSeq, projectRoot, seqState, turnEndCascadeSettleStart, fileContent, wordIndex, onWordIndexUpdated, } = options;
|
|
736
761
|
ensureCascadeTurnScope(turnSeq);
|
|
737
762
|
if (hasBlockers) {
|
|
738
763
|
logCascade({
|
|
@@ -1311,6 +1336,64 @@ export async function computeCascadeForFile(filePath, cwd, options = {}) {
|
|
|
1311
1336
|
let passiveSnapshotHits = 0;
|
|
1312
1337
|
let noLspConfigured = 0;
|
|
1313
1338
|
let touchFailures = 0;
|
|
1339
|
+
// #3157: this run's AGGREGATE display-policy drops, across every neighbour
|
|
1340
|
+
// the four display sites below rendered. Aggregate, not per neighbour: a
|
|
1341
|
+
// cascade walks up to `CASCADE_NEIGHBOUR_BUDGET` (40) neighbours per edit,
|
|
1342
|
+
// so a record inside the loop would be per-occurrence logging on a per-edit
|
|
1343
|
+
// path — one bounded row per RUN instead (AGENTS.md "bounded observability").
|
|
1344
|
+
// `policyElapsedMs` accumulates ONLY the stack's own time (round 2, F2): a
|
|
1345
|
+
// clock started here would have spanned the whole `touchFile` fan-out and
|
|
1346
|
+
// reported the neighbour WALK as the policy phase — hundreds of ms against
|
|
1347
|
+
// the sub-millisecond cost AC 3 measured, under the same phase literal the
|
|
1348
|
+
// quiet-window lane writes.
|
|
1349
|
+
let policyElapsedMs = 0;
|
|
1350
|
+
const policyCounts = {
|
|
1351
|
+
suppressed: 0,
|
|
1352
|
+
auxSuppressed: 0,
|
|
1353
|
+
total: 0,
|
|
1354
|
+
inputTruncated: 0,
|
|
1355
|
+
};
|
|
1356
|
+
/**
|
|
1357
|
+
* The ONE seam every in-lane cascade DISPLAY list goes through (#3157).
|
|
1358
|
+
*
|
|
1359
|
+
* Before this, all four sites open-coded `convertLspDiagnostics(...)` and
|
|
1360
|
+
* rendered the result raw: `grep applyFindingPolicy clients/dispatch/
|
|
1361
|
+
* integration.ts` returned nothing, so a neighbour ERROR the agent had
|
|
1362
|
+
* marked `false-positive` was hidden by `mode=delta`/`mode=full`/the
|
|
1363
|
+
* per-edit dispatcher/the probe lane/the quiet-window cascade run and STILL
|
|
1364
|
+
* re-reported here on every edit that cascaded to it. The severity filter
|
|
1365
|
+
* and the `MAX_PER_FILE` display cap were the fourth copy of the same three
|
|
1366
|
+
* lines and fold in with it.
|
|
1367
|
+
*
|
|
1368
|
+
* `neighborContent` is passed by the ONE caller that has already read the
|
|
1369
|
+
* neighbour (the active touch reads it to feed `touchFile`) — those are the
|
|
1370
|
+
* exact bytes the diagnostics were computed against, so it is both free and
|
|
1371
|
+
* more faithful than a re-read. The other three sites let the shared helper
|
|
1372
|
+
* read once, and only once a neighbour actually has an ERROR to render.
|
|
1373
|
+
*
|
|
1374
|
+
* Stage order (round 2, F1): severity filter → input bound → convert →
|
|
1375
|
+
* retag → policy → DISPLAY cap. `MAX_PER_FILE` is applied to the survivors
|
|
1376
|
+
* inside the helper; applying it here, before the policy, let policy drops
|
|
1377
|
+
* consume display slots and rendered a false clean.
|
|
1378
|
+
*/
|
|
1379
|
+
const cascadeDisplay = (rawDiags, neighborPath, neighborContent) => {
|
|
1380
|
+
const started = Date.now();
|
|
1381
|
+
const errors = rawDiags.filter((d) => d.severity === 1);
|
|
1382
|
+
const policyInput = errors.slice(0, MAX_POLICY_INPUT_PER_FILE);
|
|
1383
|
+
const result = applyCascadeDisplayPolicy(policyInput, {
|
|
1384
|
+
cwd,
|
|
1385
|
+
policyRoot: projectRoot ?? cwd,
|
|
1386
|
+
filePath: neighborPath,
|
|
1387
|
+
displayCap: MAX_PER_FILE,
|
|
1388
|
+
...(neighborContent !== undefined && { content: neighborContent }),
|
|
1389
|
+
});
|
|
1390
|
+
policyCounts.suppressed += result.suppressed;
|
|
1391
|
+
policyCounts.auxSuppressed += result.auxSuppressed;
|
|
1392
|
+
policyCounts.total += result.total;
|
|
1393
|
+
policyCounts.inputTruncated += errors.length - policyInput.length;
|
|
1394
|
+
policyElapsedMs += Date.now() - started;
|
|
1395
|
+
return result.diagnostics;
|
|
1396
|
+
};
|
|
1314
1397
|
if (sortedNeighbors.length > 0) {
|
|
1315
1398
|
const snapshotPaths = sortedNeighbors.filter(shouldReadCascadeFromSnapshot);
|
|
1316
1399
|
const activePaths = sortedNeighbors.filter((n) => !shouldReadCascadeFromSnapshot(n));
|
|
@@ -1371,7 +1454,8 @@ export async function computeCascadeForFile(filePath, cwd, options = {}) {
|
|
|
1371
1454
|
// cascade neighbor diagnostics are ephemeral display-only output
|
|
1372
1455
|
// (never reconciled into persisted widget/dedup state), so the label
|
|
1373
1456
|
// had no remaining purpose and is simply dropped rather than migrated.
|
|
1374
|
-
|
|
1457
|
+
// #3157 site 1 of 4: the passive cold-snapshot hit.
|
|
1458
|
+
const diags = cascadeDisplay(entry.diags, neighborPath);
|
|
1375
1459
|
producedLspData = true;
|
|
1376
1460
|
passiveSnapshotHits++;
|
|
1377
1461
|
const durationMs = Date.now() - neighborStart;
|
|
@@ -1623,12 +1707,12 @@ export async function computeCascadeForFile(filePath, cwd, options = {}) {
|
|
|
1623
1707
|
// doc comment on the sibling call above) — dropped rather than
|
|
1624
1708
|
// migrated to `scanOrigin` since cascade output never touches
|
|
1625
1709
|
// persisted widget/dedup state.
|
|
1626
|
-
// #1179:
|
|
1627
|
-
// `inconclusive`/`binding` flags read above
|
|
1628
|
-
// wrapper and are unaffected
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
|
|
1710
|
+
// #1179: the severity filter inside `cascadeDisplay` operates on
|
|
1711
|
+
// `rawDiags.diags`; the `inconclusive`/`binding` flags read above
|
|
1712
|
+
// stay on the `rawDiags` wrapper and are unaffected (the shape-5 fix).
|
|
1713
|
+
// #3157 site 2 of 4: the fresh in-lane touch. `content` is the copy
|
|
1714
|
+
// this touch was computed against — no second read.
|
|
1715
|
+
const diags = cascadeDisplay(rawDiags.diags, neighborPath, content);
|
|
1632
1716
|
const durationMs = Date.now() - neighborStart;
|
|
1633
1717
|
// #1899: the A5 same-write cache write stood here.
|
|
1634
1718
|
if (diags.length === 0) {
|
|
@@ -1755,10 +1839,10 @@ export async function computeCascadeForFile(filePath, cwd, options = {}) {
|
|
|
1755
1839
|
// #692: `source: "cascade"` dropped (see the doc comment above the
|
|
1756
1840
|
// first cascade call site in this file) — no longer affects `rule`
|
|
1757
1841
|
// and cascade output never touches persisted widget/dedup state.
|
|
1842
|
+
// #3157 site 3 of 4: the touch-error fallback. The touch failed, so
|
|
1843
|
+
// no content was read for this neighbour — the helper reads it.
|
|
1758
1844
|
const diags = ttlFresh && !bindingRejected
|
|
1759
|
-
?
|
|
1760
|
-
.filter((d) => d.severity === 1)
|
|
1761
|
-
.slice(0, MAX_PER_FILE), neighborPath)
|
|
1845
|
+
? cascadeDisplay(entry.diags, neighborPath)
|
|
1762
1846
|
: [];
|
|
1763
1847
|
neighbors.push({
|
|
1764
1848
|
filePath: neighborPath,
|
|
@@ -1774,12 +1858,12 @@ export async function computeCascadeForFile(filePath, cwd, options = {}) {
|
|
|
1774
1858
|
// candidates that had no configured LSP may use passive fallback; broadening
|
|
1775
1859
|
// to unrelated cache entries would make the partial active result misleading.
|
|
1776
1860
|
if (producedLspData && noLspCandidatePaths.size > 0) {
|
|
1777
|
-
const bindingRejected = appendFallbackNeighbors(neighbors, allDiags, normalizedFileKey, cwd, filePath, noLspCandidatePaths);
|
|
1861
|
+
const bindingRejected = appendFallbackNeighbors(neighbors, allDiags, normalizedFileKey, cwd, filePath, cascadeDisplay, noLspCandidatePaths);
|
|
1778
1862
|
if (bindingRejected)
|
|
1779
1863
|
fallbackBindingRejected = true;
|
|
1780
1864
|
}
|
|
1781
1865
|
else if (!producedLspData) {
|
|
1782
|
-
const bindingRejected = appendFallbackNeighbors(neighbors, allDiags, normalizedFileKey, cwd, filePath);
|
|
1866
|
+
const bindingRejected = appendFallbackNeighbors(neighbors, allDiags, normalizedFileKey, cwd, filePath, cascadeDisplay);
|
|
1783
1867
|
if (bindingRejected)
|
|
1784
1868
|
fallbackBindingRejected = true;
|
|
1785
1869
|
if (neighbors.some((n) => n.reason === "fallback")) {
|
|
@@ -1791,8 +1875,23 @@ export async function computeCascadeForFile(filePath, cwd, options = {}) {
|
|
|
1791
1875
|
});
|
|
1792
1876
|
}
|
|
1793
1877
|
}
|
|
1878
|
+
// #3157: ONE row per cascade run, covering every neighbour the four display
|
|
1879
|
+
// sites filtered — never one per neighbour. `durationMs` is the stack's own
|
|
1880
|
+
// accumulated time (the content reads the fold added included), NOT the
|
|
1881
|
+
// neighbour walk it runs inside, so this row stays comparable with the
|
|
1882
|
+
// quiet-window lane's row under the same phase literal (round 2, F2).
|
|
1883
|
+
// Silent when nothing was dropped or truncated; gated inside
|
|
1884
|
+
// `recordCascadeFindingPolicy`.
|
|
1885
|
+
recordCascadeFindingPolicy({
|
|
1886
|
+
filePath,
|
|
1887
|
+
durationMs: policyElapsedMs,
|
|
1888
|
+
suppressed: policyCounts.suppressed,
|
|
1889
|
+
total: policyCounts.total,
|
|
1890
|
+
auxSuppressed: policyCounts.auxSuppressed,
|
|
1891
|
+
inputTruncated: policyCounts.inputTruncated,
|
|
1892
|
+
});
|
|
1794
1893
|
const visibleNeighbors = applyCascadeDeltaBaselines(neighbors);
|
|
1795
|
-
const formatted = formatCascadeResult(cwd, impact, visibleNeighbors, impact.neighborFiles.length);
|
|
1894
|
+
const formatted = formatCascadeResult(cwd, impact, visibleNeighbors, impact.neighborFiles.length, policyCounts.suppressed, policyCounts.inputTruncated);
|
|
1796
1895
|
// #1104 HONESTY: filtering a bound-false display candidate must not turn a
|
|
1797
1896
|
// degraded/indeterminate cascade into a clean-looking one (same doctrine as
|
|
1798
1897
|
// #1023's graph-degraded marker). If every candidate the degraded-fallback
|
|
@@ -1977,7 +2076,10 @@ function applyCascadeDeltaBaselines(neighbors) {
|
|
|
1977
2076
|
* because its content binding was rejected (#1104) — the caller folds this
|
|
1978
2077
|
* into the run-level `fallbackBindingRejected` flag for the honesty check.
|
|
1979
2078
|
*/
|
|
1980
|
-
function appendFallbackNeighbors(neighbors, allDiags, normalizedFileKey, cwd, filePath,
|
|
2079
|
+
function appendFallbackNeighbors(neighbors, allDiags, normalizedFileKey, cwd, filePath,
|
|
2080
|
+
/** The run's shared display-policy seam (#3157) — see `cascadeDisplay` in
|
|
2081
|
+
* `computeCascadeForFile`. */
|
|
2082
|
+
cascadeDisplay, allowedPaths) {
|
|
1981
2083
|
const now = Date.now();
|
|
1982
2084
|
const seen = new Set(neighbors.map((n) => normalizeMapKey(n.filePath)));
|
|
1983
2085
|
let bindingRejected = false;
|
|
@@ -2022,8 +2124,10 @@ function appendFallbackNeighbors(neighbors, allDiags, normalizedFileKey, cwd, fi
|
|
|
2022
2124
|
continue;
|
|
2023
2125
|
}
|
|
2024
2126
|
// #692: `source: "cascade"` dropped — see the doc comment above the
|
|
2025
|
-
// first cascade
|
|
2026
|
-
|
|
2127
|
+
// first cascade display call site in this file.
|
|
2128
|
+
// #3157 site 4 of 4: the degraded fallback. `cascadeDisplay` is passed in
|
|
2129
|
+
// rather than re-derived so this run's aggregate drop counters see it too.
|
|
2130
|
+
const errors = cascadeDisplay(diags, diagPath);
|
|
2027
2131
|
if (errors.length === 0)
|
|
2028
2132
|
continue;
|
|
2029
2133
|
neighbors.push({
|
|
@@ -2048,17 +2152,36 @@ function neighborReason(importerSet, callerSet, neighborPath) {
|
|
|
2048
2152
|
return "calls";
|
|
2049
2153
|
return "references";
|
|
2050
2154
|
}
|
|
2051
|
-
function formatCascadeResult(cwd, impact, neighbors, totalNeighbors
|
|
2155
|
+
function formatCascadeResult(cwd, impact, neighbors, totalNeighbors,
|
|
2156
|
+
/** This run's aggregate policy-stack drops (#3157). */
|
|
2157
|
+
policySuppressed,
|
|
2158
|
+
/**
|
|
2159
|
+
* This run's aggregate `MAX_POLICY_INPUT_PER_FILE` truncation (#3157 round
|
|
2160
|
+
* 3, N1) — ERRORs the policy never LOOKED at.
|
|
2161
|
+
*/
|
|
2162
|
+
policyInputTruncated) {
|
|
2052
2163
|
const diagnosticsBlock = formatCascadeNeighborDiagnostics(cwd, neighbors, {
|
|
2053
2164
|
noun: "neighbor",
|
|
2054
2165
|
includeReason: true,
|
|
2055
2166
|
});
|
|
2056
|
-
|
|
2167
|
+
// #3157 round 3, N1: an empty block is silence, and silence is a CLEAN
|
|
2168
|
+
// verdict to the agent. That is honest only when everything was looked at.
|
|
2169
|
+
// `policyInputTruncated > 0` means this run's own cost bound declined to
|
|
2170
|
+
// evaluate findings that exist — the #1459 coverage-gap case, not a verdict —
|
|
2171
|
+
// so it must speak even with nothing to render. `policySuppressed` does NOT
|
|
2172
|
+
// get the same treatment on purpose: those findings WERE evaluated and every
|
|
2173
|
+
// drop is the agent's own mark or its project's own rule policy, and the
|
|
2174
|
+
// quiet-window lane deliberately says nothing at all for that case
|
|
2175
|
+
// (`buildResolvedFoundCascadeRun` returns `undefined`). Speaking here and not
|
|
2176
|
+
// there would re-open the two-lane divergence #3157 exists to close.
|
|
2177
|
+
if (!diagnosticsBlock && policyInputTruncated === 0)
|
|
2057
2178
|
return "";
|
|
2058
2179
|
const impactHeader = formatImpactCascade(impact, RUNTIME_CONFIG.pipeline.cascadeMaxFiles);
|
|
2059
|
-
let out =
|
|
2060
|
-
?
|
|
2061
|
-
:
|
|
2180
|
+
let out = !diagnosticsBlock
|
|
2181
|
+
? ""
|
|
2182
|
+
: impactHeader
|
|
2183
|
+
? `${impactHeader}\n${diagnosticsBlock}`
|
|
2184
|
+
: diagnosticsBlock;
|
|
2062
2185
|
// A10: include truncated filenames so agent knows which files were cut
|
|
2063
2186
|
const truncated = totalNeighbors - neighbors.length;
|
|
2064
2187
|
if (truncated > 0) {
|
|
@@ -2071,6 +2194,33 @@ function formatCascadeResult(cwd, impact, neighbors, totalNeighbors) {
|
|
|
2071
2194
|
: `${truncated} more dependent file(s)`;
|
|
2072
2195
|
out += `\n... and ${moreLabel}`;
|
|
2073
2196
|
}
|
|
2197
|
+
// #1616 / #3157: a delivery that still has something to say states what it
|
|
2198
|
+
// dropped, once per delivery — the same sentence the quiet-window cascade run
|
|
2199
|
+
// (`buildResolvedFoundCascadeRun`) and the late-auxiliary advisory render, so
|
|
2200
|
+
// the two cascade lanes do not disagree about whether a mark is worth
|
|
2201
|
+
// mentioning. Policy drops only: an aux drop is the file's own suppression
|
|
2202
|
+
// comment, which the per-edit dispatch path honours silently too, and it stays
|
|
2203
|
+
// in the `cascade_finding_policy` record. A run with NOTHING left returned ""
|
|
2204
|
+
// above and says nothing at all — silence is not a claim that a neighbour is
|
|
2205
|
+
// clean.
|
|
2206
|
+
// #3157 round 3, N1: the same shape and the same closing clause as the
|
|
2207
|
+
// "did not cover" line this file's renderer already uses for a neighbour no
|
|
2208
|
+
// scanner looked at (`clients/cascade-format.ts`) — the agent must not read
|
|
2209
|
+
// an empty or short block as a clean verdict when the bound cut findings
|
|
2210
|
+
// nobody evaluated.
|
|
2211
|
+
if (policyInputTruncated > 0) {
|
|
2212
|
+
if (out)
|
|
2213
|
+
out += "\n";
|
|
2214
|
+
out +=
|
|
2215
|
+
`⚠️ Cascade did not evaluate ${policyInputTruncated} finding(s) — the per-neighbor ` +
|
|
2216
|
+
`policy input is bounded at ${MAX_POLICY_INPUT_PER_FILE}; no findings does NOT mean clean here.`;
|
|
2217
|
+
}
|
|
2218
|
+
// Rides along whenever anything is rendered at all: when the block is empty
|
|
2219
|
+
// and the coverage line above is speaking, this is what explains WHY it is
|
|
2220
|
+
// empty. The empty-and-nothing-truncated case returned above.
|
|
2221
|
+
if (policySuppressed > 0) {
|
|
2222
|
+
out += `\nsuppressed by disposition: ${policySuppressed} finding(s) (marked false-positive or won't-fix).`;
|
|
2223
|
+
}
|
|
2074
2224
|
return out;
|
|
2075
2225
|
}
|
|
2076
2226
|
/**
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Per-rule path carve-outs (`ignores:` in a rule's own YAML, #965).
|
|
3
|
+
*
|
|
4
|
+
* ONE matcher, shared by every surface that decides whether a rule may fire on
|
|
5
|
+
* a path: the NAPI ast-grep runner, the tree-sitter runner (`ignore_paths`),
|
|
6
|
+
* and — since #3041 — the LSP output seam (`applyAuxiliarySuppressions`).
|
|
7
|
+
*
|
|
8
|
+
* #3041, measured against ast-grep 0.45.3: `ast-grep scan` applies a rule's
|
|
9
|
+
* `ignores` globs during its own project walk, but `ast-grep lsp` publishes
|
|
10
|
+
* per-document diagnostics WITHOUT applying them — the same rule on the same
|
|
11
|
+
* file is filtered by the CLI and unfiltered over LSP. So every pi-lens path
|
|
12
|
+
* that delivers ast-grep's LSP diagnostics has to apply the carve-out itself;
|
|
13
|
+
* the runner-side matcher below is what it applies.
|
|
14
|
+
*/
|
|
15
|
+
import * as path from "node:path";
|
|
16
|
+
import { minimatch } from "../deps/minimatch.js";
|
|
17
|
+
import { buildEffectiveAstGrepCatalog } from "./ast-grep-catalog.js";
|
|
18
|
+
/**
|
|
19
|
+
* True when `filePath` is carved out of a rule by one of its glob `patterns`.
|
|
20
|
+
*
|
|
21
|
+
* The glob is matched against `filePath` relative to `root`, forward-slashed.
|
|
22
|
+
* Falls back to the absolute (slash-normalized) path when `filePath` isn't
|
|
23
|
+
* under `root` (e.g. an out-of-tree temp file), so a glob like `scripts/**`
|
|
24
|
+
* simply never matches rather than throwing.
|
|
25
|
+
*/
|
|
26
|
+
export function isRuleIgnoredForPath(filePath, root, patterns) {
|
|
27
|
+
if (!patterns || patterns.length === 0)
|
|
28
|
+
return false;
|
|
29
|
+
const relative = path.relative(root, filePath);
|
|
30
|
+
const displayPath = (relative.startsWith("..") ? filePath : relative)
|
|
31
|
+
.split(path.sep)
|
|
32
|
+
.join("/");
|
|
33
|
+
return patterns.some((pattern) => minimatch(displayPath, pattern, { dot: true }));
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Rule id → its `ignores` globs, for the effective ast-grep catalog at `root`.
|
|
37
|
+
*
|
|
38
|
+
* #3053: the catalog walk and its first-source-wins precedence used to be
|
|
39
|
+
* reimplemented here; both this function and the NAPI runner now read
|
|
40
|
+
* `buildEffectiveAstGrepCatalog` (clients/dispatch/ast-grep-catalog.ts), so
|
|
41
|
+
* the LSP seam carves out exactly the paths the runner carves out by
|
|
42
|
+
* construction rather than by two hand-aligned copies — see that module's
|
|
43
|
+
* docstring for the precedence rule and the within-source-duplicate decision
|
|
44
|
+
* this function inherits from it. The catalog's own loaders are cached, so
|
|
45
|
+
* this is ~0.09 ms per call on pi-lens's own catalog (17 rules with
|
|
46
|
+
* `ignores`, measured) — cheap enough to call per file rather than thread a
|
|
47
|
+
* preloaded map through every sweep.
|
|
48
|
+
*
|
|
49
|
+
* Keyed by the EXACT rule id, matching the runner: a `-js` twin (e.g.
|
|
50
|
+
* `no-console-except-error-js`) carries its own `ignores` in its own document,
|
|
51
|
+
* so normalizing the suffix here would apply one document's carve-out to the
|
|
52
|
+
* other's findings.
|
|
53
|
+
*/
|
|
54
|
+
export function loadRuleIgnorePatterns(root) {
|
|
55
|
+
const patterns = new Map();
|
|
56
|
+
for (const [id, { rule }] of buildEffectiveAstGrepCatalog(root)
|
|
57
|
+
.effectiveRules) {
|
|
58
|
+
if (rule.ignores?.length)
|
|
59
|
+
patterns.set(id, rule.ignores);
|
|
60
|
+
}
|
|
61
|
+
return patterns;
|
|
62
|
+
}
|
|
@@ -12,16 +12,16 @@ import * as fs from "node:fs";
|
|
|
12
12
|
import * as path from "node:path";
|
|
13
13
|
import { incrementDegradationCount, recordDegradationOnce, } from "../../degradation-ledger.js";
|
|
14
14
|
import { loadAstGrepNapi, } from "../../deps/ast-grep-napi.js";
|
|
15
|
-
import {
|
|
15
|
+
import { buildEffectiveAstGrepCatalog } from "../ast-grep-catalog.js";
|
|
16
|
+
import { isRuleIgnoredForPath } from "../rule-ignores.js";
|
|
16
17
|
import { logLatency } from "../../latency-logger.js";
|
|
17
18
|
import { hasAuxiliaryLspPublishedForRoot } from "../../lsp/index.js";
|
|
18
19
|
import { clearPendingAuxiliaryCoverage, hasPendingAuxiliaryCoverage, recordNapiFallbackCoverage, } from "../../lsp/pending-aux-coverage.js";
|
|
19
|
-
import { getAstGrepRuleSources, } from "../../sgconfig.js";
|
|
20
20
|
import { hasEslintConfig } from "../../tool-policy.js";
|
|
21
21
|
import { enabledAuxiliaryLspServerIds } from "../auxiliary-lsp.js";
|
|
22
22
|
import { classifyDefect } from "../diagnostic-taxonomy.js";
|
|
23
23
|
import { PRIORITY } from "../priorities.js";
|
|
24
|
-
import { calculateRuleComplexity, isOverlyBroadPattern, isStructuredRule,
|
|
24
|
+
import { calculateRuleComplexity, isOverlyBroadPattern, isStructuredRule, MAX_BLOCKING_RULE_COMPLEXITY, } from "./yaml-rule-parser.js";
|
|
25
25
|
const defaultUnsupportedLanguageLog = new Set();
|
|
26
26
|
const UNSUPPORTED_RULE_ID_SAMPLE_SIZE = 5;
|
|
27
27
|
/** Clear per-session unsupported-language telemetry dedupe. */
|
|
@@ -366,22 +366,6 @@ function explicitRuleFixSuggestion(rule) {
|
|
|
366
366
|
function normalizeRuleId(ruleId) {
|
|
367
367
|
return ruleId.replace(/-js$/, "");
|
|
368
368
|
}
|
|
369
|
-
/**
|
|
370
|
-
* `filePath` relative to `root`, forward-slashed, for matching a rule's
|
|
371
|
-
* `ignores` globs (#965). Falls back to the absolute (slash-normalized) path
|
|
372
|
-
* when `filePath` isn't under `root` (e.g. an out-of-tree temp file), so a
|
|
373
|
-
* glob like `scripts/**` simply never matches rather than throwing.
|
|
374
|
-
*/
|
|
375
|
-
function relativeForIgnoreGlob(filePath, root) {
|
|
376
|
-
const rel = path.relative(root, filePath);
|
|
377
|
-
return (rel.startsWith("..") ? filePath : rel).split(path.sep).join("/");
|
|
378
|
-
}
|
|
379
|
-
function matchesRuleIgnores(filePath, root, patterns) {
|
|
380
|
-
if (!patterns || patterns.length === 0)
|
|
381
|
-
return false;
|
|
382
|
-
const rel = relativeForIgnoreGlob(filePath, root);
|
|
383
|
-
return patterns.some((pattern) => minimatch(rel, pattern, { dot: true }));
|
|
384
|
-
}
|
|
385
369
|
export function canHandle(filePath) {
|
|
386
370
|
return BINDING_BY_EXTENSION.has(path.extname(filePath).toLowerCase());
|
|
387
371
|
}
|
|
@@ -660,7 +644,7 @@ function duplicateRuleIds(rules) {
|
|
|
660
644
|
.map(([id]) => id)
|
|
661
645
|
.sort((a, b) => a.localeCompare(b));
|
|
662
646
|
}
|
|
663
|
-
function appendDuplicateRuleDiagnostics(diagnostics,
|
|
647
|
+
function appendDuplicateRuleDiagnostics(diagnostics, duplicateIds, source, filePath, maxTotalDiagnostics) {
|
|
664
648
|
const sourceLabel = `${source.origin} ${source.tier} rules`;
|
|
665
649
|
for (const ruleId of duplicateIds) {
|
|
666
650
|
diagnostics.push({
|
|
@@ -678,7 +662,6 @@ function appendDuplicateRuleDiagnostics(diagnostics, seenRuleIds, duplicateIds,
|
|
|
678
662
|
autoFixAvailable: false,
|
|
679
663
|
fixSuggestion: `Give every rule in ${sourceLabel} a unique id`,
|
|
680
664
|
});
|
|
681
|
-
seenRuleIds.add(ruleId);
|
|
682
665
|
if (diagnostics.length >= maxTotalDiagnostics)
|
|
683
666
|
return true;
|
|
684
667
|
}
|
|
@@ -723,7 +706,6 @@ export function evaluateAstGrepRules(filePath, rootNode, cwd, kind, options = {}
|
|
|
723
706
|
const log = options.log;
|
|
724
707
|
const unsupportedLanguageLog = options.unsupportedLanguageLog ?? defaultUnsupportedLanguageLog;
|
|
725
708
|
const diagnostics = [];
|
|
726
|
-
const seenRuleIds = new Set();
|
|
727
709
|
const suppressLinterOverlap = kind === "jsts" && hasEslintConfig(cwd);
|
|
728
710
|
const fileLang = ruleLanguageForFile(filePath);
|
|
729
711
|
// Embedded `<script>` coverage (#2347): on an HTML file, every script body
|
|
@@ -811,23 +793,14 @@ export function evaluateAstGrepRules(filePath, rootNode, cwd, kind, options = {}
|
|
|
811
793
|
});
|
|
812
794
|
newlyUnsupported.clear();
|
|
813
795
|
};
|
|
814
|
-
//
|
|
815
|
-
//
|
|
796
|
+
// Same walk and precedence the LSP-seam matcher uses
|
|
797
|
+
// (clients/dispatch/rule-ignores.ts), folded onto one derivation by #3053
|
|
798
|
+
// after #3046 shipped them as two independently-drifting copies.
|
|
816
799
|
const ignoreRoot = options.projectRoot ?? cwd;
|
|
817
|
-
const
|
|
818
|
-
for (const source of
|
|
819
|
-
let rules;
|
|
820
|
-
try {
|
|
821
|
-
// Project rules are mutable during a session, so their cache fingerprints
|
|
822
|
-
// relative paths and contents. Bundled catalogs are immutable per install.
|
|
823
|
-
const loader = source.origin === "project" ? loadYamlRulesFresh : loadYamlRules;
|
|
824
|
-
rules = loader(source.dir);
|
|
825
|
-
}
|
|
826
|
-
catch {
|
|
827
|
-
continue;
|
|
828
|
-
}
|
|
800
|
+
const catalog = buildEffectiveAstGrepCatalog(ignoreRoot);
|
|
801
|
+
for (const { source, rules } of catalog.sources) {
|
|
829
802
|
const duplicates = duplicateRuleIds(rules);
|
|
830
|
-
if (appendDuplicateRuleDiagnostics(diagnostics,
|
|
803
|
+
if (appendDuplicateRuleDiagnostics(diagnostics, duplicates, source, filePath, maxTotalDiagnostics)) {
|
|
831
804
|
flushUnsupportedRuleSkips();
|
|
832
805
|
return diagnostics;
|
|
833
806
|
}
|
|
@@ -835,16 +808,23 @@ export function evaluateAstGrepRules(filePath, rootNode, cwd, kind, options = {}
|
|
|
835
808
|
for (const rule of rules) {
|
|
836
809
|
if (duplicateSet.has(rule.id))
|
|
837
810
|
continue;
|
|
838
|
-
// Cross-layer collisions keep the first (higher-precedence) source
|
|
839
|
-
|
|
811
|
+
// Cross-layer collisions keep the first (higher-precedence) source —
|
|
812
|
+
// resolved once by `buildEffectiveAstGrepCatalog`, not per-rule here.
|
|
813
|
+
// Compares the winning DOCUMENT (object identity), not merely its
|
|
814
|
+
// source: comparing `.source` alone can't tell a within-source
|
|
815
|
+
// duplicate's two copies apart (they share one source), so it gave
|
|
816
|
+
// no defense if `duplicateSet.has` above were ever the only guard
|
|
817
|
+
// standing (#3053 round 2 F1). Every `rule` iterated here is the
|
|
818
|
+
// same object instance the catalog recorded, since both read the
|
|
819
|
+
// identical `rules` arrays off `catalog.sources`.
|
|
820
|
+
if (catalog.effectiveRules.get(rule.id)?.rule !== rule)
|
|
840
821
|
continue;
|
|
841
|
-
seenRuleIds.add(rule.id);
|
|
842
822
|
if (blockingOnly && rule.severity !== "error")
|
|
843
823
|
continue;
|
|
844
824
|
// Per-rule path carve-out (#965): a rule that's noise on CLI scripts or
|
|
845
825
|
// a project's own logging sink (e.g. no-console-except-error firing
|
|
846
826
|
// inside scripts/** or lib/logger.ts) opts out via `ignores`.
|
|
847
|
-
if (
|
|
827
|
+
if (isRuleIgnoredForPath(filePath, ignoreRoot, rule.ignores))
|
|
848
828
|
continue;
|
|
849
829
|
if (suppressLinterOverlap &&
|
|
850
830
|
LINTER_OVERLAP.has(normalizeRuleId(rule.id)) &&
|
|
@@ -202,7 +202,12 @@ const biomeCheckJsonRunner = {
|
|
|
202
202
|
// Defer to ESLint/oxlint if the project has explicitly configured one —
|
|
203
203
|
// biome runs as the default linter only when no alternative is present.
|
|
204
204
|
if (!policy.hasBiomeConfig && policy.hasExplicitNonBiomeLinter) {
|
|
205
|
-
return {
|
|
205
|
+
return {
|
|
206
|
+
status: "skipped",
|
|
207
|
+
diagnostics: [],
|
|
208
|
+
semantic: "none",
|
|
209
|
+
skipReason: "configured-non-biome-linter",
|
|
210
|
+
};
|
|
206
211
|
}
|
|
207
212
|
const cmd = await resolveToolCommandWithInstallFallback(cwd, "biome");
|
|
208
213
|
if (!cmd) {
|
|
@@ -3,15 +3,14 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Runs `cargo clippy` for Rust files to catch common mistakes.
|
|
5
5
|
*/
|
|
6
|
-
import {
|
|
7
|
-
import { isAbsolute, join, resolve } from "node:path";
|
|
6
|
+
import { isAbsolute, resolve } from "node:path";
|
|
8
7
|
import { rustClient } from "../../rust-client.js";
|
|
9
8
|
import { safeSpawnAsync } from "../../safe-spawn.js";
|
|
10
9
|
import { stripAnsi } from "../../sanitize.js";
|
|
11
10
|
import { getLazyInstallAttempt, tryLazyInstall, } from "./utils/lazy-installer.js";
|
|
12
11
|
import { describeInstallAttempt, logAvailabilityDecision, } from "./utils/availability-policy.js";
|
|
13
12
|
import { PRIORITY } from "../priorities.js";
|
|
14
|
-
import {
|
|
13
|
+
import { resolveRunnerCwdWithReason } from "../../tool-cwd.js";
|
|
15
14
|
import { createCwdCachedProbe } from "./utils/runner-helpers.js";
|
|
16
15
|
// Cached per-cwd `cargo clippy --version` probe (#120). Before this, the
|
|
17
16
|
// probe fired on every Rust file save in a project where clippy was already
|
|
@@ -91,13 +90,14 @@ const rustClippyRunner = {
|
|
|
91
90
|
}
|
|
92
91
|
}
|
|
93
92
|
// The package root (where Cargo.toml is), through the shared cwd seam
|
|
94
|
-
// (#2894):
|
|
95
|
-
// IS the walk this runner used to do with `findNearestContaining` — plus
|
|
93
|
+
// (#2894): the shared language marker vocabulary includes `Cargo.toml`, so
|
|
94
|
+
// this IS the walk this runner used to do with `findNearestContaining` — plus
|
|
96
95
|
// the seam's dispatch-root and `$HOME` ceilings, which the hand-rolled
|
|
97
96
|
// walk had neither of. The gate then asks about exactly the directory
|
|
98
97
|
// cargo will run in, rather than about a separately-derived one.
|
|
99
|
-
const
|
|
100
|
-
|
|
98
|
+
const cargoResolution = resolveRunnerCwdWithReason(ctx, "rust-clippy");
|
|
99
|
+
const cargoDir = cargoResolution.cwd;
|
|
100
|
+
if (cargoResolution.marker !== "Cargo.toml") {
|
|
101
101
|
return { status: "skipped", diagnostics: [], semantic: "none" };
|
|
102
102
|
}
|
|
103
103
|
// Run cargo clippy on the package
|
|
@@ -1,6 +1,5 @@
|
|
|
1
|
-
import * as fs from "node:fs";
|
|
2
|
-
import * as path from "node:path";
|
|
3
1
|
import { safeSpawnAsync } from "../../safe-spawn.js";
|
|
2
|
+
import { findNearestContaining } from "../../path-utils.js";
|
|
4
3
|
import { resolveRunnerCwd } from "../../tool-cwd.js";
|
|
5
4
|
import { PRIORITY } from "../priorities.js";
|
|
6
5
|
import { createAvailabilityChecker, resolveAvailableOrInstall, } from "./utils/runner-helpers.js";
|
|
@@ -10,16 +9,7 @@ const shfmt = createAvailabilityChecker("shfmt", ".exe");
|
|
|
10
9
|
// reports genuine parse errors instead of nagging every unformatted shell file
|
|
11
10
|
// against shfmt's built-in defaults (#211).
|
|
12
11
|
function hasEditorConfig(cwd) {
|
|
13
|
-
|
|
14
|
-
while (true) {
|
|
15
|
-
if (fs.existsSync(path.join(current, ".editorconfig")))
|
|
16
|
-
return true;
|
|
17
|
-
const parent = path.dirname(current);
|
|
18
|
-
if (parent === current)
|
|
19
|
-
break;
|
|
20
|
-
current = parent;
|
|
21
|
-
}
|
|
22
|
-
return false;
|
|
12
|
+
return findNearestContaining(cwd, [".editorconfig"]) !== undefined;
|
|
23
13
|
}
|
|
24
14
|
/**
|
|
25
15
|
* shfmt runner — checks shell script formatting.
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
*/
|
|
7
7
|
import * as path from "node:path";
|
|
8
8
|
import { RuleCache } from "../../cache/rule-cache.js";
|
|
9
|
-
import {
|
|
9
|
+
import { isRuleIgnoredForPath } from "../rule-ignores.js";
|
|
10
10
|
import { isTestFile } from "../../file-utils.js";
|
|
11
11
|
import { buildOrUpdateGraph, computeImpactCascade, recordEntitySnapshotDiff, } from "../../review-graph/service.js";
|
|
12
12
|
import { getSharedTreeSitterClient, isTreeSitterWasmAborted, markTreeSitterWasmAborted, resolveTreeSitterLanguage, } from "../../tree-sitter-shared.js";
|
|
@@ -292,25 +292,6 @@ function getTotalLinesChanged(ranges) {
|
|
|
292
292
|
}
|
|
293
293
|
/** Threshold: skip entity extraction for changes under 5 lines */
|
|
294
294
|
const ENTITY_EXTRACTION_LINE_THRESHOLD = 5;
|
|
295
|
-
/**
|
|
296
|
-
* `filePath` relative to `root`, forward-slashed, for matching a rule's
|
|
297
|
-
* `ignore_paths` globs. Falls back to the absolute (slash-normalized) path
|
|
298
|
-
* when `filePath` isn't under `root` (e.g. an out-of-tree temp file), so a
|
|
299
|
-
* glob like `scripts/**` simply never matches rather than throwing.
|
|
300
|
-
*/
|
|
301
|
-
function relativeForIgnoreGlob(filePath, root) {
|
|
302
|
-
const rel = path.relative(root, filePath);
|
|
303
|
-
const normalized = (rel.startsWith("..") ? filePath : rel)
|
|
304
|
-
.split(path.sep)
|
|
305
|
-
.join("/");
|
|
306
|
-
return normalized;
|
|
307
|
-
}
|
|
308
|
-
function matchesIgnorePaths(filePath, root, patterns) {
|
|
309
|
-
if (!patterns || patterns.length === 0)
|
|
310
|
-
return false;
|
|
311
|
-
const rel = relativeForIgnoreGlob(filePath, root);
|
|
312
|
-
return patterns.some((pattern) => minimatch(rel, pattern, { dot: true }));
|
|
313
|
-
}
|
|
314
295
|
const treeSitterRunner = {
|
|
315
296
|
id: "tree-sitter",
|
|
316
297
|
appliesTo: [
|
|
@@ -447,7 +428,7 @@ const treeSitterRunner = {
|
|
|
447
428
|
const effectiveQueries = (ctx.blockingOnly
|
|
448
429
|
? languageQueries.filter((q) => q.inline_tier !== "review")
|
|
449
430
|
: languageQueries).filter((q) => !(fileIsTest && q.skip_test_files) &&
|
|
450
|
-
!
|
|
431
|
+
!isRuleIgnoredForPath(filePath, ignoreRoot, q.ignore_paths));
|
|
451
432
|
logTreeSitter({
|
|
452
433
|
phase: "queries_loaded",
|
|
453
434
|
filePath,
|
|
@@ -1697,5 +1697,5 @@ export const sg = {
|
|
|
1697
1697
|
};
|
|
1698
1698
|
/** Shared cwd seam for runner probes and analysis children (#2777). */
|
|
1699
1699
|
export function resolveRunnerCwd(ctx, tool) {
|
|
1700
|
-
return resolveToolCwd("runner", tool, ctx.filePath, ctx);
|
|
1700
|
+
return resolveToolCwd("runner", tool, ctx.filePath, ctx).cwd;
|
|
1701
1701
|
}
|
|
@@ -11,7 +11,10 @@
|
|
|
11
11
|
* The dispatcher must handle these semantics consistently.
|
|
12
12
|
*/
|
|
13
13
|
/** Closed telemetry taxonomy for expected runner skips. */
|
|
14
|
-
export const RUNNER_SKIP_REASONS = [
|
|
14
|
+
export const RUNNER_SKIP_REASONS = [
|
|
15
|
+
"no-files-matched",
|
|
16
|
+
"configured-non-biome-linter",
|
|
17
|
+
];
|
|
15
18
|
/** Runtime guard for untyped/plugin-provided runner results. */
|
|
16
19
|
export function isRunnerSkipReason(value) {
|
|
17
20
|
return RUNNER_SKIP_REASONS.some((reason) => reason === value);
|