pi-lens 4.2.0 → 4.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +543 -0
- package/README.md +45 -2
- package/dist/clients/actionable-warnings.js +44 -16
- package/dist/clients/advisory-provenance.js +187 -51
- package/dist/clients/cache-manager.js +64 -5
- package/dist/clients/cascade-format.js +24 -0
- package/dist/clients/child-unref.js +17 -8
- package/dist/clients/config-diagnostic-codes.js +2 -0
- package/dist/clients/config-locations.js +237 -9
- package/dist/clients/config-resolve.js +7 -1
- package/dist/clients/degradation-ledger.js +33 -3
- package/dist/clients/dependency-checker.js +87 -64
- package/dist/clients/diagnostic-dispositions.js +39 -3
- package/dist/clients/dispatch/finding-policy.js +118 -1
- package/dist/clients/dispatch/indent-detect.js +8 -2
- package/dist/clients/dispatch/inline-suppressions.js +23 -4
- package/dist/clients/dispatch/rule-ignores.js +14 -7
- package/dist/clients/dispatch/runners/actionlint.js +26 -28
- package/dist/clients/dispatch/runners/biome-check.js +38 -25
- package/dist/clients/dispatch/runners/cpp-check.js +32 -49
- package/dist/clients/dispatch/runners/credo.js +16 -14
- package/dist/clients/dispatch/runners/cue-vet.js +25 -7
- package/dist/clients/dispatch/runners/dart-analyze.js +28 -47
- package/dist/clients/dispatch/runners/detekt.js +11 -4
- package/dist/clients/dispatch/runners/dotnet-build.js +27 -22
- package/dist/clients/dispatch/runners/elixir-check.js +31 -46
- package/dist/clients/dispatch/runners/eslint.js +29 -46
- package/dist/clients/dispatch/runners/gleam-check.js +91 -11
- package/dist/clients/dispatch/runners/go-vet.js +16 -2
- package/dist/clients/dispatch/runners/golangci-lint.js +38 -23
- package/dist/clients/dispatch/runners/hadolint.js +27 -15
- package/dist/clients/dispatch/runners/htmlhint.js +17 -8
- package/dist/clients/dispatch/runners/javac.js +31 -21
- package/dist/clients/dispatch/runners/ktlint.js +34 -60
- package/dist/clients/dispatch/runners/lsp.js +6 -5
- package/dist/clients/dispatch/runners/markdownlint.js +12 -5
- package/dist/clients/dispatch/runners/mypy.js +2 -1
- package/dist/clients/dispatch/runners/oxlint.js +22 -6
- package/dist/clients/dispatch/runners/php-lint.js +39 -16
- package/dist/clients/dispatch/runners/phpstan.js +1 -1
- package/dist/clients/dispatch/runners/rubocop.js +27 -18
- package/dist/clients/dispatch/runners/ruff.js +18 -19
- package/dist/clients/dispatch/runners/rust-clippy.js +6 -1
- package/dist/clients/dispatch/runners/shellcheck.js +6 -2
- package/dist/clients/dispatch/runners/spellcheck.js +10 -3
- package/dist/clients/dispatch/runners/sqlfluff.js +11 -2
- package/dist/clients/dispatch/runners/stylelint.js +10 -2
- package/dist/clients/dispatch/runners/swiftlint.js +11 -2
- package/dist/clients/dispatch/runners/taplo.js +12 -6
- package/dist/clients/dispatch/runners/tflint.js +30 -15
- package/dist/clients/dispatch/runners/tree-sitter.js +23 -0
- package/dist/clients/dispatch/runners/trivy-config.js +8 -2
- package/dist/clients/dispatch/runners/utils/diagnostic-parsers.js +11 -1
- package/dist/clients/dispatch/runners/vale.js +15 -3
- package/dist/clients/dispatch/runners/yamllint.js +12 -2
- package/dist/clients/dispatch/runners/zig-check.js +27 -39
- package/dist/clients/dispatch/suppress-writer.js +13 -3
- package/dist/clients/effective-config.js +3 -1
- package/dist/clients/extension-log.js +48 -1
- package/dist/clients/finding-delivery-gate.js +53 -23
- package/dist/clients/formatters.js +49 -11
- package/dist/clients/git-guard.js +247 -34
- package/dist/clients/gradle-ktfmt-style.js +121 -25
- package/dist/clients/inline-blocker-dispositions.js +186 -0
- package/dist/clients/install-diagnostics.js +19 -6
- package/dist/clients/installer/index.js +281 -32
- package/dist/clients/jscpd-client.js +8 -0
- package/dist/clients/latency-logger.js +8 -0
- package/dist/clients/lens-config.js +66 -8
- package/dist/clients/lsp/cascade-tier.js +3 -0
- package/dist/clients/lsp/client.js +139 -11
- package/dist/clients/lsp/config.js +9 -7
- package/dist/clients/lsp/content-limits.js +41 -0
- package/dist/clients/lsp/index.js +42 -8
- package/dist/clients/lsp/launch.js +23 -2
- package/dist/clients/lsp/server.js +106 -25
- package/dist/clients/lsp/sync-kind.js +14 -0
- package/dist/clients/lsp/wait-policy/strategies.js +105 -16
- package/dist/clients/mcp/analyze.js +9 -2
- package/dist/clients/mcp/ipc.js +232 -44
- package/dist/clients/mcp/review.js +53 -7
- package/dist/clients/opengrep-client.js +11 -6
- package/dist/clients/path-utils.js +51 -9
- package/dist/clients/persistent-reverify.js +271 -0
- package/dist/clients/pipeline.js +136 -67
- package/dist/clients/project-diagnostics/fresh-fetch.js +21 -3
- package/dist/clients/project-lens-config.js +11 -3
- package/dist/clients/review-graph/builder.js +34 -26
- package/dist/clients/review-graph/git-identity.js +16 -10
- package/dist/clients/ruff-client.js +31 -4
- package/dist/clients/runtime-coordinator.js +38 -4
- package/dist/clients/runtime-tool-call.js +16 -5
- package/dist/clients/runtime-tool-result.js +27 -12
- package/dist/clients/runtime-turn.js +571 -287
- package/dist/clients/safe-spawn.js +256 -65
- package/dist/clients/skills-resolver.js +38 -2
- package/dist/clients/spawn-output-cap.js +84 -1
- package/dist/clients/tree-sitter-client.js +119 -58
- package/dist/clients/turn-end/lane.js +51 -0
- package/dist/clients/turn-end/lanes/govulncheck.js +112 -0
- package/dist/clients/turn-end/lanes/secrets.js +204 -0
- package/dist/clients/warm-attach.js +59 -0
- package/dist/clients/widget-state.js +294 -17
- package/dist/clients/workspace-topology.js +57 -3
- package/dist/clients/zizmor-config.js +26 -13
- package/dist/index.js +6342 -3995
- package/dist/mcp/analyze-cli.js +4 -0
- package/dist/mcp/server.js +33 -21
- package/dist/scripts/lib/web-tree-sitter-dir.mjs +123 -0
- package/dist/tools/lens-diagnostic-mark.js +14 -2
- package/dist/tools/lens-diagnostics.js +127 -33
- package/dist/tools/lsp-diagnostics.js +150 -12
- package/docs/adr/0001-stale-advisory-live-arm.md +29 -0
- package/docs/adr/0002-workspace-hash-rendezvous.md +30 -0
- package/docs/adr/0003-git-guard-latch-writer.md +203 -0
- package/docs/adr/0004-disposition-policy-seam.md +38 -0
- package/docs/adr/0005-tool-availability-enforcement-seam.md +29 -0
- package/docs/adr/0006-derived-state-benchmark-first.md +31 -0
- package/docs/adr/0007-end-to-end-witness-per-seam-slice.md +38 -0
- package/docs/adr/0008-turn-end-lane-interface.md +165 -0
- package/docs/adr/0009-reported-path-attribution.md +114 -0
- package/docs/adr/README.md +26 -0
- package/docs/agent-tools.md +18 -1
- package/docs/ast-grep_rules_catalog.md +4 -3
- package/docs/configuration.md +55 -1
- package/docs/engineering-principles.md +202 -0
- package/docs/environment-variables.md +61 -4
- package/docs/features.md +2 -2
- package/docs/globalconfig.md +6 -1
- package/docs/language-coverage.md +17 -0
- package/docs/lsp-capability-matrix.md +98 -31
- package/docs/pi-lens-fixer.md +57 -3
- package/docs/pi-lens-investigator.md +9 -0
- package/docs/pi-lens-reviewer.md +16 -0
- package/docs/pi-lens-subagent.md +9 -0
- package/docs/public-api-stability.md +1 -0
- package/docs/release-qa-baseline.md +2 -0
- package/docs/servercapabilities.md +21 -11
- package/docs/settings.md +12 -3
- package/docs/subagent-compat.md +1 -1
- package/package.json +5 -3
- package/rules/ast-grep-rules/rule-tests/no-assert-tuple-test.yml +13 -0
- package/rules/ast-grep-rules/rule-tests/no-boolean-in-except-test.yml +26 -0
- package/rules/ast-grep-rules/rule-tests/no-http-headers-bracket-access-test.yml +10 -0
- package/rules/ast-grep-rules/rule-tests/unchecked-numeric-parse-python-test.yml +18 -0
- package/rules/ast-grep-rules/rule-tests/unchecked-throwing-call-python-test.yml +4 -3
- package/rules/ast-grep-rules/rules/go-no-fmt-println.yml +2 -2
- package/rules/ast-grep-rules/rules/no-assert-tuple.yml +23 -13
- package/rules/ast-grep-rules/rules/no-boolean-in-except.yml +7 -3
- package/rules/ast-grep-rules/rules/no-console-except-error-js.yml +2 -2
- package/rules/ast-grep-rules/rules/no-console-except-error.yml +2 -2
- package/rules/ast-grep-rules/rules/no-http-headers-bracket-access.yml +12 -7
- package/rules/ast-grep-rules/rules/unchecked-numeric-parse-python.yml +44 -0
- package/rules/ast-grep-rules/rules/unchecked-throwing-call-python.yml +5 -3
- package/rules/tree-sitter-queries/typescript/console-statement.yml +2 -2
- package/scripts/install-selftest.mjs +21 -17
- package/scripts/lib/web-tree-sitter-dir.mjs +123 -0
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# ADR 0006: benchmark before a derived-state cache
|
|
2
|
+
|
|
3
|
+
## Status
|
|
4
|
+
|
|
5
|
+
Accepted — 2026-09-23
|
|
6
|
+
|
|
7
|
+
## Context
|
|
8
|
+
|
|
9
|
+
The staleness arc fixed caches whose invalidation keys could not express the
|
|
10
|
+
truth. For signatures, line counts, hashes, and import graphs, a bounded
|
|
11
|
+
request-local recompute can remove that invalidation surface. Tool-run caches
|
|
12
|
+
are a separate class because the expensive operation is the tool run.
|
|
13
|
+
|
|
14
|
+
## Decision
|
|
15
|
+
|
|
16
|
+
Do not add a cross-request derived-state cache until a fresh-process benchmark
|
|
17
|
+
shows that request-local recomputation is the cost. A justified cache carries
|
|
18
|
+
the generation from which it was derived.
|
|
19
|
+
|
|
20
|
+
## Consequences
|
|
21
|
+
|
|
22
|
+
Cheap derived state has no stale cache to invalidate. Any future cache needs a
|
|
23
|
+
benchmark with a control row, explicit freshness axes, and a measured reason
|
|
24
|
+
that the request-local pass is insufficient.
|
|
25
|
+
|
|
26
|
+
## Links
|
|
27
|
+
|
|
28
|
+
- Catalog shape: `AGENTS.md` shape 51.
|
|
29
|
+
- Issue: #1644 closure decision, 2026-09-22.
|
|
30
|
+
- PR: #3253.
|
|
31
|
+
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# ADR 0007: end-to-end witness per seam slice
|
|
2
|
+
|
|
3
|
+
## Status
|
|
4
|
+
|
|
5
|
+
Accepted — 2026-09-23
|
|
6
|
+
|
|
7
|
+
## Context
|
|
8
|
+
|
|
9
|
+
Seam umbrellas #1892, #1816, #1193, #1894, and #1844 need evidence that
|
|
10
|
+
survives adapter wiring and records both what the model receives and what the
|
|
11
|
+
degradation ledger recorded. The #3247 round-2 adapter tests establish the
|
|
12
|
+
shape through the pi and MCP turn-end surfaces.
|
|
13
|
+
|
|
14
|
+
## Decision
|
|
15
|
+
|
|
16
|
+
Every slice under those seam umbrellas adds one witness test through the real
|
|
17
|
+
host entry: pi `index.ts` `turn_end` to the runtime-context message, or the MCP
|
|
18
|
+
turn-end entry in `clients/mcp/session.ts`. The test commits the rendered
|
|
19
|
+
context message and degradation-ledger rows as a golden fixture under
|
|
20
|
+
`tests/fixtures/witness/<slice>/` and diffs it on every run. The witness is
|
|
21
|
+
owned by the #1605 lane umbrella. Process boundaries, including LSP servers
|
|
22
|
+
and tool binaries, use the real-wire fake server rather than a module mock.
|
|
23
|
+
|
|
24
|
+
## Consequences
|
|
25
|
+
|
|
26
|
+
Witnesses are the verifiable, repeatable artifact for each seam slice and
|
|
27
|
+
expose adapter drift at the host boundary. A witness is not the sole mechanism
|
|
28
|
+
at a process boundary: a module mock cannot prove argv, wire framing, process
|
|
29
|
+
lifecycle, or tool output, so those boundaries require the real-wire fake
|
|
30
|
+
server. In-process stores, sinks, coordinators, and dispatchers remain real.
|
|
31
|
+
|
|
32
|
+
## Links
|
|
33
|
+
|
|
34
|
+
- Umbrella: #1605.
|
|
35
|
+
- Seam slices: #1892, #1816, #1193, #1894, #1844.
|
|
36
|
+
- Shape: `tests/index-3246-turn-end-delivery.test.ts` and
|
|
37
|
+
`tests/clients/mcp/session-inline-blocker-dispositions.test.ts`.
|
|
38
|
+
- Adapter evidence: #3247 round 2.
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
# ADR 0008: one `TurnEndLane` interface, and what stays in the composer
|
|
2
|
+
|
|
3
|
+
## Status
|
|
4
|
+
|
|
5
|
+
Accepted — 2026-09-23
|
|
6
|
+
|
|
7
|
+
## Context
|
|
8
|
+
|
|
9
|
+
`clients/runtime-turn.ts` was 4,654 lines with fourteen delivery lanes written
|
|
10
|
+
inline in `handleTurnEnd`. Each lane read its own store, applied its own
|
|
11
|
+
freshness/disposition policy, and formatted its own section into
|
|
12
|
+
`blockerParts`/`advisoryParts`, so the composer stated every lane's rules and no
|
|
13
|
+
lane could be read, tested or changed on its own. #1892's third slice deepens
|
|
14
|
+
the composer into a thin orchestrator over lane modules that share ONE
|
|
15
|
+
interface, one lane per round, with no agent-facing change.
|
|
16
|
+
|
|
17
|
+
The constraint that shapes the interface is #3264's fold: the turn-end scanner
|
|
18
|
+
stores share ONE `gateFindingsByPathFreshness` call, so a path two stores cite
|
|
19
|
+
is `stat`'d once, spends one stat budget, and writes one bounded decision
|
|
20
|
+
record per delivery instead of up to six. A lane that gated itself would
|
|
21
|
+
re-split that pass the moment a second lane existed.
|
|
22
|
+
|
|
23
|
+
The second constraint is the delivery-surface registry
|
|
24
|
+
(`clients/finding-delivery-gate.ts`): its `@delivery-surface:` seam scan reads
|
|
25
|
+
exactly `clients/runtime-turn.ts` and `tools/lens-diagnostics.ts`. Code that
|
|
26
|
+
renders an agent-facing section from a file that scan does not read would be an
|
|
27
|
+
unregistered surface, invisible to the mechanism built to enumerate them.
|
|
28
|
+
|
|
29
|
+
## Decision
|
|
30
|
+
|
|
31
|
+
**The interface** (`clients/turn-end/lane.ts`) is three stages, and nothing else — no lane id, because nothing would read one:
|
|
32
|
+
|
|
33
|
+
| Stage | Signature | Owns |
|
|
34
|
+
|---|---|---|
|
|
35
|
+
| collect | `collect(ctx) → Promise<S>` where `S` maps STORE name → `FindingFreshnessSource` | which caches to read, classification, the per-store `citedPath`/`scannedAt`/`onMissing` policy. Structured rows with source identity, never a rendered string. Calls no gate. |
|
|
36
|
+
| gate | `gate(gates: TurnEndLaneGates<S>, ctx) → Kept` | the policy the shared pass cannot apply: dispositions through `filterFindingsByDisposition` (the seam in `clients/dispatch/finding-policy.ts`, ADR 0004), plus any per-store existence/lifecycle contract. `Kept` is lane-private. |
|
|
37
|
+
| render | `render(kept, ctx) → TurnEndLaneParts` | the sections, the display cap, the tier each section belongs to, the per-store suppressed counts, and the location keys it delivered. Returns sections; never pushes. `TurnEndLaneParts` carries a field per tier the composer actually pushes and no others — an unread field is a side channel that drops a lane's output, so the advisory tier joins the type with the lane that renders one. |
|
|
38
|
+
|
|
39
|
+
`TurnEndLaneContext` is the whole window a lane gets onto the turn: `cwd`, the
|
|
40
|
+
hook's `signal`, `readScannerCache` (memoized per turn by the composer) and
|
|
41
|
+
`peekActionableWarnings`.
|
|
42
|
+
|
|
43
|
+
**Writers by axis** — what the composer keeps, and why it cannot be a lane's:
|
|
44
|
+
|
|
45
|
+
| Axis | Owner | Why |
|
|
46
|
+
|---|---|---|
|
|
47
|
+
| Path-freshness pass (stat memo, stat budget, `finding_dead_path_drop` / `finding_stale_line_demote` rows) | composer | A cross-lane resource. Per-lane gating is #3264 undone: N stat passes, N budgets, N records for one filesystem fact. A lane only DECLARES sources. |
|
|
48
|
+
| One read per scanner store per turn | composer (`readScannerCache` memo) | The trivy store is read by the secrets lane and by the CVE/license tiers. Two envelopes of one store in one delivery is the parallel-store shape, and the TTL boundary can fall between the reads. |
|
|
49
|
+
| Disposition application | lane | Per-store anchor identity; only the lane knows which `ProjectDiagnostic` adapter anchors its rows. |
|
|
50
|
+
| Tier order, `blockerParts`/`staleSecretParts`/`advisoryParts` accumulation, the `@delivery-surface:` tags | composer | The tags are what enumerates the surfaces; the scan reads the composer. |
|
|
51
|
+
| Suppressed-by-disposition notice | composer, fed per lane | One bounded notice per turn (#1616), with per-lane attribution. |
|
|
52
|
+
| Turn signature dedupe, `turn-end-findings` cache write, git-guard record, message cap, turn telemetry | composer | Delivery-event properties of the whole turn, not of one lane. |
|
|
53
|
+
| Delivered location keys | lane produces, composer consumes | One secret is reported once across tiers (#131 Mode 3). |
|
|
54
|
+
|
|
55
|
+
**Registry evidence.** A surface whose render moved into a lane keeps its
|
|
56
|
+
registry `file` as `clients/runtime-turn.ts`, because that is where its tagged
|
|
57
|
+
push seam and its gate call live. Its evidence literal is repinned from the
|
|
58
|
+
partition it used to read (`gitleaksGate.live,`) to the gated store object the
|
|
59
|
+
composer hands the lane (`gitleaksGate,`). That is a weaker pin — it no longer
|
|
60
|
+
states which partition feeds which tier — and the loss is recorded rather than
|
|
61
|
+
hidden: the live/stale split is a lane rule now, pinned behaviourally by
|
|
62
|
+
`tests/clients/runtime-turn-finding-freshness.test.ts` and the witness goldens.
|
|
63
|
+
What the pin still proves is what the registry exists for: the rows came out of
|
|
64
|
+
a real gate call, not a hand-built object.
|
|
65
|
+
|
|
66
|
+
**Lanes that do not fit, and are not being made to.** Test-runner findings
|
|
67
|
+
(generation/retirement state, a verdict rather than diagnostics, its own
|
|
68
|
+
provenance validation at read), the git-guard latch (a durable second
|
|
69
|
+
writer/consumer with a commit-gate audience) and the agent nudge (session
|
|
70
|
+
read/edit relevance, no findings and no freshness axis at all) stay outside
|
|
71
|
+
this interface. So do the package-pinned trivy CVE/license tiers: they have no
|
|
72
|
+
cited path, so their `collect` would declare no freshness source and their
|
|
73
|
+
honesty mechanism is the age label, not the gate.
|
|
74
|
+
|
|
75
|
+
**One lane is one delivery contract, not one store.** The secrets lane
|
|
76
|
+
(`clients/turn-end/lanes/secrets.ts`) owns BOTH secret stores, because #131
|
|
77
|
+
Mode 3 collapses gitleaks, trivy-secrets and ast-grep by LOCATION into one
|
|
78
|
+
blocker section with combined provenance, and the demoted tier merges the same
|
|
79
|
+
two stores. A "gitleaks-only" lane would leave every rendering rule in the
|
|
80
|
+
composer and move only a cache read.
|
|
81
|
+
|
|
82
|
+
## Amendment — 2026-09-23: the advisory tier joins `TurnEndLaneParts`
|
|
83
|
+
|
|
84
|
+
`TurnEndLaneParts` gains ONE field, `advisoryParts?: readonly string[]`, with
|
|
85
|
+
the govulncheck lane (`clients/turn-end/lanes/govulncheck.ts`). This is the
|
|
86
|
+
amendment the Decision above anticipated ("the advisory tier joins the type
|
|
87
|
+
with the lane that renders one"), not a widening of the rule.
|
|
88
|
+
|
|
89
|
+
**Why it was withheld until now.** The rule is "a field per tier the composer
|
|
90
|
+
PUSHES, and no others", because a field the composer does not read is a side
|
|
91
|
+
channel that silently drops a lane's whole output (AGENTS.md shape 5). The
|
|
92
|
+
secrets lane renders no advisory, so an `advisoryParts` shipped with the
|
|
93
|
+
interface would have been exactly that: a slot a later lane could fill and lose.
|
|
94
|
+
The field, the composer's push and the surface's registry id therefore move
|
|
95
|
+
together, in one round, as one claim.
|
|
96
|
+
|
|
97
|
+
**What the composer pushes it into.** `handleTurnEnd`'s `advisoryParts` array
|
|
98
|
+
(`clients/runtime-turn.ts`), under the existing tag
|
|
99
|
+
`// @delivery-surface: runtime-turn:govulncheck-advisory`, at the same position
|
|
100
|
+
in the tier order the inline block occupied. The tag is what makes the tier a
|
|
101
|
+
registered delivery surface rather than a string, and
|
|
102
|
+
`tests/config/turn-end-lane-boundaries.test.ts` already listed `advisoryParts`
|
|
103
|
+
in the tier-push rule it reds on — so a lane that pushes its own advisory was
|
|
104
|
+
forbidden before the field existed, and still is.
|
|
105
|
+
|
|
106
|
+
**What did NOT change.** Still three stages; still no lane id; still no fourth
|
|
107
|
+
method. The `runtime-turn:govulncheck-advisory` registry entry keeps its `file`,
|
|
108
|
+
its `gated` mode and its evidence literal `scannerGates.govulncheck` unchanged
|
|
109
|
+
— the composer still binds that gate arm itself and hands it to the lane, so
|
|
110
|
+
unlike the secrets repin nothing was weakened here.
|
|
111
|
+
|
|
112
|
+
**The interface question this lane was asked and did not need to answer.** The
|
|
113
|
+
brief asked how a lane declares "no freshness source" honestly, since a
|
|
114
|
+
package-pinned row (trivy CVE/license) has no path to stat. The state table is:
|
|
115
|
+
|
|
116
|
+
| Lane's store | has a cited path | honesty mechanism | fits the interface today |
|
|
117
|
+
|---|---|---|---|
|
|
118
|
+
| govulncheck | yes (first trace frame with a filename) | the shared gate, `onMissing: "demote"` | yes — this round |
|
|
119
|
+
| gitleaks / trivy-secrets | yes (`finding.file`) | the shared gate, `onMissing: "drop"` | yes — #3269 |
|
|
120
|
+
| trivy CVE / license | no — pinned by package, not by file | `formatCacheAgeLabel`, the registry's `labeled` mode | NO |
|
|
121
|
+
| a finding whose `citedPath` returns `undefined` for SOME rows | mixed | the gate leaves the uncited row live and gates the rest | yes — already handled per row |
|
|
122
|
+
|
|
123
|
+
The last row is the one that matters for "no freshness source": the gate already
|
|
124
|
+
takes `citedPath` returning `undefined` as "leave live", per FINDING, so a lane
|
|
125
|
+
with a partially-uncited store needs nothing new (the govulncheck witness pins
|
|
126
|
+
it: `GO-2026-0103` has an empty trace and renders by module). A store where
|
|
127
|
+
EVERY row is uncited is a different animal — it is not a gated lane at all, it
|
|
128
|
+
is a `labeled` one, and giving it a `sources: {}` `collect` would let it pass
|
|
129
|
+
the composer's gate with an empty declaration and look gated in the registry
|
|
130
|
+
while nothing checked anything. That is the second exception the brief said not
|
|
131
|
+
to bend the lane around, so it is not added: the labeled lanes need a `label`
|
|
132
|
+
stage (an age string the composer renders into the header) before they can be
|
|
133
|
+
extracted, and that is the design decision of the round that extracts one.
|
|
134
|
+
|
|
135
|
+
## Consequences
|
|
136
|
+
|
|
137
|
+
- The composer no longer states a govulncheck rule (2026-09-23): 4,438 →
|
|
138
|
+
4,403 lines, and the six rules the block held (the `demote`/first-frame
|
|
139
|
+
freshness declaration, the both-arm disposition anchor, the stale-line
|
|
140
|
+
withholding and marker, the module/package fallback, the fix hint and the
|
|
141
|
+
display cap) are stated once, in the lane.
|
|
142
|
+
- The composer no longer states a secrets rule: 4,654 → 4,438 lines, and the
|
|
143
|
+
ten rules the block used to hold (cache key, classification budget and
|
|
144
|
+
fail-open, blocking filter, freshness declaration, two disposition anchors
|
|
145
|
+
over both arms, location dedupe, ast-grep enrichment, display cap, both
|
|
146
|
+
preambles, demoted-row identity) are stated once, in the lane.
|
|
147
|
+
- Extracting a lane moves code out from under two mechanisms that read only
|
|
148
|
+
`clients/runtime-turn.ts`, so `tests/config/turn-end-lane-boundaries.test.ts`
|
|
149
|
+
walks `clients/turn-end/` and reds if a lane pushes into a tier or calls the
|
|
150
|
+
freshness gate itself. Both rules name the recurrence they prevent.
|
|
151
|
+
- Registrations move with the code: the `bounded()` call-site registry entry,
|
|
152
|
+
the one-hop unbounded-await pin, and the delivery-surface marker exemption
|
|
153
|
+
all name the lane file now. A lane is a new file every directory-scanning
|
|
154
|
+
governance suite sees.
|
|
155
|
+
- The next lanes are extracted one per round against this interface. A lane
|
|
156
|
+
that needs a stage this interface does not have is a finding to report, not a
|
|
157
|
+
fourth method to add quietly.
|
|
158
|
+
|
|
159
|
+
## Links
|
|
160
|
+
|
|
161
|
+
- Umbrella: #1892. Previous slices: #3264 (the shared freshness pass), #3269
|
|
162
|
+
(this interface and the secrets lane).
|
|
163
|
+
- Witness convention: docs/adr/0007-end-to-end-witness-per-seam-slice.md.
|
|
164
|
+
- Disposition seam: docs/adr/0004-disposition-policy-seam.md.
|
|
165
|
+
- Registry and evidence rules: `clients/finding-delivery-gate.ts`.
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
# ADR 0009: reported-path attribution is one seam, based on the runner cwd
|
|
2
|
+
|
|
3
|
+
## Status
|
|
4
|
+
|
|
5
|
+
Accepted — 2026-09-23
|
|
6
|
+
|
|
7
|
+
## Context
|
|
8
|
+
|
|
9
|
+
Every runner that parses a tool's textual or JSON output has to answer one
|
|
10
|
+
question per reported line: *is this diagnostic about the file the dispatcher
|
|
11
|
+
ran me for?* Twelve sites answered it with their own predicate — a bare `===` or
|
|
12
|
+
`!==` over two `path.resolve` results, a `path.resolve` with NO base argument, a
|
|
13
|
+
`path.posix.basename` compare, and two `endsWith` compares. #209 and #3277 are
|
|
14
|
+
the shipped defects of the first two shapes: a spelling that differs only in
|
|
15
|
+
case is the SAME file on Windows and on a case-folding POSIX mount, so the
|
|
16
|
+
compare dropped every finding for the edited file and the run reported clean.
|
|
17
|
+
#3278 measured the population at ~11 and #1193 recorded the fold verdict.
|
|
18
|
+
|
|
19
|
+
The state space, written before the first edit. Left = the path the tool
|
|
20
|
+
printed. Right = `path.resolve(ctx.filePath)`, which is
|
|
21
|
+
`normalizeMapKey`-canonical and absolute by the #2016 invariant.
|
|
22
|
+
|
|
23
|
+
| axis | value | consequence |
|
|
24
|
+
|---|---|---|
|
|
25
|
+
| host folds case | win32 always; POSIX only on a case-folding mount (APFS, `nocase` vfat/ntfs3/cifs) | a `process.platform === "win32"` test is wrong in both directions on APFS. Only the filesystem knows, so the predicate must ask it. |
|
|
26
|
+
| reported spelling | absolute (the tool echoes our argv), relative to the tool's own cwd, relative to a package/base directory it chose, or basename-only | only the tool's OWN cwd is a base the caller can supply; `process.cwd()` is the extension's and is a different directory whenever `resolveRunnerCwd` returns a nested language root. |
|
|
27
|
+
| separators after `path.resolve` | host-native on both sides; a foreign separator survives only embedded in the reported string | separator folding can only MERGE, never split. |
|
|
28
|
+
| symlinked project dir | `pathsEqual` does NOT resolve symlinks on POSIX (`clients/path-utils.ts:181`, deliberate) | a project entered through a directory symlink can still mis-match; that is a property of the shared seam, unchanged by this decision (#3276's recorded output). |
|
|
29
|
+
| purpose | identity test of ONE file, per reported line — not a stored key, not set membership | asks for an equality predicate, not a key derivation. |
|
|
30
|
+
|
|
31
|
+
Candidate seams: `normalizeEphemeralMapKey` is rejected by its own contract
|
|
32
|
+
(`clients/path-utils.ts:488-490` — "never … compared against externally-supplied
|
|
33
|
+
paths where symlink / real-casing resolution actually matters"; the left side is
|
|
34
|
+
literally an external process's output, and it folds case on
|
|
35
|
+
`process.platform === "win32"` only). `normalizeFilePath`/`normalizeMapKey` are
|
|
36
|
+
the right rule in the wrong shape: the caller would still hold the comparison
|
|
37
|
+
and re-derive both keys. A NEW `reportedPathMatchesTarget(reported, { cwd,
|
|
38
|
+
target })` helper fails the deletion test — removing it would put back the same
|
|
39
|
+
single expression at each call site rather than concentrating anything — and it
|
|
40
|
+
would hide the comparison from the #1193 sweep's call-site walk while raising
|
|
41
|
+
the helper count.
|
|
42
|
+
|
|
43
|
+
## Decision
|
|
44
|
+
|
|
45
|
+
The predicate is `pathsEqual(path.resolve(<the cwd the tool ran in>, reported),
|
|
46
|
+
absTarget)`, written INLINE at each call site. It is not a new helper: it is the
|
|
47
|
+
call shape `clients/dispatch/runners/terragrunt.ts:90`,
|
|
48
|
+
`elixir-check.ts:63` (#3276) and `go-vet.ts:107` (#3277) already use, so this
|
|
49
|
+
decision migrates the remaining members onto an existing seam and adds nothing.
|
|
50
|
+
A parse function that does not receive the runner cwd takes it as a parameter;
|
|
51
|
+
no parse function resolves a reported path against `process.cwd()`.
|
|
52
|
+
|
|
53
|
+
Absence is enforced by `tests/config/reported-path-attribution-sweep.test.ts`, a
|
|
54
|
+
shrink-only census of runner sites that still hold their own predicate.
|
|
55
|
+
`tests/config/path-key-fold-sweep.test.ts` counts folds that EXIST and therefore
|
|
56
|
+
structurally cannot see a member with no fold at all — measured on #3277, where
|
|
57
|
+
mutating `go-vet.ts` to a hand-rolled case fold left that sweep green.
|
|
58
|
+
|
|
59
|
+
A member's fold is admitted only with its premise established: what spelling
|
|
60
|
+
does this tool really emit under OUR invocation? Where the tool echoes the
|
|
61
|
+
absolute path we hand it as argv, the fold is behaviour-preserving and its tests
|
|
62
|
+
say so, rather than feeding a spelling the tool never emits.
|
|
63
|
+
|
|
64
|
+
## Consequences
|
|
65
|
+
|
|
66
|
+
The path-case rule exists once, in `clients/path-utils.ts`; no runner holds a
|
|
67
|
+
`process.platform` test or a `toLowerCase` for this question. `pathsEqual` costs
|
|
68
|
+
a `realpathSync.native`/`existsSync` per reported line, which is bounded by the
|
|
69
|
+
tool's own output size on a parse path that already allocates per line.
|
|
70
|
+
|
|
71
|
+
Two directions are now pinned per member: the tool's real spelling attaches, and
|
|
72
|
+
a different file's finding does not. On a case-folding host the case-variant
|
|
73
|
+
spelling attaches too, and on a case-sensitive host it must not — asserted from
|
|
74
|
+
the filesystem's own answer, never from `process.platform`.
|
|
75
|
+
|
|
76
|
+
The predicate still cannot see through a directory symlink on POSIX, and a
|
|
77
|
+
runner that needs that must say so at its own call site rather than re-deriving
|
|
78
|
+
a local rule. It also cannot SPLIT a separator: `pathsEqual` folds `\` to `/` on
|
|
79
|
+
every platform (`clients/path-utils.ts:231`), so a POSIX file literally named
|
|
80
|
+
`a\b.gleam` stays merged with `a/b.gleam` after a member's own hand-rolled fold
|
|
81
|
+
is deleted. Deleting the local fold removes a second, divergent copy of the rule;
|
|
82
|
+
it does not change that answer.
|
|
83
|
+
|
|
84
|
+
Two amendments from #3285 / #3286, the two remainders:
|
|
85
|
+
|
|
86
|
+
1. The CAPTURE is part of this decision, not a detail before it. A tool renders
|
|
87
|
+
its location line however it likes, and `gleam` renders through
|
|
88
|
+
`codespan_reporting`, whose locus line wraps the path in a `┌─` gutter. A
|
|
89
|
+
suffix compare tolerates that decoration and an equality predicate cannot, so
|
|
90
|
+
a member whose reported path arrives decorated moves the decoration out of the
|
|
91
|
+
captured group in the same change — and its premise is a vector generated by
|
|
92
|
+
the upstream RENDERER (`tests/fixtures/gleam-codespan/`), not a transcription
|
|
93
|
+
of it. The same rule covers colour: a tool that may emit SGR codes goes
|
|
94
|
+
through `stripAnsi` before an anchored capture.
|
|
95
|
+
2. The population is runners AND tool clients (`clients/*-client.ts`). The autofix
|
|
96
|
+
half of a tool lives in its client, `ruff-client.ts` held the same bare `!==`,
|
|
97
|
+
and a detector scoped to `clients/dispatch/runners/**` structurally could not
|
|
98
|
+
see it. Widening cost exactly one non-member registration
|
|
99
|
+
(`clients/test-runner-client.ts`, two self-derived directories).
|
|
100
|
+
|
|
101
|
+
## Links
|
|
102
|
+
|
|
103
|
+
- Umbrella: #1193 (P3 fold), #3278 (the population).
|
|
104
|
+
- Precedents: #3276 (`elixir-check`), #3277 / PR #3281 (`go-vet`).
|
|
105
|
+
- Shipped defects: #209, #3277.
|
|
106
|
+
- Detector: `tests/config/reported-path-attribution-sweep.test.ts`.
|
|
107
|
+
- Cells: `tests/clients/dispatch/runners/reported-path-attribution.test.ts`.
|
|
108
|
+
- Witness (ADR 0007): `tests/fixtures/witness/runner-outcome-eslint-golangci/golangci-lint-relative-path.txt`.
|
|
109
|
+
- Remainders, both now closed: `gleam-check.ts` (#3285 — its `endsWith` was
|
|
110
|
+
load-bearing for `codespan_reporting`'s gutter) and `clients/ruff-client.ts`
|
|
111
|
+
(#3286 — outside the runner population).
|
|
112
|
+
- Upstream vectors: `tests/fixtures/gleam-codespan/` (gleam v1.18.1 +
|
|
113
|
+
codespan-reporting 0.13.1), `tests/fixtures/ruff-json/` (ruff 0.16.8).
|
|
114
|
+
- Cells for the client half: `tests/clients/ruff-client-reported-path.test.ts`.
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# Architecture decision records
|
|
2
|
+
|
|
3
|
+
Each record is `NNNN-title.md` and uses the same short shape: number/title,
|
|
4
|
+
status, date, Context, Decision, Consequences, and Links. Records capture a
|
|
5
|
+
settled design decision; observations without a decision remain in the defect
|
|
6
|
+
catalog or issue history.
|
|
7
|
+
|
|
8
|
+
The catalog is the index for defect shapes. A shape that records a decision
|
|
9
|
+
links its ADR in `AGENTS.md`; ADR links point to the catalog shape, the issue
|
|
10
|
+
that supplied the question, and the PR or brief that settled it. Use `refs`
|
|
11
|
+
for work that remains open and `closes` only when its acceptance criteria are
|
|
12
|
+
complete.
|
|
13
|
+
|
|
14
|
+
## Seed catalog
|
|
15
|
+
|
|
16
|
+
| ADR | Decision | Catalog shape | Status |
|
|
17
|
+
|---|---|---:|---|
|
|
18
|
+
| [0001](0001-stale-advisory-live-arm.md) | Stale advisory arm skips disposition policy; live arm applies it. | 10 | Accepted |
|
|
19
|
+
| [0002](0002-workspace-hash-rendezvous.md) | `workspaceHash` is pure cross-process rendezvous derivation. | 1, 2 | Accepted |
|
|
20
|
+
| [0003](0003-git-guard-latch-writer.md) | Git-guard latch is a separate writer on the durable record. | 24 | Accepted |
|
|
21
|
+
| [0004](0004-disposition-policy-seam.md) | Dispositions fold onto `applyFindingPolicy`; no new store before measured #1892 work. | 10, 26 | Accepted |
|
|
22
|
+
| [0005](0005-tool-availability-enforcement-seam.md) | Tool availability is one seam by enforcement, with migrate-on-touch. | 52 | Accepted |
|
|
23
|
+
| [0006](0006-derived-state-benchmark-first.md) | No cross-request derived-state cache without a fresh-process benchmark. | 51 | Accepted |
|
|
24
|
+
| [0007](0007-end-to-end-witness-per-seam-slice.md) | Every seam slice commits an end-to-end host witness and degradation ledger artifact. | #1605 umbrella; #1892, #1816, #1193, #1894, #1844 | Accepted |
|
|
25
|
+
| [0009](0009-reported-path-attribution.md) | Reported-path attribution is `pathsEqual` against the runner cwd, inline at each call site. | 2, 34, 38; #1193, #3278 | Accepted |
|
|
26
|
+
| [0008](0008-turn-end-lane-interface.md) | One `TurnEndLane` interface; `handleTurnEnd` stays a thin orchestrator over lane modules. | #1892 umbrella; #3264, #3269 | Accepted |
|
package/docs/agent-tools.md
CHANGED
|
@@ -46,7 +46,24 @@ same gate rather than bypassing it.
|
|
|
46
46
|
`workspace`; explicit `paths` always win over `scope`). `severity` is a
|
|
47
47
|
threshold, not an exact filter: `error` shows only errors; `warning` adds
|
|
48
48
|
warnings; `information` adds information; `hint`/`all` (default) show every
|
|
49
|
-
tier.
|
|
49
|
+
tier. `source=lsp` reads a file's on-disk content and will not sync it to a
|
|
50
|
+
language server past **2 MiB or 5000 lines** (`clients/runtime-config.ts`
|
|
51
|
+
`pipeline.lspMaxFileBytes`/`lspMaxFileLines`, checked in
|
|
52
|
+
`clients/lsp/content-limits.ts`) — not env-tunable today. A file over that
|
|
53
|
+
bound gets an explicit `file too large for LSP diagnostics (<N> bytes/lines
|
|
54
|
+
> <limit>)` result instead of stale or partial diagnostics. This is a
|
|
55
|
+
*different* 2 MiB from the auto-fix full-content attachment cap described in
|
|
56
|
+
[agent-guide.md](agent-guide.md), section 6 ("Auto-format / auto-fix
|
|
57
|
+
timing") — the two caps currently share one constant
|
|
58
|
+
(`RUNTIME_CONFIG.pipeline.lspMaxFileBytes`) but gate unrelated behaviors
|
|
59
|
+
(LSP sync vs. tool-result content attachment) and can diverge independently
|
|
60
|
+
in the future. Separately, every save-triggered edit now sends the server a
|
|
61
|
+
`textDocument/didSave` when it declared `textDocumentSync.save` — with the
|
|
62
|
+
file text when the server asked for it (`includeText`) and the file is
|
|
63
|
+
within the same bound, otherwise the save is still sent but without `text`
|
|
64
|
+
(never dropped outright); this is what makes save-triggered servers like
|
|
65
|
+
Expert (Elixir) re-diagnose an edit made through pi-lens (refs #3405,
|
|
66
|
+
#3408). Legacy `mode`: `delta` (current turn), `all` (resurfaces stale
|
|
50
67
|
blockers dropped from turn context), `full` (project-wide scan).
|
|
51
68
|
- **`lens_diagnostic_mark`** — Triage a diagnostic: `false-positive` /
|
|
52
69
|
`suppress` (writes an inline `pi-lens-ignore` comment) / `defer`
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
<!-- GENERATED by scripts/gen-rule-catalogs.mjs — do not edit by hand. Run `npm run docs:rule-catalogs` after changing rules. -->
|
|
4
4
|
|
|
5
|
-
pi-lens bundles **
|
|
5
|
+
pi-lens bundles **482 enabled** ast-grep rules (298 pi-lens-authored + 184 vendored from CodeRabbit) across **17 languages**, plus 16 disabled.
|
|
6
6
|
|
|
7
7
|
See [`docs/custom-rules.md`](custom-rules.md) to add your own. Rule sources: `rules/ast-grep-rules/rules/` (pi-lens), `rules/ast-grep-rules/coderabbit/rules/` (vendored).
|
|
8
8
|
|
|
@@ -260,7 +260,7 @@ See [`docs/custom-rules.md`](custom-rules.md) to add your own. Rule sources: `ru
|
|
|
260
260
|
| `openssl-cbc-static-iv-php` | warning | no | CodeRabbit | Static IV used with AES in CBC mode. Static IVs enable chosen-plaintext attacks against encrypted data. |
|
|
261
261
|
| `search-active-debug-php` | warning | no | CodeRabbit | Debug logging is explicitly enabled. This can potentially disclose sensitive information and should never be active on production systems. |
|
|
262
262
|
|
|
263
|
-
### Python (
|
|
263
|
+
### Python (99)
|
|
264
264
|
|
|
265
265
|
| Rule | Severity | Fixable | Source | Description |
|
|
266
266
|
|---|---|---|---|---|
|
|
@@ -271,7 +271,7 @@ See [`docs/custom-rules.md`](custom-rules.md) to add your own. Rule sources: `ru
|
|
|
271
271
|
| `hashids-with-flask-secret-python` | warning | no | CodeRabbit | The Flask secret key is used as salt in HashIDs. The HashID mechanism is not secure. By observing sufficient HashIDs, the salt used to cons… |
|
|
272
272
|
| `insecure-cipher-algorithm-rc4-python` | warning | no | CodeRabbit | Detected ARC4 cipher algorithm which is considered insecure. This algorithm is not cryptographically secure and can be reversed easily. Use… |
|
|
273
273
|
| `jwt-python-hardcoded-secret-python` | warning | no | CodeRabbit | Hardcoded JWT secret or private key is used. This is a Insufficiently Protected Credentials weakness: https://cwe.mitre.org/data/definition… |
|
|
274
|
-
| `no-assert-tuple` |
|
|
274
|
+
| `no-assert-tuple` | warning | no | pi-lens | Assert should not be called on a tuple literal |
|
|
275
275
|
| `no-aws-access-key-literal` | error | no | pi-lens | AWS access keys should not be hardcoded — use IAM roles, environment variables, or AWS Secrets Manager |
|
|
276
276
|
| `no-aws-apigateway-no-auth` | error | no | pi-lens | AWS API Gateway should require authentication |
|
|
277
277
|
| `no-aws-s3-public-access` | error | no | pi-lens | S3 buckets should not grant access to all users or authenticated users |
|
|
@@ -361,6 +361,7 @@ See [`docs/custom-rules.md`](custom-rules.md) to add your own. Rule sources: `ru
|
|
|
361
361
|
| `python-webrepl-empty-password-python` | warning | no | CodeRabbit | The application creates a database connection with an empty password. This can lead to unauthorized access by either an internal or externa… |
|
|
362
362
|
| `python-webrepl-hardcoded-secret-python` | warning | no | CodeRabbit | A secret is hard-coded in the application. Secrets stored in source code, such as credentials, identifiers, and other types of sensitive da… |
|
|
363
363
|
| `reducible-list-comprehension` | warning | yes | pi-lens | A list comprehension is unnecessary here; use a generator expression |
|
|
364
|
+
| `unchecked-numeric-parse-python` | warning | no | pi-lens | int()/float() without try/except — raises ValueError on non-numeric input |
|
|
364
365
|
| `unchecked-throwing-call-python` | error | no | pi-lens | Call without try/except — throws on invalid input or missing file |
|
|
365
366
|
|
|
366
367
|
### Ruby (20)
|
package/docs/configuration.md
CHANGED
|
@@ -5,7 +5,7 @@ There are **two** pi-lens config files:
|
|
|
5
5
|
| File | Scope | Notes |
|
|
6
6
|
| --- | --- | --- |
|
|
7
7
|
| `.pi-lens.json` | the project | Committed or not, your call. Nearest one wins **per field** — a package can override one setting without restating the repo root's. |
|
|
8
|
-
| `~/.pi-lens/config.json` | the machine | Your defaults across every project.
|
|
8
|
+
| `~/.pi-lens/config.json` | the machine | Your defaults across every project. The winning global location is selected by the [global-location table](#global-config-location); see [environment variables](environment-variables.md) for the knobs. |
|
|
9
9
|
|
|
10
10
|
Both files have the same shape, with one exception noted below the example:
|
|
11
11
|
everything LSP-related lives under an `lsp` namespace inside them.
|
|
@@ -86,6 +86,60 @@ Subsystem-specific env overrides follow the same shape: a
|
|
|
86
86
|
`reviewGraph.maxFiles`. `docs/environment-variables.md` and `docs/settings.md`
|
|
87
87
|
are the per-setting references.
|
|
88
88
|
|
|
89
|
+
### Global config location
|
|
90
|
+
|
|
91
|
+
The global file is selected by this order, highest precedence first. The
|
|
92
|
+
canonical default is still `~/.pi-lens/config.json`; `PI_LENS_HOME` relocates
|
|
93
|
+
machine-generated data and state, not this file. This is the complete truth
|
|
94
|
+
table for the three existence axes.
|
|
95
|
+
|
|
96
|
+
| `PI_LENS_CONFIG_PATH` | `~/.pi-lens/config.json` exists | `PI_CODING_AGENT_DIR` set and `extensions/pi-lens.json` exists | Winner |
|
|
97
|
+
| --- | --- | --- | --- |
|
|
98
|
+
| unset | no | no | `~/.pi-lens/config.json` (canonical default) |
|
|
99
|
+
| unset | no | yes | `$PI_CODING_AGENT_DIR/extensions/pi-lens.json` |
|
|
100
|
+
| unset | yes | no | `~/.pi-lens/config.json` (grandfathered existing file) |
|
|
101
|
+
| unset | yes | yes | `~/.pi-lens/config.json` (the host file is shadowed; shadowed file reported once per session (#3299)) |
|
|
102
|
+
| set | no | no | the explicit `PI_LENS_CONFIG_PATH` file |
|
|
103
|
+
| set | no | yes | the explicit `PI_LENS_CONFIG_PATH` file |
|
|
104
|
+
| set | yes | no | the explicit `PI_LENS_CONFIG_PATH` file |
|
|
105
|
+
| set | yes | yes | the explicit `PI_LENS_CONFIG_PATH` file |
|
|
106
|
+
|
|
107
|
+
If an existence probe for a recognized candidate errors (for example,
|
|
108
|
+
`ENOTDIR`, `EACCES`, or `ELOOP`), that candidate's tier is retained rather
|
|
109
|
+
than falling through to a lower location. The subsequent read reports the
|
|
110
|
+
degraded config under `PILENS_CFG_0001`; a probe error is not treated as
|
|
111
|
+
absence.
|
|
112
|
+
|
|
113
|
+
pi-lens does not write this file. Create or edit it yourself, then start a new
|
|
114
|
+
process. For an XDG-like setup, put generated data and state at a durable data
|
|
115
|
+
root and choose one config surface:
|
|
116
|
+
|
|
117
|
+
```sh
|
|
118
|
+
export PI_LENS_HOME="$HOME/.local/share/pi-lens"
|
|
119
|
+
|
|
120
|
+
# XDG-shaped host: use its config directory.
|
|
121
|
+
export PI_CODING_AGENT_DIR="${XDG_CONFIG_HOME:-$HOME/.config}/pi"
|
|
122
|
+
mkdir -p "$PI_CODING_AGENT_DIR/extensions"
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
For hosts without `PI_CODING_AGENT_DIR`, use an explicit config path instead:
|
|
126
|
+
|
|
127
|
+
```sh
|
|
128
|
+
export PI_LENS_CONFIG_PATH="${XDG_CONFIG_HOME:-$HOME/.config}/pi-lens/config.json"
|
|
129
|
+
mkdir -p "$(dirname "$PI_LENS_CONFIG_PATH")"
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
When migrating an existing `~/.pi-lens/config.json`, create the new file first
|
|
133
|
+
and delete the old file second. While both exist, the old file wins by design;
|
|
134
|
+
deleting it first would leave the process without the intended settings. Copy
|
|
135
|
+
the file (or hand-author it, since pi-lens has no writer), verify the new
|
|
136
|
+
location, then run `rm "$HOME/.pi-lens/config.json"` to opt out of
|
|
137
|
+
grandfathering. If the new host file is later deleted, the old file wins again
|
|
138
|
+
while it exists; with neither file present, the canonical default path is used.
|
|
139
|
+
|
|
140
|
+
See also the [environment-variable reference](environment-variables.md#config-location)
|
|
141
|
+
and [settings overview](settings.md#the-three-ways-to-configure-pi-lens).
|
|
142
|
+
|
|
89
143
|
### One exception: `lsp.disabledServers` is a denial, not a value
|
|
90
144
|
|
|
91
145
|
Ordinary settings are last-tier-wins. A **denial** is not, because the tier
|