@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
|
@@ -42,6 +42,41 @@ HTTPServer → httpserver, http, server
|
|
|
42
42
|
each through `splitIdentifier`; queries are tokenized the same way so
|
|
43
43
|
`"authenticate user"` matches `authenticateUser`, `auth_user`, etc.
|
|
44
44
|
|
|
45
|
+
## Query prefix filters (#1450)
|
|
46
|
+
|
|
47
|
+
The query string can mix plain terms with composable `key:value` filters,
|
|
48
|
+
parsed by `parseWordIndexQuery` (`clients/word-index.ts`) before tokenization:
|
|
49
|
+
|
|
50
|
+
- `lang:<kind>` — restrict to a file kind's extensions, drawn from
|
|
51
|
+
`KIND_EXTENSIONS` (`clients/file-kinds.ts`) — the single source of truth
|
|
52
|
+
(#894 invariant), so there is no second, hand-maintained language list here.
|
|
53
|
+
- `file:<substr>` — path substring match against the index's own stored path,
|
|
54
|
+
normalized through `wordIndexKey` (case-folded on win32 only, matching how
|
|
55
|
+
the index's own path-keyed maps already fold win32 paths, #1025).
|
|
56
|
+
- `ext:<ext>` — literal extension match (`ext:ts` and `ext:.ts` are
|
|
57
|
+
equivalent).
|
|
58
|
+
- Any filter may be negated with a leading `-` (`-file:test`).
|
|
59
|
+
|
|
60
|
+
Example: `lang:jsts file:clients/ -file:test rank`. Multiple positive filters
|
|
61
|
+
of the SAME key OR together; different keys AND; negations always subtract.
|
|
62
|
+
Filters are applied as a `fileFilter` predicate BEFORE BM25/priors/centrality
|
|
63
|
+
scoring (same seam as the pre-existing `paths`/`lang` structured options,
|
|
64
|
+
#771), so a surviving file's score is unaffected by filtering. An unrecognized
|
|
65
|
+
`key:` token passes through as an ordinary search term (colon-bearing terms
|
|
66
|
+
like `std::vector`, URLs, and Windows paths search normally). Only a
|
|
67
|
+
recognized key with a bad value — an unrecognized `lang:` kind — throws
|
|
68
|
+
`WordIndexQueryError`
|
|
69
|
+
naming the supported list — never silently falls through as a literal search
|
|
70
|
+
term. Both the pi `symbol_search` tool and the MCP `pilens_symbol_search`
|
|
71
|
+
mirror inherit the syntax for free since both pass their `query` argument
|
|
72
|
+
straight through to `searchWordIndex` (the entry point where parsing happens).
|
|
73
|
+
|
|
74
|
+
DF-normalization: BM25's per-token `idf` is computed from the FULL,
|
|
75
|
+
unfiltered posting for that token before `fileFilter` is applied per
|
|
76
|
+
candidate file — a filtered query reuses the global, corpus-wide document
|
|
77
|
+
frequency rather than recomputing it over the filtered subset. Documented as
|
|
78
|
+
an acceptable approximation (see the doc comment on `searchWordIndex`).
|
|
79
|
+
|
|
45
80
|
## Lifecycle
|
|
46
81
|
|
|
47
82
|
The index is built/refreshed in **every** startup mode: load the persisted
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
id: no-conditional-empty-object-spread-js
|
|
2
|
+
valid:
|
|
3
|
+
- 'const o = {...(cond ? {a: 1} : {b: 2})}'
|
|
4
|
+
- 'const o = {...base}'
|
|
5
|
+
- 'const o = {...(cond ? extra : other)}'
|
|
6
|
+
invalid:
|
|
7
|
+
- 'const o = {...(cond ? {} : {x})}'
|
|
8
|
+
- 'const o = {...(cond ? {y} : {})}'
|
|
9
|
+
- 'const o = {...cond ? {} : {x}}'
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
id: no-conditional-empty-object-spread
|
|
2
|
+
valid:
|
|
3
|
+
- 'const o = {...(cond ? {a: 1} : {b: 2})}'
|
|
4
|
+
- 'const o = {...base}'
|
|
5
|
+
- 'const o = {...(cond ? extra : other)}'
|
|
6
|
+
invalid:
|
|
7
|
+
- 'const o = {...(cond ? {} : {x})}'
|
|
8
|
+
- 'const o = {...(cond ? {y} : {})}'
|
|
9
|
+
- 'const o = {...cond ? {} : {x}}'
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
id: no-unknown-laundering
|
|
2
|
+
valid:
|
|
3
|
+
- 'const x: unknown = foo()'
|
|
4
|
+
- 'function f(x: unknown) {}'
|
|
5
|
+
- 'function isValidPayload(data: unknown): data is Foo { return true; }'
|
|
6
|
+
- 'function f(cause: unknown) {}'
|
|
7
|
+
- 'type X = { a: unknown }'
|
|
8
|
+
- 'type Y = Record<string, unknown>'
|
|
9
|
+
invalid:
|
|
10
|
+
- 'type X = unknown'
|
|
11
|
+
- 'type LooseHandler = unknown'
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
id: no-chained-type-assertions
|
|
2
|
+
language: TypeScript
|
|
3
|
+
severity: warning
|
|
4
|
+
message: "Chained type assertion (x as A as B) — stacking assertions with no runtime check between them"
|
|
5
|
+
note: |
|
|
6
|
+
`x as A as B` stacks two assertions with no runtime check between
|
|
7
|
+
them, so the compiler can't verify either hop. Narrow with a type
|
|
8
|
+
guard, or assert once to the type you actually need. `as const`
|
|
9
|
+
chains are allowed — they don't launder the value's type, they just
|
|
10
|
+
freeze a literal. `as unknown as B` chains are also allowed: routing
|
|
11
|
+
a cast through `unknown` (rather than through an unrelated concrete
|
|
12
|
+
type) is the standard, TypeScript-endorsed way to perform a cast that
|
|
13
|
+
the compiler can't otherwise verify as safe — see no-as-any.yml's own
|
|
14
|
+
note, which recommends this exact form as the safe replacement for
|
|
15
|
+
`as any`. An FP-scan of the pi-lens codebase found 22 chained-assertion
|
|
16
|
+
hits and all 22 were `as unknown as`; none were a genuine two
|
|
17
|
+
concrete-type stack.
|
|
18
|
+
rule:
|
|
19
|
+
pattern: $X as $A as $B
|
|
20
|
+
not:
|
|
21
|
+
pattern: "$X as unknown as $B"
|
package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-conditional-empty-object-spread-js.yml
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
id: no-conditional-empty-object-spread-js
|
|
2
|
+
language: JavaScript
|
|
3
|
+
severity: hint
|
|
4
|
+
message: "Conditional empty-object spread — use an explicit if/else to omit fields"
|
|
5
|
+
note: |
|
|
6
|
+
`{...(cond ? {} : {x})}` hides field omission behind a ternary — an
|
|
7
|
+
explicit `if` reads better. Matched structurally (spread of a ternary
|
|
8
|
+
whose consequence or alternative is an empty object literal), not by
|
|
9
|
+
exact source text, so it also catches the un-parenthesized form.
|
|
10
|
+
|
|
11
|
+
Kept at `hint` to match no-conditional-empty-object-spread.yml — see
|
|
12
|
+
that rule's note for the FP-scan finding (147 hits, all a deliberate
|
|
13
|
+
existing idiom).
|
|
14
|
+
rule:
|
|
15
|
+
kind: spread_element
|
|
16
|
+
has:
|
|
17
|
+
stopBy: end
|
|
18
|
+
kind: ternary_expression
|
|
19
|
+
any:
|
|
20
|
+
- has: { field: consequence, stopBy: neighbor, kind: object, regex: '^\{\s*\}$' }
|
|
21
|
+
- has: { field: alternative, stopBy: neighbor, kind: object, regex: '^\{\s*\}$' }
|
package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-conditional-empty-object-spread.yml
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
id: no-conditional-empty-object-spread
|
|
2
|
+
language: TypeScript
|
|
3
|
+
severity: hint
|
|
4
|
+
message: "Conditional empty-object spread — use an explicit if/else to omit fields"
|
|
5
|
+
note: |
|
|
6
|
+
`{...(cond ? {} : {x})}` hides field omission behind a ternary — an
|
|
7
|
+
explicit `if` reads better and lets the object's shape stay typed.
|
|
8
|
+
Matched structurally (spread of a ternary whose consequence or
|
|
9
|
+
alternative is an empty object literal), not by exact source text, so
|
|
10
|
+
it also catches the un-parenthesized and multi-property-spread forms.
|
|
11
|
+
|
|
12
|
+
FP-scan finding (pi-lens codebase, clients/ tools/ mcp/ index.ts): 147
|
|
13
|
+
hits, and every sampled hit is this exact shape used deliberately to
|
|
14
|
+
build an optional-field object (`...(hit.annotations ? {
|
|
15
|
+
annotations: hit.annotations } : {})`). That is a real, established
|
|
16
|
+
idiom here, not a shape mismatch — so the rule stays at `hint` rather
|
|
17
|
+
than `warning`: still discoverable in a scan, but it does not read as
|
|
18
|
+
a call to fix 147 existing sites.
|
|
19
|
+
rule:
|
|
20
|
+
kind: spread_element
|
|
21
|
+
has:
|
|
22
|
+
# the ternary sits under a parenthesized_expression when the source
|
|
23
|
+
# parenthesizes it, so this needs stopBy: end — spread_element's
|
|
24
|
+
# DIRECT child is not the ternary in that (common) form.
|
|
25
|
+
stopBy: end
|
|
26
|
+
kind: ternary_expression
|
|
27
|
+
any:
|
|
28
|
+
- has: { field: consequence, stopBy: neighbor, kind: object, regex: '^\{\s*\}$' }
|
|
29
|
+
- has: { field: alternative, stopBy: neighbor, kind: object, regex: '^\{\s*\}$' }
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
id: no-reflect-apply-js
|
|
2
|
+
language: JavaScript
|
|
3
|
+
severity: warning
|
|
4
|
+
message: "Reflect.apply() — use a typed call (fn.apply(...) or fn(...args)) instead"
|
|
5
|
+
note: |
|
|
6
|
+
Reflect.apply reaches for reflection where a typed call already works.
|
|
7
|
+
Prefer Function#apply or a direct call with spread arguments.
|
|
8
|
+
rule:
|
|
9
|
+
pattern: Reflect.apply($$$ARGS)
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
id: no-reflect-apply
|
|
2
|
+
language: TypeScript
|
|
3
|
+
severity: warning
|
|
4
|
+
message: "Reflect.apply() — use a typed call (fn.apply(...) or fn(...args)) instead"
|
|
5
|
+
note: |
|
|
6
|
+
Reflect.apply reaches for reflection where a typed call already works.
|
|
7
|
+
Prefer Function#apply or a direct call with spread arguments.
|
|
8
|
+
rule:
|
|
9
|
+
pattern: Reflect.apply($$$ARGS)
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
id: no-reflect-get-js
|
|
2
|
+
language: JavaScript
|
|
3
|
+
severity: warning
|
|
4
|
+
message: "Reflect.get(obj, key) — use a typed property access (obj.prop or obj[key]) instead"
|
|
5
|
+
note: |
|
|
6
|
+
Reflect.get reaches for reflection where a typed property access
|
|
7
|
+
already works. Prefer dot or bracket access.
|
|
8
|
+
|
|
9
|
+
Scoped to the 2-argument form only — see no-reflect-get.yml's note:
|
|
10
|
+
the 3-argument receiver-forwarding form is the standard Proxy `get`
|
|
11
|
+
trap idiom.
|
|
12
|
+
rule:
|
|
13
|
+
pattern: Reflect.get($OBJ, $KEY)
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
id: no-reflect-get
|
|
2
|
+
language: TypeScript
|
|
3
|
+
severity: warning
|
|
4
|
+
message: "Reflect.get(obj, key) — use a typed property access (obj.prop or obj[key]) instead"
|
|
5
|
+
note: |
|
|
6
|
+
Reflect.get reaches for reflection where a typed property access
|
|
7
|
+
already works. Prefer dot or bracket access.
|
|
8
|
+
|
|
9
|
+
Scoped to the 2-argument form only. The 3-argument form
|
|
10
|
+
(`Reflect.get(target, key, receiver)`) is the standard, correct way to
|
|
11
|
+
forward a `receiver` inside a Proxy `get` trap — an FP-scan of the
|
|
12
|
+
pi-lens codebase found 2 hits, both inside a Proxy trap using the
|
|
13
|
+
3-argument receiver-forwarding form, so that arity is excluded rather
|
|
14
|
+
than allowlisted by call site.
|
|
15
|
+
rule:
|
|
16
|
+
pattern: Reflect.get($OBJ, $KEY)
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
id: no-unknown-laundering
|
|
2
|
+
language: TypeScript
|
|
3
|
+
severity: warning
|
|
4
|
+
message: "type alias to bare 'unknown' — give the alias real shape or inline 'unknown' at the one call site that needs it"
|
|
5
|
+
note: |
|
|
6
|
+
`type X = unknown` adds a name but zero type information — every
|
|
7
|
+
consumer of X still has to narrow from scratch, so the alias only
|
|
8
|
+
launders the fact that nothing was typed. Give it real shape, or drop
|
|
9
|
+
the alias and let the (rare) genuine unknown-input site say so
|
|
10
|
+
directly.
|
|
11
|
+
|
|
12
|
+
FP-scan finding (pi-lens codebase, clients/ tools/ mcp/ index.ts):
|
|
13
|
+
the originally proposed wider fan-out — bare `unknown` return types,
|
|
14
|
+
`unknown` parameters, and `Record<string, unknown>` / index-signature
|
|
15
|
+
dictionaries — was dropped from this rule. Each of those arms fired
|
|
16
|
+
overwhelmingly on legitimate, idiomatic TypeScript already in this
|
|
17
|
+
codebase: `unknown` parameters on type-guard/validator functions
|
|
18
|
+
(`errorMessage(err: unknown)`, `asRecord(value: unknown)`), `unknown`
|
|
19
|
+
return types on parsers and a `ProxyHandler.get` trap (whose signature
|
|
20
|
+
TypeScript itself requires), and `Record<string, unknown>` on
|
|
21
|
+
telemetry/metadata bags (`metadata?: Record<string, unknown>`). A
|
|
22
|
+
declared `unknown` at those positions is the CORRECT contract — it
|
|
23
|
+
forces the caller to narrow — so it is not syntactically distinguishable
|
|
24
|
+
from a laundered one; only `type X = unknown` stayed a reliable
|
|
25
|
+
precision signal (zero hits in the same scan).
|
|
26
|
+
rule:
|
|
27
|
+
pattern: "type $T = unknown"
|
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
* ~/.pi-lens/actionable-warnings*.log JSONL advisory pipeline (inject/suppress)
|
|
13
13
|
* ~/.pi-lens/ast-grep-tools*.log JSONL MCP ast-grep search/replace telemetry
|
|
14
14
|
* ~/.pi-lens/logs/*.jsonl JSONL diagnostic findings
|
|
15
|
+
* ~/.pi-lens/projects/<slug>/worklog.jsonl JSONL fix worklog (rule/tool/model/provider, #1448)
|
|
15
16
|
*/
|
|
16
17
|
|
|
17
18
|
import fs from "node:fs";
|
|
@@ -69,6 +70,7 @@ async function main() {
|
|
|
69
70
|
analyzeSessionStart(files.sessionStart, state),
|
|
70
71
|
analyzeActionableWarnings(files.actionableWarnings, state),
|
|
71
72
|
analyzeAstGrepTools(files.astGrepTools, state),
|
|
73
|
+
analyzeWorklog(files.worklog, state),
|
|
72
74
|
]);
|
|
73
75
|
|
|
74
76
|
const report = buildReport(state);
|
|
@@ -139,6 +141,13 @@ function discoverLogFiles(logRoot, archived) {
|
|
|
139
141
|
.filter((name) => name.endsWith(".jsonl"))
|
|
140
142
|
.map((name) => path.join(logsDir, name));
|
|
141
143
|
|
|
144
|
+
// worklog.jsonl lives per-project under projects/<slug>/ (getProjectDataDir),
|
|
145
|
+
// not at the log root — walk one level to find every project's file (#1448).
|
|
146
|
+
const projectsDir = path.join(logRoot, "projects");
|
|
147
|
+
const worklogs = safeReaddir(projectsDir)
|
|
148
|
+
.map((slug) => path.join(projectsDir, slug, "worklog.jsonl"))
|
|
149
|
+
.filter((file) => fs.existsSync(file));
|
|
150
|
+
|
|
142
151
|
const byPrefix = (prefix) =>
|
|
143
152
|
allRootFiles.filter((file) => {
|
|
144
153
|
const base = path.basename(file);
|
|
@@ -156,6 +165,7 @@ function discoverLogFiles(logRoot, archived) {
|
|
|
156
165
|
actionableWarnings: byPrefix("actionable-warnings"),
|
|
157
166
|
astGrepTools: byPrefix("ast-grep-tools"),
|
|
158
167
|
diagnostics: dailyLogs,
|
|
168
|
+
worklog: worklogs,
|
|
159
169
|
};
|
|
160
170
|
}
|
|
161
171
|
|
|
@@ -263,6 +273,14 @@ function createState(files) {
|
|
|
263
273
|
errors: [],
|
|
264
274
|
slow: [],
|
|
265
275
|
},
|
|
276
|
+
worklog: {
|
|
277
|
+
// key: "<rule>||<model>" (model "" when the entry predates #1448 or the
|
|
278
|
+
// runtime didn't know it) -> { total, autoFixed }
|
|
279
|
+
byRuleModel: new Map(),
|
|
280
|
+
byModel: counter(),
|
|
281
|
+
byModelAutoFixed: counter(),
|
|
282
|
+
byProvider: counter(),
|
|
283
|
+
},
|
|
266
284
|
};
|
|
267
285
|
}
|
|
268
286
|
|
|
@@ -737,6 +755,39 @@ async function analyzeAstGrepTools(files, state) {
|
|
|
737
755
|
}
|
|
738
756
|
}
|
|
739
757
|
|
|
758
|
+
/**
|
|
759
|
+
* Per-model rollup (#1448): rule × model counts and auto-fixed vs
|
|
760
|
+
* agent-required rates, from worklog.jsonl's optional `model`/`provider`
|
|
761
|
+
* fields. Entries predating #1448 (or written outside a live agent turn)
|
|
762
|
+
* have neither field — bucketed under the empty-string model/provider key so
|
|
763
|
+
* "unattributed" volume stays visible rather than silently dropped.
|
|
764
|
+
*/
|
|
765
|
+
async function analyzeWorklog(files, state) {
|
|
766
|
+
for (const file of files) {
|
|
767
|
+
await forEachJsonLine(file, "worklog", state, (entry) => {
|
|
768
|
+
const ts = dateOf(entry.timestamp);
|
|
769
|
+
if (!inWindow(ts)) return;
|
|
770
|
+
state.seen.inc("worklog");
|
|
771
|
+
const rule = entry.rule ?? "unknown";
|
|
772
|
+
const model = entry.model ?? "";
|
|
773
|
+
const provider = entry.provider ?? "";
|
|
774
|
+
const key = `${rule}||${model}`;
|
|
775
|
+
const row = state.worklog.byRuleModel.get(key) ?? {
|
|
776
|
+
rule,
|
|
777
|
+
model,
|
|
778
|
+
total: 0,
|
|
779
|
+
autoFixed: 0,
|
|
780
|
+
};
|
|
781
|
+
row.total += 1;
|
|
782
|
+
if (entry.autoFixed) row.autoFixed += 1;
|
|
783
|
+
state.worklog.byRuleModel.set(key, row);
|
|
784
|
+
state.worklog.byModel.inc(model || "(unknown)");
|
|
785
|
+
if (entry.autoFixed) state.worklog.byModelAutoFixed.inc(model || "(unknown)");
|
|
786
|
+
state.worklog.byProvider.inc(provider || "(unknown)");
|
|
787
|
+
});
|
|
788
|
+
}
|
|
789
|
+
}
|
|
790
|
+
|
|
740
791
|
async function forEachJsonLine(file, bucket, state, visitor) {
|
|
741
792
|
await forEachLine(file, async (line) => {
|
|
742
793
|
if (!line.trim()) return;
|
|
@@ -1259,6 +1310,17 @@ function buildReport(state) {
|
|
|
1259
1310
|
errors: state.astGrep.errors.slice(0, limit),
|
|
1260
1311
|
slow: state.astGrep.slow.slice(0, limit),
|
|
1261
1312
|
},
|
|
1313
|
+
worklog: {
|
|
1314
|
+
byModel: state.worklog.byModel.top(limit * 2),
|
|
1315
|
+
byProvider: state.worklog.byProvider.top(limit * 2),
|
|
1316
|
+
byRuleModel: [...state.worklog.byRuleModel.values()]
|
|
1317
|
+
.map((row) => ({
|
|
1318
|
+
...row,
|
|
1319
|
+
autoFixedRate: row.total ? row.autoFixed / row.total : 0,
|
|
1320
|
+
}))
|
|
1321
|
+
.sort((a, b) => b.total - a.total)
|
|
1322
|
+
.slice(0, limit * 3),
|
|
1323
|
+
},
|
|
1262
1324
|
};
|
|
1263
1325
|
}
|
|
1264
1326
|
|
|
@@ -1397,6 +1459,23 @@ function printReport(report) {
|
|
|
1397
1459
|
` started=${ws.started} completed=${ws.completed} incomplete=${ws.incomplete} aborted=${ws.aborted} fileTimeouts=${ws.timedOutFilesTotal} (in ${ws.timedOutSweeps} sweeps)`,
|
|
1398
1460
|
);
|
|
1399
1461
|
}
|
|
1462
|
+
const wl = report.worklog;
|
|
1463
|
+
if (wl && (wl.byModel.length || wl.byRuleModel.length)) {
|
|
1464
|
+
console.log("\nWorklog per-model rollup (#1448)");
|
|
1465
|
+
console.log(
|
|
1466
|
+
` by model: ${wl.byModel.map((x) => `${x.key}=${x.count}`).join(", ")}`,
|
|
1467
|
+
);
|
|
1468
|
+
if (wl.byProvider.length)
|
|
1469
|
+
console.log(
|
|
1470
|
+
` by provider: ${wl.byProvider.map((x) => `${x.key}=${x.count}`).join(", ")}`,
|
|
1471
|
+
);
|
|
1472
|
+
console.log(" rule × model (auto-fixed vs agent-required):");
|
|
1473
|
+
for (const row of wl.byRuleModel.slice(0, limit))
|
|
1474
|
+
console.log(
|
|
1475
|
+
` ${String(row.total).padStart(5)} ${row.rule} [${row.model || "(unknown)"}] autoFixed=${row.autoFixed} (${(row.autoFixedRate * 100).toFixed(0)}%)`,
|
|
1476
|
+
);
|
|
1477
|
+
}
|
|
1478
|
+
|
|
1400
1479
|
const timeouts = Object.entries(report.latency.phaseTimeouts ?? {});
|
|
1401
1480
|
if (timeouts.length) {
|
|
1402
1481
|
console.log("\nPhase timeouts");
|
|
@@ -52,12 +52,15 @@ Use `$$$` when you don't need the captured value; `$$$NAME` when you do.
|
|
|
52
52
|
|
|
53
53
|
Use these instead of writing raw YAML:
|
|
54
54
|
|
|
55
|
-
| Parameter | What it does |
|
|
56
|
-
|
|
57
|
-
| `insideKind` | Only match inside an ancestor of this node kind |
|
|
58
|
-
| `hasKind` | Only match nodes
|
|
59
|
-
| `
|
|
60
|
-
| `
|
|
55
|
+
| Parameter | Tool | What it does |
|
|
56
|
+
|---|---|---|
|
|
57
|
+
| `insideKind` | both | Only match inside an ancestor of this node kind (searches ALL ancestors, `stopBy: end`) |
|
|
58
|
+
| `hasKind` | both | Only match nodes whose **immediate child** has this kind (`stopBy: neighbor` — NOT recursive) |
|
|
59
|
+
| `hasDescendantKind` | both | Only match nodes containing this kind **anywhere in their descendants** (`stopBy: end`) — use this instead of `hasKind` when the target isn't a direct child |
|
|
60
|
+
| `follows` | both | Only match nodes preceded by a sibling matching this pattern |
|
|
61
|
+
| `precedes` | both | Only match nodes followed by a sibling matching this pattern |
|
|
62
|
+
|
|
63
|
+
`hasKind` and `hasDescendantKind` are mutually exclusive on both tools — combining them errors.
|
|
61
64
|
|
|
62
65
|
```
|
|
63
66
|
# console.log only inside functions
|
|
@@ -152,13 +155,9 @@ Use these as starting points, then scope `paths` tightly.
|
|
|
152
155
|
|
|
153
156
|
| Task | Pattern / params |
|
|
154
157
|
|---|---|
|
|
155
|
-
| Find pi lifecycle handlers | `pattern: pi.on($EVENT, $HANDLER)` |
|
|
156
|
-
| Find timers | `pattern: setTimeout($CALLBACK, $$$REST)` or `setInterval($CALLBACK, $$$REST)` |
|
|
157
|
-
| Find immediate/deferred work | `pattern: setImmediate($CALLBACK)` |
|
|
158
|
-
| Find promise callbacks | `pattern: $PROMISE.then($CALLBACK)` / `.catch($CALLBACK)` / `.finally($CALLBACK)` |
|
|
159
158
|
| Find object-literal function dependency by name | `pattern: { resetLSPService: $FN, $$$REST }` |
|
|
160
|
-
| Find fire-and-forget async calls | `pattern: void $CALL` |
|
|
161
159
|
| Find empty catches | `pattern: try { $$$BODY } catch ($ERR) { }` |
|
|
160
|
+
| Find fire-and-forget async calls | `pattern: void $CALL` |
|
|
162
161
|
|
|
163
162
|
For lifecycle bugs, search first, then use the returned `details.matchLocations[].readSlice` handle for bounded context.
|
|
164
163
|
|
|
@@ -13,39 +13,39 @@ Use `lsp_diagnostics` before builds/tests or after touching several files:
|
|
|
13
13
|
|
|
14
14
|
| Need | Tool call |
|
|
15
15
|
|---|---|
|
|
16
|
-
| Check one file | `lsp_diagnostics({
|
|
17
|
-
| Check a folder | `lsp_diagnostics({
|
|
18
|
-
| Check exact touched files | `lsp_diagnostics({
|
|
19
|
-
| Slow server (Rust, Java) | `lsp_diagnostics({
|
|
20
|
-
| Include warnings | `lsp_diagnostics({
|
|
16
|
+
| Check one file | `lsp_diagnostics({ path: "src/file.ts" })` |
|
|
17
|
+
| Check a folder | `lsp_diagnostics({ path: "src/", severity: "error" })` |
|
|
18
|
+
| Check exact touched files | `lsp_diagnostics({ paths: ["src/a.ts", "src/b.ts"], concurrency: 8 })` |
|
|
19
|
+
| Slow server (Rust, Java) | `lsp_diagnostics({ paths: files, waitMs: 2000 })` |
|
|
20
|
+
| Include warnings | `lsp_diagnostics({ paths: files, severity: "all" })` |
|
|
21
21
|
|
|
22
|
-
Prefer explicit `
|
|
22
|
+
Prefer explicit `paths` batches after multi-file edits — bounded concurrency, no unrelated directory noise.
|
|
23
23
|
|
|
24
24
|
## Navigation (Code Intelligence)
|
|
25
25
|
|
|
26
26
|
| Question | Operation | Parameters |
|
|
27
27
|
|---|---|---|
|
|
28
|
-
| Where is this defined? | `definition` |
|
|
29
|
-
| Where is this symbol's *type* defined? | `typeDefinition` |
|
|
30
|
-
| Where is this declared (vs defined)? | `declaration` |
|
|
31
|
-
| Find all usages | `references` |
|
|
32
|
-
| What type is this? | `hover` |
|
|
33
|
-
| Call signature | `signatureHelp` |
|
|
34
|
-
| Symbols in this file | `documentSymbol` |
|
|
35
|
-
| Find symbol across project | `workspaceSymbol` | query +
|
|
36
|
-
| Quick fixes available | `codeAction` |
|
|
37
|
-
| Rename symbol safely | `rename` |
|
|
38
|
-
| Who implements this? | `implementation` |
|
|
39
|
-
| Who calls this function? | `prepareCallHierarchy` → `incomingCalls` |
|
|
40
|
-
| What does this call? | `prepareCallHierarchy` → `outgoingCalls` |
|
|
41
|
-
| What commands does the server offer? | `capabilities` | (optional
|
|
28
|
+
| Where is this defined? | `definition` | path, line, character |
|
|
29
|
+
| Where is this symbol's *type* defined? | `typeDefinition` | path, line, character |
|
|
30
|
+
| Where is this declared (vs defined)? | `declaration` | path, line, character |
|
|
31
|
+
| Find all usages | `references` | path, line, character |
|
|
32
|
+
| What type is this? | `hover` | path, line, character |
|
|
33
|
+
| Call signature | `signatureHelp` | path, line, character (at arg position) |
|
|
34
|
+
| Symbols in this file | `documentSymbol` | path |
|
|
35
|
+
| Find symbol across project | `workspaceSymbol` | query + path (strongly recommended) |
|
|
36
|
+
| Quick fixes available | `codeAction` | path, line, character, endLine, endCharacter |
|
|
37
|
+
| Rename symbol safely | `rename` | path, line, character, newName |
|
|
38
|
+
| Who implements this? | `implementation` | path, line, character |
|
|
39
|
+
| Who calls this function? | `prepareCallHierarchy` → `incomingCalls` | path, line, character |
|
|
40
|
+
| What does this call? | `prepareCallHierarchy` → `outgoingCalls` | path, line, character |
|
|
41
|
+
| What commands does the server offer? | `capabilities` | (optional path) — lists advertised commands |
|
|
42
42
|
| Run a server command (e.g. organize imports) | `executeCommand` | command (+ commandArguments); dry-run unless `apply:true` |
|
|
43
43
|
|
|
44
44
|
## Call Hierarchy Pattern
|
|
45
45
|
|
|
46
46
|
```
|
|
47
47
|
// Step 1
|
|
48
|
-
lsp_navigation(operation="prepareCallHierarchy",
|
|
48
|
+
lsp_navigation(operation="prepareCallHierarchy", path="src/api.ts", line=42, character=10)
|
|
49
49
|
// → returns callHierarchyItem
|
|
50
50
|
|
|
51
51
|
// Step 2
|
|
@@ -56,7 +56,7 @@ lsp_navigation(operation="outgoingCalls", callHierarchyItem=<item from step 1>)
|
|
|
56
56
|
## Operational Notes
|
|
57
57
|
|
|
58
58
|
- **`definition` returns nothing?** The file may not be open/indexed yet. Read it first, then retry.
|
|
59
|
-
- **`workspaceSymbol` empty?** Always pass `
|
|
59
|
+
- **`workspaceSymbol` empty?** Always pass `path`. Unscoped queries are best-effort and frequently return nothing. If TypeScript returns "No Project", open the scoped file first.
|
|
60
60
|
- **`references`** — query from the *definition site* for full cross-file coverage; usage-site queries can be partial.
|
|
61
61
|
- **`signatureHelp`** — only valid at call-site argument positions; declaration positions return empty.
|
|
62
62
|
- **`workspaceDiagnostics`** — tracked push snapshot only, not an active check. Use `lsp_diagnostics` when you need fresh results.
|