@herbertgao/pi-extensions 2026.8.5 → 2026.8.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -5
- package/node_modules/@herbertgao/pi-cc-extensions/README.en.md +1 -1
- package/node_modules/@herbertgao/pi-cc-extensions/README.md +1 -1
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/feature/context.ts +74 -5
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/markdown-enhance.ts +48 -6
- package/node_modules/@herbertgao/pi-cc-extensions/package.json +3 -3
- package/node_modules/@herbertgao/pi-subagents/CHANGELOG.md +10 -0
- package/node_modules/@herbertgao/pi-subagents/package.json +3 -3
- package/node_modules/@herbertgao/pi-subagents/src/agent-runner.ts +5 -2
- package/node_modules/@herbertgao/pi-subagents/src/ui/fleet-list.ts +15 -6
- package/node_modules/@herbertgao/pi-subagents/src/worktree.ts +9 -5
- package/node_modules/@juicesharp/rpiv-ask-user-question/README.md +2 -0
- package/node_modules/@juicesharp/rpiv-ask-user-question/ask-user-question.ts +20 -0
- package/node_modules/@juicesharp/rpiv-ask-user-question/docs/hosts.md +6 -0
- package/node_modules/@juicesharp/rpiv-ask-user-question/package.json +2 -2
- package/node_modules/@narumitw/pi-btw/README.md +24 -17
- package/node_modules/@narumitw/pi-btw/package.json +5 -5
- package/node_modules/@narumitw/pi-btw/src/btw.ts +4 -2
- package/node_modules/@narumitw/pi-btw/src/menu.ts +33 -13
- package/node_modules/@narumitw/pi-btw/src/settings.ts +22 -2
- package/node_modules/pi-lens/CHANGELOG.md +95 -0
- package/node_modules/pi-lens/dist/clients/advisory-provenance.js +314 -0
- package/node_modules/pi-lens/dist/clients/agent-nudge.js +14 -7
- package/node_modules/pi-lens/dist/clients/biome-client.js +121 -13
- package/node_modules/pi-lens/dist/clients/bus-events-logger.js +62 -6
- package/node_modules/pi-lens/dist/clients/bus-publish.js +11 -3
- package/node_modules/pi-lens/dist/clients/cascade-format.js +57 -2
- package/node_modules/pi-lens/dist/clients/console-guard-install.js +16 -4
- package/node_modules/pi-lens/dist/clients/dead-code-client.js +135 -30
- package/node_modules/pi-lens/dist/clients/dependency-checker.js +19 -7
- package/node_modules/pi-lens/dist/clients/diagnostic-dispositions.js +6 -4
- package/node_modules/pi-lens/dist/clients/diagnostics-publish.js +10 -3
- package/node_modules/pi-lens/dist/clients/dispatch/dispatcher.js +114 -10
- package/node_modules/pi-lens/dist/clients/dispatch/integration.js +101 -16
- package/node_modules/pi-lens/dist/clients/dispatch/runners/lsp.js +37 -4
- package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/availability-policy.js +226 -0
- package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/candidate-probe.js +69 -0
- package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/runner-helpers.js +230 -50
- package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/toolchain-availability.js +97 -0
- package/node_modules/pi-lens/dist/clients/disposition-publish.js +10 -3
- package/node_modules/pi-lens/dist/clients/eval-timestamp.js +17 -0
- package/node_modules/pi-lens/dist/clients/extension-log.js +296 -3
- package/node_modules/pi-lens/dist/clients/fix-worklog.js +5 -1
- package/node_modules/pi-lens/dist/clients/format-events-publish.js +39 -8
- package/node_modules/pi-lens/dist/clients/git-guard.js +18 -21
- package/node_modules/pi-lens/dist/clients/go-client.js +21 -39
- package/node_modules/pi-lens/dist/clients/govulncheck-client.js +116 -7
- package/node_modules/pi-lens/dist/clients/host-ports.js +1 -1
- package/node_modules/pi-lens/dist/clients/installer/index.js +5 -1
- package/node_modules/pi-lens/dist/clients/jscpd-client.js +4 -9
- package/node_modules/pi-lens/dist/clients/knip-client.js +51 -14
- package/node_modules/pi-lens/dist/clients/latency-logger.js +50 -1
- package/node_modules/pi-lens/dist/clients/lens-events.js +56 -25
- package/node_modules/pi-lens/dist/clients/lens-flag-registry.js +8 -0
- package/node_modules/pi-lens/dist/clients/live-bus-emitter.js +45 -2
- package/node_modules/pi-lens/dist/clients/lsp/aggregation.js +30 -4
- package/node_modules/pi-lens/dist/clients/lsp/cascade-tier.js +58 -13
- package/node_modules/pi-lens/dist/clients/lsp/client.js +169 -8
- package/node_modules/pi-lens/dist/clients/lsp/diagnostic-binding.js +29 -0
- package/node_modules/pi-lens/dist/clients/lsp/index.js +264 -66
- package/node_modules/pi-lens/dist/clients/lsp/server.js +247 -60
- package/node_modules/pi-lens/dist/clients/lsp/tsserver-sync.js +96 -0
- package/node_modules/pi-lens/dist/clients/lsp/wait-policy/classification.js +21 -5
- package/node_modules/pi-lens/dist/clients/lsp/wait-policy/strategies.js +18 -3
- package/node_modules/pi-lens/dist/clients/mcp/analyze.js +4 -0
- package/node_modules/pi-lens/dist/clients/mcp/session.js +30 -17
- package/node_modules/pi-lens/dist/clients/model-provider.js +53 -0
- package/node_modules/pi-lens/dist/clients/pipeline.js +29 -2
- package/node_modules/pi-lens/dist/clients/project-diagnostics/fresh-fetch.js +1 -1
- package/node_modules/pi-lens/dist/clients/project-diagnostics/runner-adapters/runner-findings.js +23 -2
- package/node_modules/pi-lens/dist/clients/review-graph/query.js +24 -0
- package/node_modules/pi-lens/dist/clients/run-duration.js +55 -0
- package/node_modules/pi-lens/dist/clients/runtime-agent-end.js +153 -7
- package/node_modules/pi-lens/dist/clients/runtime-context.js +104 -11
- package/node_modules/pi-lens/dist/clients/runtime-coordinator.js +170 -22
- package/node_modules/pi-lens/dist/clients/runtime-session.js +28 -5
- package/node_modules/pi-lens/dist/clients/runtime-tool-result.js +124 -10
- package/node_modules/pi-lens/dist/clients/runtime-turn.js +388 -35
- package/node_modules/pi-lens/dist/clients/rust-client.js +21 -37
- package/node_modules/pi-lens/dist/clients/security-scan-client.js +88 -5
- package/node_modules/pi-lens/dist/clients/sg-runner.js +141 -23
- package/node_modules/pi-lens/dist/clients/smells-rollup.js +18 -11
- package/node_modules/pi-lens/dist/clients/startup-timing.js +7 -1
- package/node_modules/pi-lens/dist/clients/test-runner-client.js +431 -24
- package/node_modules/pi-lens/dist/clients/tool-policy.js +2 -0
- package/node_modules/pi-lens/dist/clients/tool-set-policy.js +76 -0
- package/node_modules/pi-lens/dist/clients/warm-attach.js +17 -0
- package/node_modules/pi-lens/dist/clients/word-index.js +305 -33
- package/node_modules/pi-lens/dist/index.js +4827 -1633
- package/node_modules/pi-lens/dist/mcp/server.js +8 -2
- package/node_modules/pi-lens/dist/tools/activate-tools.js +17 -5
- package/node_modules/pi-lens/dist/tools/ast-grep-replace.js +9 -4
- package/node_modules/pi-lens/dist/tools/lens-diagnostic-mark.js +5 -2
- package/node_modules/pi-lens/dist/tools/lens-diagnostics.js +3 -2
- package/node_modules/pi-lens/dist/tools/lsp-diagnostics.js +62 -7
- package/node_modules/pi-lens/dist/tools/symbol-search.js +1 -1
- package/node_modules/pi-lens/docs/agent-guide.md +38 -13
- package/node_modules/pi-lens/docs/ast-grep_rules_catalog.md +11 -3
- package/node_modules/pi-lens/docs/features.md +14 -1
- package/node_modules/pi-lens/docs/globalconfig.md +11 -0
- package/node_modules/pi-lens/docs/servercapabilities.md +1 -1
- package/node_modules/pi-lens/docs/settings.md +6 -0
- package/node_modules/pi-lens/docs/usage.md +23 -5
- package/node_modules/pi-lens/docs/word-index.md +35 -0
- package/node_modules/pi-lens/package.json +1 -1
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-chained-type-assertions-test.yml +8 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-conditional-empty-object-spread-js-test.yml +9 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-conditional-empty-object-spread-test.yml +9 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-reflect-apply-js-test.yml +7 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-reflect-apply-test.yml +7 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-reflect-get-js-test.yml +8 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-reflect-get-test.yml +8 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-unknown-laundering-test.yml +11 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-chained-type-assertions.yml +21 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-conditional-empty-object-spread-js.yml +21 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-conditional-empty-object-spread.yml +29 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-reflect-apply-js.yml +9 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-reflect-apply.yml +9 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-reflect-get-js.yml +13 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-reflect-get.yml +16 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-unknown-laundering.yml +27 -0
- package/node_modules/pi-lens/scripts/analyze-pi-lens-logs.mjs +79 -0
- package/node_modules/pi-lens/skills/pi-lens-ast-grep/SKILL.md +10 -11
- package/node_modules/pi-lens/skills/pi-lens-lsp-navigation/SKILL.md +22 -22
- package/node_modules/pi-lens/skills/pi-lens-write-ast-grep-rule/SKILL.md +8 -114
- package/node_modules/pi-lens/skills/pi-lens-write-ast-grep-rule/reference.md +129 -0
- package/node_modules/pi-lens/skills/pi-lens-write-tree-sitter-rule/SKILL.md +3 -1
- package/node_modules/pi-mcp-adapter/CHANGELOG.md +13 -0
- package/node_modules/pi-mcp-adapter/README.md +4 -1
- package/node_modules/pi-mcp-adapter/agent-dir.ts +12 -4
- package/node_modules/pi-mcp-adapter/cli.js +25 -4
- package/node_modules/pi-mcp-adapter/config.ts +4 -4
- package/node_modules/pi-mcp-adapter/direct-tools.ts +18 -15
- package/node_modules/pi-mcp-adapter/mcp-setup-panel.ts +2 -1
- package/node_modules/pi-mcp-adapter/metadata-cache.ts +18 -8
- package/node_modules/pi-mcp-adapter/package.json +2 -1
- package/node_modules/pi-mcp-adapter/request-headers-command.ts +336 -0
- package/node_modules/pi-mcp-adapter/server-manager.ts +5 -0
- package/node_modules/pi-mcp-adapter/tool-metadata.ts +38 -18
- package/node_modules/pi-mcp-adapter/types.ts +93 -10
- package/node_modules/pi-web-access/CHANGELOG.md +14 -0
- package/node_modules/pi-web-access/README.md +18 -12
- package/node_modules/pi-web-access/auth-fetch.ts +148 -0
- package/node_modules/pi-web-access/chrome-cookies.ts +110 -23
- package/node_modules/pi-web-access/curator-page.ts +5 -3
- package/node_modules/pi-web-access/curator-server.ts +2 -1
- package/node_modules/pi-web-access/extract.ts +106 -34
- package/node_modules/pi-web-access/fetch-params.ts +17 -3
- package/node_modules/pi-web-access/firecrawl.ts +172 -12
- package/node_modules/pi-web-access/gemini-search.ts +18 -4
- package/node_modules/pi-web-access/index.ts +120 -48
- package/node_modules/pi-web-access/package.json +2 -2
- package/node_modules/pi-web-access/summary-review.ts +11 -5
- package/node_modules/pi-web-access/youtube-extract.ts +2 -2
- package/package.json +9 -9
|
@@ -29,6 +29,7 @@ import { acknowledgeTurnEnd, analyzeFile, analyzeFileFresh, canRebuildPiLens, cr
|
|
|
29
29
|
import { createAstGrepReplaceTool } from "../tools/ast-grep-replace.js";
|
|
30
30
|
import { createAstGrepSearchTool } from "../tools/ast-grep-search.js";
|
|
31
31
|
import { createLensDiagnosticsTool } from "../tools/lens-diagnostics.js";
|
|
32
|
+
import { peekMcpSessionRuntime } from "../clients/mcp/session.js";
|
|
32
33
|
import { createLspDiagnosticsTool } from "../tools/lsp-diagnostics.js";
|
|
33
34
|
import { createLspNavigationTool } from "../tools/lsp-navigation.js";
|
|
34
35
|
import { computeBuildStamp, STALE_SERVED_BY_FRESH, STALE_WARN_ONLY, StalenessGate, stalenessCheckEnabled, } from "./build-staleness.js";
|
|
@@ -411,7 +412,12 @@ const cacheManager = new CacheManager();
|
|
|
411
412
|
// nothing — a no-op dressed as a fix, not a real parity gap. The 4th arg is left
|
|
412
413
|
// at its default (`async () => {}`, already a no-op) rather than importing and
|
|
413
414
|
// wiring a flush with nothing to flush.
|
|
414
|
-
const lensDiagnosticsTool = createLensDiagnosticsTool(cacheManager, () => DEFAULT_CWD
|
|
415
|
+
const lensDiagnosticsTool = createLensDiagnosticsTool(cacheManager, () => DEFAULT_CWD, undefined, undefined, undefined, undefined,
|
|
416
|
+
// #1413 surface parity: validate cached test-runner findings against the
|
|
417
|
+
// same session identity the in-process path uses. Resolved lazily — until
|
|
418
|
+
// an MCP session context exists there is no session to compare against and
|
|
419
|
+
// validation skips the check, which is the honest classification.
|
|
420
|
+
() => peekMcpSessionRuntime());
|
|
415
421
|
const astGrepClient = new AstGrepClient();
|
|
416
422
|
const astGrepSearchTool = createAstGrepSearchTool(astGrepClient);
|
|
417
423
|
const astGrepReplaceTool = createAstGrepReplaceTool(astGrepClient);
|
|
@@ -544,7 +550,7 @@ const ALL_TOOLS = [
|
|
|
544
550
|
properties: {
|
|
545
551
|
query: {
|
|
546
552
|
type: "string",
|
|
547
|
-
description: "Identifier-ish query, e.g. 'authenticate user'.",
|
|
553
|
+
description: "Identifier-ish query, e.g. 'authenticate user'. Mix in composable prefix filters: lang:<kind> (e.g. lang:jsts, lang:python — kinds from file-kinds.ts), file:<substr> (path substring), ext:<ext> (e.g. ext:ts or ext:.ts), each optionally negated with a leading '-' (-file:test). Filters apply before ranking; e.g. 'lang:jsts file:clients/ -file:test rank'. Unknown prefixes/kinds error with the supported list.",
|
|
548
554
|
},
|
|
549
555
|
cwd: { type: "string" },
|
|
550
556
|
limit: {
|
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
* instead, so calling this tool is harmless, just unnecessary.
|
|
19
19
|
*/
|
|
20
20
|
import { Type } from "../clients/deps/typebox.js";
|
|
21
|
-
export function createActivateToolsTool(pi, lazyTools) {
|
|
21
|
+
export function createActivateToolsTool(pi, lazyTools, options = {}) {
|
|
22
22
|
const lazyNames = lazyTools.map((t) => t.name);
|
|
23
23
|
const lazyNameSet = new Set(lazyNames);
|
|
24
24
|
const catalog = lazyTools.map((t) => `${t.name} — ${t.summary}`).join("\n");
|
|
@@ -35,7 +35,7 @@ export function createActivateToolsTool(pi, lazyTools) {
|
|
|
35
35
|
description: "Names of situational tools to activate (see this tool's description for the catalog).",
|
|
36
36
|
}),
|
|
37
37
|
}),
|
|
38
|
-
async execute(_toolCallId, params, _signal, _onUpdate) {
|
|
38
|
+
async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
|
|
39
39
|
const requested = Array.isArray(params.tools)
|
|
40
40
|
? params.tools.filter((t) => typeof t === "string" && lazyNameSet.has(t))
|
|
41
41
|
: [];
|
|
@@ -53,10 +53,22 @@ export function createActivateToolsTool(pi, lazyTools) {
|
|
|
53
53
|
}
|
|
54
54
|
// Additive only, per the docs' contract: never drop currently active
|
|
55
55
|
// tools in the same call.
|
|
56
|
+
// Remember every requested tool, not just the newly-added ones: a
|
|
57
|
+
// tool that is already active still has to survive the next
|
|
58
|
+
// fork/reload/resume restore.
|
|
59
|
+
options.onActivated?.(requested);
|
|
56
60
|
const active = typeof pi.getActiveTools === "function" ? pi.getActiveTools() : [];
|
|
57
|
-
const
|
|
58
|
-
|
|
61
|
+
const activeSet = new Set(active);
|
|
62
|
+
const added = requested.filter((name) => !activeSet.has(name));
|
|
63
|
+
const merged = [...new Set([...active, ...added])];
|
|
64
|
+
if (added.length > 0 && typeof pi.setActiveTools === "function") {
|
|
59
65
|
pi.setActiveTools(merged);
|
|
66
|
+
options.onMutation?.({
|
|
67
|
+
addedCount: added.length,
|
|
68
|
+
removedCount: 0,
|
|
69
|
+
reason: "lazy_activation",
|
|
70
|
+
deferralApplies: options.deferredToolSupport?.(ctx) ?? false,
|
|
71
|
+
});
|
|
60
72
|
}
|
|
61
73
|
return {
|
|
62
74
|
content: [
|
|
@@ -65,7 +77,7 @@ export function createActivateToolsTool(pi, lazyTools) {
|
|
|
65
77
|
text: `Activated: ${requested.join(", ")}. Available starting next turn.`,
|
|
66
78
|
},
|
|
67
79
|
],
|
|
68
|
-
details: { matches: requested, added
|
|
80
|
+
details: { matches: requested, added },
|
|
69
81
|
};
|
|
70
82
|
},
|
|
71
83
|
};
|
|
@@ -67,7 +67,12 @@ export function createAstGrepReplaceTool(astGrepClient) {
|
|
|
67
67
|
}),
|
|
68
68
|
paths: Type.Optional(Type.Array(Type.String(), { description: "Specific files/folders" })),
|
|
69
69
|
insideKind: Type.Optional(Type.String({ description: "Restrict matches to nodes inside an ancestor of this AST node kind. Synthesizes a YAML rule." })),
|
|
70
|
-
hasKind: Type.Optional(Type.String({
|
|
70
|
+
hasKind: Type.Optional(Type.String({
|
|
71
|
+
description: 'Restrict matches to nodes whose immediate child has this AST node kind (ast-grep default stopBy: neighbor). Example: `hasKind: "await_expression"`.',
|
|
72
|
+
})),
|
|
73
|
+
hasDescendantKind: Type.Optional(Type.String({
|
|
74
|
+
description: "Restrict matches to nodes containing this AST node kind anywhere in their descendants. Explicit recursive form (`stopBy: end`); use this instead of `hasKind` when nesting is not immediate.",
|
|
75
|
+
})),
|
|
71
76
|
follows: Type.Optional(Type.String({ description: "Restrict matches to nodes that immediately follow a sibling matching this pattern." })),
|
|
72
77
|
precedes: Type.Optional(Type.String({ description: "Restrict matches to nodes that immediately precede a sibling matching this pattern." })),
|
|
73
78
|
apply: Type.Optional(Type.Boolean({ description: "Apply changes (default: false)" })),
|
|
@@ -78,7 +83,7 @@ export function createAstGrepReplaceTool(astGrepClient) {
|
|
|
78
83
|
}),
|
|
79
84
|
async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
|
|
80
85
|
const startedAt = Date.now();
|
|
81
|
-
const { pattern, rewrite, paths, apply, strictness, insideKind, hasKind, follows, precedes } = params;
|
|
86
|
+
const { pattern, rewrite, paths, apply, strictness, insideKind, hasKind, hasDescendantKind, follows, precedes } = params;
|
|
82
87
|
const lang = (params.lang ?? "").replace(/^"|"$/g, "");
|
|
83
88
|
const pathsCount = paths?.length ?? 1;
|
|
84
89
|
const applyFlag = apply ?? false;
|
|
@@ -122,10 +127,10 @@ export function createAstGrepReplaceTool(astGrepClient) {
|
|
|
122
127
|
}
|
|
123
128
|
const searchPaths = paths?.length ? paths : [ctx.cwd || "."];
|
|
124
129
|
// Phase 3: structural-intent params → synthesize YAML with fix: field
|
|
125
|
-
if (hasStructuralIntent({ insideKind, hasKind, follows, precedes })) {
|
|
130
|
+
if (hasStructuralIntent({ insideKind, hasKind, hasDescendantKind, follows, precedes })) {
|
|
126
131
|
let ruleYaml;
|
|
127
132
|
try {
|
|
128
|
-
ruleYaml = synthesizeReplaceRule({ pattern, lang, rewrite, insideKind, hasKind, follows, precedes });
|
|
133
|
+
ruleYaml = synthesizeReplaceRule({ pattern, lang, rewrite, insideKind, hasKind, hasDescendantKind, follows, precedes });
|
|
129
134
|
}
|
|
130
135
|
catch (err) {
|
|
131
136
|
logOutcome("error", { errorRaw: String(err) });
|
|
@@ -162,7 +162,10 @@ function verifyLine(content, absPath, tool, rule, message, callerLine) {
|
|
|
162
162
|
};
|
|
163
163
|
}
|
|
164
164
|
}
|
|
165
|
-
export function createLensDiagnosticMarkTool(getCwd
|
|
165
|
+
export function createLensDiagnosticMarkTool(getCwd,
|
|
166
|
+
/** Runtime telemetry identity, when known (#1448 class sweep) — attributed
|
|
167
|
+
* onto the disposition log alongside the mark. */
|
|
168
|
+
getIdentity) {
|
|
166
169
|
return {
|
|
167
170
|
name: "lens_diagnostic_mark",
|
|
168
171
|
label: "Mark Diagnostic",
|
|
@@ -326,7 +329,7 @@ export function createLensDiagnosticMarkTool(getCwd) {
|
|
|
326
329
|
message,
|
|
327
330
|
line: verifiedLine,
|
|
328
331
|
content,
|
|
329
|
-
}, disposition, reason);
|
|
332
|
+
}, disposition, reason, getIdentity?.());
|
|
330
333
|
const verb = disposition === "suppress"
|
|
331
334
|
? `suppressed (inline pi-lens-ignore comment written above line ${verifiedLine})`
|
|
332
335
|
: disposition === "defer"
|
|
@@ -118,7 +118,7 @@ nextWriteIndex,
|
|
|
118
118
|
// #798/#338: capture host UI methods synchronously from the active tool event.
|
|
119
119
|
// The returned closure is safe for the later async sweep to invoke because it
|
|
120
120
|
// never dereferences the session-guarded ctx.ui getter.
|
|
121
|
-
captureLspStatusRepaint) {
|
|
121
|
+
captureLspStatusRepaint, getRuntime) {
|
|
122
122
|
return {
|
|
123
123
|
name: "lens_diagnostics",
|
|
124
124
|
label: "Project Diagnostics",
|
|
@@ -293,6 +293,7 @@ captureLspStatusRepaint) {
|
|
|
293
293
|
onServerReady: repaintLspStatus,
|
|
294
294
|
pathsScope,
|
|
295
295
|
nextWriteIndex,
|
|
296
|
+
runtime: getRuntime?.(),
|
|
296
297
|
});
|
|
297
298
|
}
|
|
298
299
|
return formatDeltaMode(cacheManager, cwd, severity, pathsScope);
|
|
@@ -935,7 +936,7 @@ async function formatFullMode(cwd, severity, lspService, cacheManager, options =
|
|
|
935
936
|
// scan, makes it genuinely concurrent — all three phases now race the SAME
|
|
936
937
|
// signal from the same starting point instead of stacking.
|
|
937
938
|
const analyzersPromise = shouldIncludeProjectRunners(options.refreshRunners)
|
|
938
|
-
? loadBootstrapClients().then((clients) => fetchFreshProjectDiagnostics(cacheManager, cwd, clients, signal))
|
|
939
|
+
? loadBootstrapClients().then((clients) => fetchFreshProjectDiagnostics(cacheManager, cwd, clients, signal, { runtime: options.runtime }))
|
|
939
940
|
: Promise.resolve({
|
|
940
941
|
diagnostics: [],
|
|
941
942
|
runners: [],
|
|
@@ -14,7 +14,7 @@ import { primaryServerId } from "../clients/lsp/config.js";
|
|
|
14
14
|
import { combineAbortSignals, withDeadline } from "../clients/deadline-utils.js";
|
|
15
15
|
import { applyAuxiliarySuppressions, retagAuxiliaryDiagnostics, } from "../clients/dispatch/auxiliary-lsp.js";
|
|
16
16
|
import { detectFileRole } from "../clients/file-role.js";
|
|
17
|
-
import { hashDiagnosticContent, } from "../clients/lsp/diagnostic-binding.js";
|
|
17
|
+
import { hashDiagnosticContent, touchCompletedConfirmationPolicy, touchCoverageGap, } from "../clients/lsp/diagnostic-binding.js";
|
|
18
18
|
import { classifyCascadeWaitTier } from "../clients/lsp/wait-policy/index.js";
|
|
19
19
|
import { attemptTsserverSyncDiagnostics, } from "../clients/lsp/tsserver-sync.js";
|
|
20
20
|
import { convertLspDiagnostics } from "../clients/dispatch/utils/lsp-diagnostics.js";
|
|
@@ -471,8 +471,15 @@ async function collectDiagnosticsForFile(absPath, lspService, waitMs, serverScop
|
|
|
471
471
|
// confirmation: it carries the same caveat all-scope local
|
|
472
472
|
// touches do, and classic TypeScript still needs the primary-only
|
|
473
473
|
// tsserver sync check below rather than this verdict.
|
|
474
|
-
|
|
474
|
+
// #1470: `"partial"` counts, for the same reason the local touch
|
|
475
|
+
// branch below accepts it — the incumbent's primary confirmed; only
|
|
476
|
+
// a named auxiliary did not.
|
|
477
|
+
confirmedByTouch: attached.response.confirmation !== undefined &&
|
|
475
478
|
primaryServerId(absPath) !== "typescript",
|
|
479
|
+
// #1470: the incumbent's narrowed verdict, carried as an explicit DTO
|
|
480
|
+
// field across the socket. An older incumbent omits it → empty → the
|
|
481
|
+
// pre-#1470 handling, unchanged.
|
|
482
|
+
unconfirmedServerIds: attached.response.unconfirmedServerIds ?? [],
|
|
476
483
|
content,
|
|
477
484
|
};
|
|
478
485
|
}
|
|
@@ -526,11 +533,20 @@ async function collectDiagnosticsForFile(absPath, lspService, waitMs, serverScop
|
|
|
526
533
|
// one; the openFile-only / getDiagnostics fallback leaves it undefined →
|
|
527
534
|
// "unknown", no demotion).
|
|
528
535
|
const binding = usedTouch ? touched?.binding : undefined;
|
|
529
|
-
|
|
536
|
+
// #1470: `"partial"` counts here. `confirmedByTouch` feeds
|
|
537
|
+
// `canTrustTouchConfirmation`, which asks about the PRIMARY's own verdict —
|
|
538
|
+
// and a partial touch is one whose primary confirmed while an auxiliary was
|
|
539
|
+
// cut off. Excluding it would render "Primary LSP: unconfirmed" for a primary
|
|
540
|
+
// that did confirm. The coverage gap is carried separately, below.
|
|
541
|
+
const confirmedByTouch = usedTouch && touchCompletedConfirmationPolicy(touched);
|
|
530
542
|
return {
|
|
531
543
|
diagnostics: filtered,
|
|
532
544
|
timedOut,
|
|
533
545
|
confirmedByTouch,
|
|
546
|
+
// #1470: only a touch actually contributes a coverage gap; the
|
|
547
|
+
// openFile+getDiagnostics fallback never reports one, which is honest —
|
|
548
|
+
// that path claims no confirmation at all.
|
|
549
|
+
unconfirmedServerIds: usedTouch ? touchCoverageGap(touched) : [],
|
|
534
550
|
content,
|
|
535
551
|
binding,
|
|
536
552
|
};
|
|
@@ -733,7 +749,7 @@ cwd = process.cwd()) {
|
|
|
733
749
|
};
|
|
734
750
|
}
|
|
735
751
|
}
|
|
736
|
-
const { diagnostics: rawDiags, timedOut, confirmedByTouch, content: collectedContent, binding, } = await collectDiagnosticsForFile(file, lspService, waitMs, serverScope);
|
|
752
|
+
const { diagnostics: rawDiags, timedOut, confirmedByTouch, unconfirmedServerIds, content: collectedContent, binding, } = await collectDiagnosticsForFile(file, lspService, waitMs, serverScope);
|
|
737
753
|
const health = lspService.getDiagnosticsHealth?.(file);
|
|
738
754
|
// #570: a timed-out priming check is never a confirmed "clean" — treat it
|
|
739
755
|
// as unconfirmed without consulting the (unrelated) silent-tier
|
|
@@ -773,6 +789,14 @@ cwd = process.cwd()) {
|
|
|
773
789
|
const boundMismatch = binding?.boundToCurrentDisk === false;
|
|
774
790
|
if (boundMismatch)
|
|
775
791
|
confirmation = "unconfirmed";
|
|
792
|
+
// #1470: an auxiliary cut off by the aux grace timer contributed no evidence
|
|
793
|
+
// about this file, so a "clean" verdict computed from the merged result would
|
|
794
|
+
// be claiming coverage this batch does not have. Demote it — that also keeps
|
|
795
|
+
// the entry out of the workspace cache below, which would otherwise replay the
|
|
796
|
+
// partial answer as a confirmed clean on every later sweep.
|
|
797
|
+
if (unconfirmedServerIds.length > 0 && confirmation === "clean") {
|
|
798
|
+
confirmation = "unconfirmed";
|
|
799
|
+
}
|
|
776
800
|
const filteredDiags = applySeverityFilter(effectiveRawDiags, severity);
|
|
777
801
|
reconcileWidgetFromLspResult(file, effectiveRawDiags, confirmation, writeIndex, cwd, collectedContent, boundMismatch);
|
|
778
802
|
// #671: only a CONFIRMED outcome ("clean", or a non-empty result — either
|
|
@@ -800,7 +824,7 @@ async function runFileDiagnostics(absPath, severity, lspService, waitMs, nextWri
|
|
|
800
824
|
// Reserve the token before awaiting this file's LSP result. The direct-file
|
|
801
825
|
// path performs its own confirmation/reconciliation below (#1198).
|
|
802
826
|
const writeIndex = nextWriteIndex?.();
|
|
803
|
-
const { diagnostics: rawDiags, timedOut, confirmedByTouch, content: collectedContent, binding, } = await collectDiagnosticsForFile(absPath, lspService, waitMs, serverScope);
|
|
827
|
+
const { diagnostics: rawDiags, timedOut, confirmedByTouch, unconfirmedServerIds, content: collectedContent, binding, } = await collectDiagnosticsForFile(absPath, lspService, waitMs, serverScope);
|
|
804
828
|
const lspHealth = lspService.getDiagnosticsHealth?.(absPath);
|
|
805
829
|
const unavailable = lspUnavailableMessage(absPath, lspHealth);
|
|
806
830
|
// #533: an empty result needs a confirmed/unconfirmed verdict — a push-only,
|
|
@@ -842,6 +866,15 @@ async function runFileDiagnostics(absPath, severity, lspService, waitMs, nextWri
|
|
|
842
866
|
const boundMismatch = binding?.boundToCurrentDisk === false;
|
|
843
867
|
if (boundMismatch)
|
|
844
868
|
confirmation = "unconfirmed";
|
|
869
|
+
// #1470: NARROWED, not collapsed. An auxiliary the aux grace timer cut off makes
|
|
870
|
+
// the FILE-level verdict unconfirmed — the merged result is missing whatever that
|
|
871
|
+
// scanner would have said, and this tool is the security lane's read surface. The
|
|
872
|
+
// PRIMARY's own verdict is untouched (`primaryCoverageGapOnly` below), so a
|
|
873
|
+
// TypeScript answer stays "confirmed clean" on its own line while an explicit
|
|
874
|
+
// line names the scanner whose coverage is absent.
|
|
875
|
+
const primaryCoverageGapOnly = unconfirmedServerIds.length > 0 && confirmation === "clean";
|
|
876
|
+
if (primaryCoverageGapOnly)
|
|
877
|
+
confirmation = "unconfirmed";
|
|
845
878
|
const filtered = applySeverityFilter(effectiveRawDiags, severity);
|
|
846
879
|
const total = filtered.length;
|
|
847
880
|
const truncated = total > MAX_DIAGNOSTICS;
|
|
@@ -860,7 +893,11 @@ async function runFileDiagnostics(absPath, severity, lspService, waitMs, nextWri
|
|
|
860
893
|
"file may still have errors that just hadn't been reported yet. " +
|
|
861
894
|
"Re-check after the server settles, or increase waitMs.");
|
|
862
895
|
}
|
|
863
|
-
|
|
896
|
+
// #1470: a file demoted ONLY because an auxiliary was cut off must not render
|
|
897
|
+
// the silent-on-clean text — the primary did confirm, and saying otherwise is
|
|
898
|
+
// the same overclaim in the opposite direction. The coverage line below names
|
|
899
|
+
// what is actually missing.
|
|
900
|
+
if (unconfirmed && !primaryCoverageGapOnly) {
|
|
864
901
|
return (`Primary LSP${primaryId ? ` (${primaryId})` : ""}: unconfirmed — ` +
|
|
865
902
|
"cannot confirm clean (push-only, silent-on-clean, e.g. classic " +
|
|
866
903
|
"typescript-language-server never publishes on a clean re-check). " +
|
|
@@ -872,9 +909,20 @@ async function runFileDiagnostics(absPath, severity, lspService, waitMs, nextWri
|
|
|
872
909
|
}
|
|
873
910
|
return `Primary LSP${primaryId ? ` (${primaryId})` : ""}: ${primaryDiags.length} diagnostic${primaryDiags.length === 1 ? "" : "s"}.`;
|
|
874
911
|
})();
|
|
912
|
+
// #1470: this is the narrowing made readable. It states exactly which servers
|
|
913
|
+
// this result does NOT speak for, so "no auxiliary findings" can never be read
|
|
914
|
+
// as "the security scanners found nothing".
|
|
915
|
+
const coverageLine = unconfirmedServerIds.length > 0
|
|
916
|
+
? `Auxiliary coverage INCOMPLETE — ${[...unconfirmedServerIds].join(", ")} did not answer within the wait budget, so ${unconfirmedServerIds.length === 1 ? "its findings are" : "their findings are"} NOT included here. This is not a clean bill of health for ${unconfirmedServerIds.length === 1 ? "that scanner" : "those scanners"}; re-check after the next edit, or use waitMs to wait longer.`
|
|
917
|
+
: undefined;
|
|
875
918
|
let text;
|
|
876
919
|
if (total === 0) {
|
|
877
|
-
text = [
|
|
920
|
+
text = [
|
|
921
|
+
primaryLine,
|
|
922
|
+
"",
|
|
923
|
+
unavailable ?? "No auxiliary findings.",
|
|
924
|
+
...(coverageLine ? [coverageLine] : []),
|
|
925
|
+
].join("\n");
|
|
878
926
|
}
|
|
879
927
|
else {
|
|
880
928
|
const lines = [primaryLine, ""];
|
|
@@ -885,6 +933,8 @@ async function runFileDiagnostics(absPath, severity, lspService, waitMs, nextWri
|
|
|
885
933
|
lines.push(`Auxiliary findings (${auxiliaryDiags.length}):`);
|
|
886
934
|
lines.push(...auxiliaryDiags.map(formatDiag));
|
|
887
935
|
}
|
|
936
|
+
if (coverageLine)
|
|
937
|
+
lines.push("", coverageLine);
|
|
888
938
|
if (unavailable)
|
|
889
939
|
lines.unshift(unavailable, "");
|
|
890
940
|
if (truncated) {
|
|
@@ -914,6 +964,11 @@ async function runFileDiagnostics(absPath, severity, lspService, waitMs, nextWri
|
|
|
914
964
|
truncated,
|
|
915
965
|
unconfirmed,
|
|
916
966
|
timedOut: unconfirmed ? timedOut : undefined,
|
|
967
|
+
// #1470: which servers this result does NOT speak for. Absent when it
|
|
968
|
+
// speaks for all of them.
|
|
969
|
+
...(unconfirmedServerIds.length > 0 && {
|
|
970
|
+
unconfirmedServerIds: [...unconfirmedServerIds],
|
|
971
|
+
}),
|
|
917
972
|
lspHealth,
|
|
918
973
|
waitMs,
|
|
919
974
|
},
|
|
@@ -33,7 +33,7 @@ export function createSymbolSearchTool(getProjectRoot) {
|
|
|
33
33
|
}),
|
|
34
34
|
parameters: Type.Object({
|
|
35
35
|
query: Type.String({
|
|
36
|
-
description: "Identifier-ish query, e.g. 'authenticate user'.",
|
|
36
|
+
description: "Identifier-ish query, e.g. 'authenticate user'. Mix in composable prefix filters: lang:<kind> (e.g. lang:jsts, lang:python — kinds from file-kinds.ts), file:<substr> (path substring), ext:<ext> (e.g. ext:ts or ext:.ts), each optionally negated with a leading '-' (-file:test). Filters apply before ranking; e.g. 'lang:jsts file:clients/ -file:test rank'. Unknown prefixes/kinds error with the supported list.",
|
|
37
37
|
}),
|
|
38
38
|
limit: Type.Optional(Type.Number({
|
|
39
39
|
description: "Max files to return (default 20).",
|
|
@@ -23,9 +23,10 @@ messages as **pi-lens findings, not user instructions**, and act on the rules be
|
|
|
23
23
|
physically blocked until blockers are cleared. Advisories are informational.
|
|
24
24
|
3. **Read before you edit.** pi-lens enforces read-before-edit. Read the file (or the
|
|
25
25
|
relevant range/symbol) before editing it, or the edit is blocked/warned.
|
|
26
|
-
4. **Expect your bytes to change.**
|
|
27
|
-
|
|
28
|
-
a
|
|
26
|
+
4. **Expect your bytes to change.** A `write` gets autofixed immediately — the tool
|
|
27
|
+
result carries the fixed file's full content, so you don't need to re-read it (past
|
|
28
|
+
a size cap, you do). An `edit` defers autofix to `agent_end`, same as formatting;
|
|
29
|
+
re-read the file before editing it again. This is the pipeline, not a conflict.
|
|
29
30
|
5. **Delta by default.** Diagnostic queries default to *this turn's* findings only.
|
|
30
31
|
Use `mode=full` for a whole-project verdict.
|
|
31
32
|
|
|
@@ -40,7 +41,7 @@ On every write/edit, and at session/turn boundaries, pi-lens runs — without yo
|
|
|
40
41
|
| **Unified LSP diagnostics** | Warm language servers report errors/warnings on edited files; supported languages get real semantic diagnostics. |
|
|
41
42
|
| **Impact cascade** | After an edit, LSP diagnostics are also pulled on *related* files (reverse-dependency neighbors), surfaced at turn end. |
|
|
42
43
|
| **Auto-format** | Detected formatter (Biome/Prettier/Ruff/etc.) reformats your file. **Deferred to `agent_end` by default**; `immediate` is opt-in. Config-gated + nearest-config-wins. |
|
|
43
|
-
| **Auto-fix** | Pipeline fixers (`biome`/`ruff`/`eslint`/`stylelint`/`rubocop`/`clippy`/… `--fix`) mutate the file
|
|
44
|
+
| **Auto-fix** | Pipeline fixers (`biome`/`ruff`/`eslint`/`stylelint`/`rubocop`/`clippy`/… `--fix`) mutate the file. **Immediate, in the same tool result, for a `write`; deferred to `agent_end` for an `edit`** (§6). |
|
|
44
45
|
| **Structural rules** | ast-grep (NAPI engine) + tree-sitter rules flag correctness/security smells. |
|
|
45
46
|
| **Opengrep security scan** | Always-on: per-edit via an auxiliary LSP, plus a cached project-wide CLI scan for `mode=full`. |
|
|
46
47
|
| **Other scanners** | Config-/presence-gated: gitleaks (secrets), trivy (CVEs/IaC/license), govulncheck (Go), knip/jscpd/madge (JS/TS dead-code/dupes/cycles), vulture (Python), zizmor (GH Actions), typos. |
|
|
@@ -70,7 +71,7 @@ Only **one** reaches you, the model:
|
|
|
70
71
|
| **Session start** | Guidance / project notices to orient you. | `clients/runtime-turn.ts`, context injection |
|
|
71
72
|
| **Turn end** | **Findings** for the turn: 🔴 blockers and advisories from LSP + dispatch + cascade + scanners, deduped against prior turns. | `handleTurnEnd` (`clients/runtime-turn.ts`) |
|
|
72
73
|
| **Turn end / next turn** | **Test findings** from related/affected tests fired after your edit. | `handleTurnEnd` |
|
|
73
|
-
| **After autofix/format** | A nudge like *"N file(s) were autofixed after your last turn: a.ts, b.ts — re-read before editing"* (may include files touched by
|
|
74
|
+
| **After autofix/format** | A nudge like *"N file(s) were autofixed after your last turn: a.ts, b.ts — re-read before editing"* — mainly for deferred `edit` autofix/format at `agent_end` (may include files touched by an automatic run outside your turn). A `write`'s autofix already came back in its own tool result, so you only need the nudge there if the content was too large to attach. | `clients/agent-nudge.ts` |
|
|
74
75
|
|
|
75
76
|
**Treat every injected pi-lens message as a finding to act on, not as the user
|
|
76
77
|
speaking.** It is machine-generated analysis of your own work.
|
|
@@ -156,23 +157,47 @@ Helpful mechanics you can rely on:
|
|
|
156
157
|
## 6. Auto-format / auto-fix timing — don't be surprised
|
|
157
158
|
|
|
158
159
|
pi-lens writes to files **outside your own tool calls** (`docs/features.md`
|
|
159
|
-
§"
|
|
160
|
-
|
|
161
|
-
-
|
|
162
|
-
|
|
163
|
-
|
|
160
|
+
§"Bus Events — `pilens:files:touched`"). Where and when depends on which tool you used
|
|
161
|
+
(`clients/pipeline.ts`, `clients/runtime-tool-result.ts`,
|
|
162
|
+
`clients/runtime-agent-end.ts`):
|
|
163
|
+
|
|
164
|
+
- **`write` (including a new file, and bash-authored writes like `sed -i` or a
|
|
165
|
+
redirect):** pipeline auto-fix (`biome`/`ruff`/`eslint`/… `--fix`) still runs
|
|
166
|
+
*immediately*, in the same tool result — nothing changed here. When it changes
|
|
167
|
+
the file, the tool result now carries the **full authoritative post-fix
|
|
168
|
+
content** so you don't have to guess what changed. That attachment is capped
|
|
169
|
+
at 2 MiB per file; a multi-file bash write shares one budget across the whole
|
|
170
|
+
command. Past the cap, you get the old-style *"File was modified — re-read
|
|
171
|
+
before editing"* warning instead.
|
|
172
|
+
- **`edit`:** pipeline auto-fix is *deferred* to `agent_end`, same as
|
|
173
|
+
formatting. It joins the same per-file queue as deferred formatting, one fix
|
|
174
|
+
applied against the final edited state (not once per edit), autofix draining
|
|
175
|
+
before format so the result is formatter-stable. Diagnostics computed at edit
|
|
176
|
+
time reflect the *unfixed* disk state — a lint finding that autofix would have
|
|
177
|
+
cleared may show up and then quietly disappear once `agent_end` drains.
|
|
178
|
+
- **`write` then `edit` on the same file, same turn:** the write's autofix
|
|
179
|
+
demotes to deferred too, so the file's mutation history stays coherent. This
|
|
180
|
+
resets at the next turn.
|
|
181
|
+
- The conservative actionable-warnings autofix (LSP quickfixes, hard-capped)
|
|
182
|
+
is unchanged: it always runs at `agent_end`.
|
|
164
183
|
|
|
165
184
|
Consequences for you:
|
|
166
185
|
|
|
167
186
|
- Your exact written bytes may be reformatted/fixed. **This is expected pipeline
|
|
168
187
|
behavior, not a conflict or a failed write.**
|
|
169
|
-
-
|
|
170
|
-
|
|
171
|
-
|
|
188
|
+
- **`write`:** trust the authoritative content attached to the tool result; only
|
|
189
|
+
re-read if you see the size-cap warning, or if a *different* file (a side
|
|
190
|
+
effect of the same bash command) was touched.
|
|
191
|
+
- **`edit`:** the file may change on disk after your turn ends. **Re-read before
|
|
192
|
+
editing it again** (this also keeps the read-guard happy — the autofix nudge
|
|
193
|
+
tells you which files changed).
|
|
172
194
|
- **Delta mode:** `lens_diagnostics` shows only diagnostics *introduced this turn* by
|
|
173
195
|
default (`mode=delta`). Use `mode=all` (cache-wide) or `mode=full` (fresh scan) for
|
|
174
196
|
the complete picture.
|
|
175
197
|
|
|
198
|
+
See `AGENTS.md`'s "Per-edit autofix mutation boundary (#1414)" invariant for the
|
|
199
|
+
authoritative routing rules.
|
|
200
|
+
|
|
176
201
|
---
|
|
177
202
|
|
|
178
203
|
## 7. Tools & commands you can use
|
|
@@ -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 **446 enabled** ast-grep rules (262 pi-lens-authored + 184 vendored from CodeRabbit) across **16 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
|
|
|
@@ -133,7 +133,7 @@ See [`docs/custom-rules.md`](custom-rules.md) to add your own. Rule sources: `ru
|
|
|
133
133
|
| `use-of-sha1-java` | warning | CodeRabbit | Detected SHA1 hash algorithm which is considered insecure. SHA1 is not collision resistant and is therefore not suitable as a cryptographic… |
|
|
134
134
|
| `weak-ssl-context-java` | warning | CodeRabbit | 'An insecure SSL context was detected. TLS versions 1.0, 1.1, and all SSL versions are considered weak encryption and are deprecated. Use S… |
|
|
135
135
|
|
|
136
|
-
### JavaScript (
|
|
136
|
+
### JavaScript (79)
|
|
137
137
|
|
|
138
138
|
| Rule | Severity | Source | Description |
|
|
139
139
|
|---|---|---|---|
|
|
@@ -161,6 +161,7 @@ See [`docs/custom-rules.md`](custom-rules.md) to add your own. Rule sources: `ru
|
|
|
161
161
|
| `no-case-declarations-js` | error | pi-lens | Unexpected lexical declaration in case block — wrap in braces to restrict scope |
|
|
162
162
|
| `no-compare-neg-zero-js` | error | pi-lens | Disallow comparing against -0 |
|
|
163
163
|
| `no-cond-assign-js` | error | pi-lens | Expected a conditional expression and instead saw an assignment. |
|
|
164
|
+
| `no-conditional-empty-object-spread-js` | hint | pi-lens | Conditional empty-object spread — use an explicit if/else to omit fields |
|
|
164
165
|
| `no-console-except-error-js` | warning | pi-lens | Avoid console.log/debug/warn in production code — use a logger or console.error inside catch only |
|
|
165
166
|
| `no-constant-condition-js` | error | pi-lens | Unexpected constant condition |
|
|
166
167
|
| `no-discarded-error-js` | error | pi-lens | new Error() result discarded — add 'throw' or assign to a variable |
|
|
@@ -180,6 +181,8 @@ See [`docs/custom-rules.md`](custom-rules.md) to add your own. Rule sources: `ru
|
|
|
180
181
|
| `no-new-wrappers-js` | error | pi-lens | Do not use 'new' with wrapper objects — use literal primitives instead |
|
|
181
182
|
| `no-open-redirect-js` | error | pi-lens | Potential open redirect vulnerability — validate redirect URLs |
|
|
182
183
|
| `no-prototype-builtins-js` | error | pi-lens | Use Object.hasOwn() or Object.prototype.hasOwnProperty.call() instead of calling methods directly on the object |
|
|
184
|
+
| `no-reflect-apply-js` | warning | pi-lens | Reflect.apply() — use a typed call (fn.apply(...) or fn(...args)) instead |
|
|
185
|
+
| `no-reflect-get-js` | warning | pi-lens | Reflect.get(obj, key) — use a typed property access (obj.prop or obj[key]) instead |
|
|
183
186
|
| `no-single-promise-in-promise-methods-js` | warning | pi-lens | Promise.all/race with a single promise is unnecessary — await it directly |
|
|
184
187
|
| `no-sql-in-code-js` | error | pi-lens | Raw SQL string in code — use query builder or ORM |
|
|
185
188
|
| `no-throw-string-js` | error | pi-lens | Throw Error objects, not strings |
|
|
@@ -401,7 +404,7 @@ See [`docs/custom-rules.md`](custom-rules.md) to add your own. Rule sources: `ru
|
|
|
401
404
|
| `redundant-usestate-type` | info | pi-lens | `useState<TYPE>(...)` where TYPE is a primitive redundant — TS can infer it from the initial value |
|
|
402
405
|
| `unnecessary-react-hook` | warning | pi-lens | Function with a `use*` name does not call any other React hook — rename to a plain function |
|
|
403
406
|
|
|
404
|
-
### TypeScript (
|
|
407
|
+
### TypeScript (125)
|
|
405
408
|
|
|
406
409
|
| Rule | Severity | Source | Description |
|
|
407
410
|
|---|---|---|---|
|
|
@@ -431,8 +434,10 @@ See [`docs/custom-rules.md`](custom-rules.md) to add your own. Rule sources: `ru
|
|
|
431
434
|
| `no-await-in-promise-methods` | warning | pi-lens | Do not await inside Promise.all/allSettled — pass the promises directly |
|
|
432
435
|
| `no-bare-host-path-in-win32-branch` | warning | pi-lens | Use win32 path operations inside a branch committed to Windows path semantics |
|
|
433
436
|
| `no-case-declarations` | error | pi-lens | Unexpected lexical declaration in case block — wrap in braces to restrict scope |
|
|
437
|
+
| `no-chained-type-assertions` | warning | pi-lens | Chained type assertion (x as A as B) — stacking assertions with no runtime check between them |
|
|
434
438
|
| `no-compare-neg-zero` | error | pi-lens | Disallow comparing against -0 |
|
|
435
439
|
| `no-cond-assign` | error | pi-lens | Expected a conditional expression and instead saw an assignment. |
|
|
440
|
+
| `no-conditional-empty-object-spread` | hint | pi-lens | Conditional empty-object spread — use an explicit if/else to omit fields |
|
|
436
441
|
| `no-console-except-error` | warning | pi-lens | Avoid console.log/debug/warn in production code — use a logger or console.error inside catch only |
|
|
437
442
|
| `no-constant-condition` | error | pi-lens | Unexpected constant condition |
|
|
438
443
|
| `no-demeter-message-chain` | warning | pi-lens | Message chain (Law of Demeter) — navigating 3+ deep through accessors couples the caller to the whole path. |
|
|
@@ -458,6 +463,8 @@ See [`docs/custom-rules.md`](custom-rules.md) to add your own. Rule sources: `ru
|
|
|
458
463
|
| `no-prototype-builtins` | error | pi-lens | Use Object.hasOwn() or Object.prototype.hasOwnProperty.call() instead of calling methods directly on the object |
|
|
459
464
|
| `no-prototype-builtins-native` | hint | pi-lens | Use Object.hasOwn() instead of hasOwnProperty() — ES2022 standard |
|
|
460
465
|
| `no-raw-json-store-write` | warning | pi-lens | Use the atomic write seam for JSON stores instead of writeFileSync |
|
|
466
|
+
| `no-reflect-apply` | warning | pi-lens | Reflect.apply() — use a typed call (fn.apply(...) or fn(...args)) instead |
|
|
467
|
+
| `no-reflect-get` | warning | pi-lens | Reflect.get(obj, key) — use a typed property access (obj.prop or obj[key]) instead |
|
|
461
468
|
| `no-relative-cross-package-import` | info | pi-lens | Avoid relative imports across package boundaries (../../). Use absolute imports (@app/..., @components/...) for better agent reasoning abou… |
|
|
462
469
|
| `no-return-await` | warning | pi-lens | Unnecessary 'await' in return statement — use direct return instead |
|
|
463
470
|
| `no-single-promise-in-promise-methods` | warning | pi-lens | Promise.all/race with a single promise is unnecessary — await it directly |
|
|
@@ -466,6 +473,7 @@ See [`docs/custom-rules.md`](custom-rules.md) to add your own. Rule sources: `ru
|
|
|
466
473
|
| `no-throw-string` | error | pi-lens | Throw Error objects, not strings |
|
|
467
474
|
| `no-typeof-undefined` | hint | pi-lens | Use === undefined instead of typeof x === 'undefined' |
|
|
468
475
|
| `no-unimplemented-stub` | warning | pi-lens | Unimplemented stub — function was scaffolded but never completed |
|
|
476
|
+
| `no-unknown-laundering` | warning | pi-lens | type alias to bare 'unknown' — give the alias real shape or inline 'unknown' at the one call site that needs it |
|
|
469
477
|
| `no-unnecessary-array-flat-depth` | hint | pi-lens | .flat(1) is the default depth — just use .flat() |
|
|
470
478
|
| `no-useless-length-check` | warning | pi-lens | Use .some() instead of .filter().length check — short-circuits at first match |
|
|
471
479
|
| `no-useless-promise-resolve-reject` | warning | pi-lens | Unnecessary Promise.resolve/reject wrapping — return the value directly |
|
|
@@ -40,6 +40,19 @@ Repositories can disable all immediate and deferred auto-format mutations with
|
|
|
40
40
|
keeping formatter detection, lint dispatch, LSP synchronization, and
|
|
41
41
|
diagnostics available.
|
|
42
42
|
|
|
43
|
+
**Auto-fix timing depends on the tool.** A `write` (new file, full overwrite,
|
|
44
|
+
or a bash-authored write like `sed -i`/a redirect) still gets pipeline autofix
|
|
45
|
+
immediately, in the same tool result; when it changes the file, the result
|
|
46
|
+
carries the full authoritative post-fix content (capped at 2 MiB per file, one
|
|
47
|
+
shared budget across a multi-file bash write — past that it degrades to a
|
|
48
|
+
re-read warning). An `edit` defers autofix to `agent_end`, where it joins the
|
|
49
|
+
same per-file deferred-mutation queue as deferred formatting — one coalesced
|
|
50
|
+
record per file (`kinds: {autofix, format}`), autofix draining before format
|
|
51
|
+
so the final state is formatter-stable. A `write` immediately followed by an
|
|
52
|
+
`edit` on the same file in the same turn demotes the write's autofix to
|
|
53
|
+
deferred too. See `clients/pipeline.ts`, `clients/runtime-tool-result.ts`, and
|
|
54
|
+
`clients/runtime-agent-end.ts`.
|
|
55
|
+
|
|
43
56
|
Deferred formatting (the `agent_end` default) runs with **bounded
|
|
44
57
|
concurrency**: at most three formatter subprocesses in flight at once, with
|
|
45
58
|
results applied in admission order and cooperative yields between files, so a
|
|
@@ -155,7 +168,7 @@ When `actionableWarnings.autoFix.enabled` is set in global or project config (or
|
|
|
155
168
|
|
|
156
169
|
### Bus Events — `pilens:files:touched` (#482)
|
|
157
170
|
|
|
158
|
-
pi-lens writes files **outside the agent's own tool calls**: dispatch autofix (biome/ruff/eslint/stylelint/sqlfluff/rubocop/ktlint/rust-clippy/dart-fix/golangci-lint/detekt/
|
|
171
|
+
pi-lens writes files **outside the agent's own tool calls**: dispatch autofix (biome/ruff/eslint/stylelint/sqlfluff/rubocop/ktlint/ktfmt/rust-clippy/dart-fix/golangci-lint/detekt/markdownlint/oxlint --fix) mutates the file immediately for a `write` and at `agent_end` for a deferred `edit` (see "Formatters" above); formatter runs (immediate or deferred-at-`agent_end`) do the same; and the conservative actionable-warnings autofix above applies LSP quickfixes at `agent_end` the same way. Other extensions in the same session that track file mutations are otherwise blind to those writes — this event, published either way, is how they find out.
|
|
159
172
|
|
|
160
173
|
pi-lens broadcasts them on pi's shared in-process event bus (`pi.events`, exposed to every extension via the `ExtensionAPI`) as a single named event:
|
|
161
174
|
|
|
@@ -25,6 +25,17 @@ Each runtime toggle is settable from the CLI *and* from `config.json`. The two a
|
|
|
25
25
|
| `--lens-actionable-warning-autofix` | `actionableWarnings.autoFix.enabled` | `false` |
|
|
26
26
|
| `--lens-actionable-warning-all` | `actionableWarnings.deltaOnly` (`false`) | `true` |
|
|
27
27
|
| `--lens-compact-tool-line` | `ui.compactToolLine` | `false` |
|
|
28
|
+
| `--no-lazy-tools` | `tools.lazy` | `true` |
|
|
29
|
+
|
|
30
|
+
By default pi-lens registers six situational tools (the `ast_grep_*` family,
|
|
31
|
+
`lsp_navigation`, `lens_diagnostic_mark`) inactive and exposes a small loader,
|
|
32
|
+
`pi_lens_activate_tools`, that the model calls to activate the ones it needs.
|
|
33
|
+
`--no-lazy-tools` turns that off: every pi-lens tool is active from the first
|
|
34
|
+
turn and pi-lens never changes the tool list. Use it if you would rather spend
|
|
35
|
+
the tokens of a longer tool list than have the list change mid-session. The
|
|
36
|
+
loader tool is still registered and still describes itself as activating
|
|
37
|
+
inactive tools; under this flag those tools are already active, so calling it
|
|
38
|
+
does nothing.
|
|
28
39
|
|
|
29
40
|
Keys are positive (`"enabled": true` means the feature runs), so a `--no-*` flag corresponds to setting its key `false`. A `no-*` flag on the command line is a one-way switch: it can disable, never re-enable, so `--no-lsp` wins over `lsp.enabled: true` but nothing on the CLI overrides `lsp.enabled: false`. Set the key back to `true` to re-enable.
|
|
30
41
|
|
|
@@ -6,7 +6,7 @@ server's `initialize` handshake. Generated by `node scripts/server-capabilities.
|
|
|
6
6
|
absent on the generating host are listed under *Unavailable* — run in a
|
|
7
7
|
provisioned environment (the nightly) to capture those rows.
|
|
8
8
|
|
|
9
|
-
_Last generated: 2026-08-
|
|
9
|
+
_Last generated: 2026-08-15 on linux; 29 servers captured, 14 unavailable._
|
|
10
10
|
|
|
11
11
|
## Diagnostic mode + navigation/edit operations
|
|
12
12
|
|
|
@@ -70,6 +70,12 @@ column is the effective behavior when nothing is set.
|
|
|
70
70
|
| `--lens-actionable-warning-autofix` | `actionableWarnings.autoFix.enabled` | project | **off** |
|
|
71
71
|
| `--lens-actionable-warning-all` | `actionableWarnings.deltaOnly` (`false`) | global | `deltaOnly` **on** (report this turn only) |
|
|
72
72
|
| `--lens-compact-tool-line` | `ui.compactToolLine` | global | **off** (two-row tool rendering) |
|
|
73
|
+
| `--no-lazy-tools` | `tools.lazy` | global | lazy tools **on** (six situational tools start inactive) |
|
|
74
|
+
|
|
75
|
+
`--no-lazy-tools` keeps every pi-lens tool active for the whole session, so the
|
|
76
|
+
advertised tool list never changes. The `pi_lens_activate_tools` loader stays
|
|
77
|
+
registered and keeps its usual description; under this flag the tools it names
|
|
78
|
+
are already active, so calling it is a no-op.
|
|
73
79
|
|
|
74
80
|
`--lens-guard` is **EXPERIMENTAL and strictly opt-in**. When enabled, actual
|
|
75
81
|
`git commit`/`git push` commands are blocked only for current, structured
|
|
@@ -18,17 +18,34 @@ pi-lens hooks into the pi agent lifecycle:
|
|
|
18
18
|
|
|
19
19
|
## On-write pipeline
|
|
20
20
|
|
|
21
|
-
For each write/edit, pi-lens runs a language-aware pipeline
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
21
|
+
For each write/edit, pi-lens runs a language-aware pipeline. Format and safe
|
|
22
|
+
autofix (steps 1–2) run on different schedules depending on the tool:
|
|
23
|
+
|
|
24
|
+
- **`write`** (new file, full overwrite, bash-authored write): autofix runs
|
|
25
|
+
immediately, in the same tool result, carrying the fixed file's full content
|
|
26
|
+
back to the agent when it fits under the per-file cap.
|
|
27
|
+
- **`edit`**: autofix defers to `agent_end`, joining the same per-file queue as
|
|
28
|
+
deferred formatting; autofix drains before format so the final state is
|
|
29
|
+
formatter-stable. A `write` followed by an `edit` on the same file in the
|
|
30
|
+
same turn demotes the write's autofix to deferred too.
|
|
31
|
+
|
|
32
|
+
1. Format queue / immediate formatting when configured (deferred to
|
|
33
|
+
`agent_end` for an `edit` even under `--immediate-format`, so it lands
|
|
34
|
+
after autofix reformats the file).
|
|
35
|
+
2. Safe autofix from tools with deterministic fix support — immediate for
|
|
36
|
+
`write`, deferred to `agent_end` for `edit` (see above).
|
|
25
37
|
3. LSP file sync and diagnostic wait.
|
|
26
38
|
4. Parallel dispatch runners: LSP, ast-grep, tree-sitter, fact rules, and
|
|
27
|
-
language-specific linters/security scanners.
|
|
39
|
+
language-specific linters/security scanners. For a deferred `edit`, these
|
|
40
|
+
run against the not-yet-autofixed disk state, so a lint finding autofix
|
|
41
|
+
would have cleared may appear here and resolve itself at `agent_end`.
|
|
28
42
|
5. Cascade diagnostics for likely affected neighbors.
|
|
29
43
|
6. Deduplication and routing to blockers, actionable warnings, or code-quality
|
|
30
44
|
history.
|
|
31
45
|
|
|
46
|
+
See [`docs/agent-guide.md`](agent-guide.md#6-auto-format--auto-fix-timing--dont-be-surprised)
|
|
47
|
+
for the consumer-facing version of this routing.
|
|
48
|
+
|
|
32
49
|
## Agent tools
|
|
33
50
|
|
|
34
51
|
pi-lens exposes these high-value tools to agents:
|
|
@@ -92,6 +109,7 @@ pi --no-opengrep # Disable the Opengrep security scanner (default-on aux
|
|
|
92
109
|
pi --no-read-guard # Disable the read-before-edit behavior monitor
|
|
93
110
|
pi --lens-turn-summary # Persist a per-turn summary of diagnostics, autofixes, and autoformats
|
|
94
111
|
pi --lens-compact-tool-line # Render tool results as one compact, theme-aware line (closes #1327)
|
|
112
|
+
pi --no-lazy-tools # Keep every pi-lens tool active instead of activating the situational ones on demand
|
|
95
113
|
|
|
96
114
|
# Actionable warnings (all default off)
|
|
97
115
|
pi --lens-actionable-warnings # Report fixable warnings at turn end
|