@herbertgao/pi-extensions 2026.9.2 → 2026.9.4
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 +4 -2
- package/THIRD_PARTY_NOTICES.md +49 -0
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/compact-mode.ts +3 -2
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/default-mode.ts +16 -11
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/diff/diff-renderer.ts +20 -6
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/grouping.ts +12 -7
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/result.ts +98 -0
- package/node_modules/@herbertgao/pi-cc-extensions/package.json +3 -3
- package/node_modules/@herbertgao/sol-pi/LICENSE +19 -0
- package/node_modules/@herbertgao/sol-pi/README.md +159 -0
- package/node_modules/@herbertgao/sol-pi/SECURITY.md +26 -0
- package/node_modules/@herbertgao/sol-pi/THIRD_PARTY_NOTICES.md +19 -0
- package/node_modules/@herbertgao/sol-pi/agents-install.md +150 -0
- package/node_modules/@herbertgao/sol-pi/assets/sol-pi-hero.png +0 -0
- package/node_modules/@herbertgao/sol-pi/docs/compatibility.md +69 -0
- package/node_modules/@herbertgao/sol-pi/docs/configuration.md +75 -0
- package/node_modules/@herbertgao/sol-pi/package.json +76 -0
- package/node_modules/@herbertgao/sol-pi/scripts/check-pi-compat.mjs +32 -0
- package/node_modules/@herbertgao/sol-pi/scripts/check-sol-pi-config.mjs +120 -0
- package/node_modules/@herbertgao/sol-pi/sol-pi.example.json +10 -0
- package/node_modules/@herbertgao/sol-pi/src/sol-pi/config.ts +135 -0
- package/node_modules/@herbertgao/sol-pi/src/sol-pi/extensions/action-fusion/file-queue.ts +74 -0
- package/node_modules/@herbertgao/sol-pi/src/sol-pi/extensions/action-fusion/index.ts +185 -0
- package/node_modules/@herbertgao/sol-pi/src/sol-pi/extensions/action-fusion/then-run.ts +128 -0
- package/node_modules/@herbertgao/sol-pi/src/sol-pi/extensions/evidence-preserving-reducer/archive.ts +53 -0
- package/node_modules/@herbertgao/sol-pi/src/sol-pi/extensions/evidence-preserving-reducer/candidate.ts +101 -0
- package/node_modules/@herbertgao/sol-pi/src/sol-pi/extensions/evidence-preserving-reducer/config.ts +71 -0
- package/node_modules/@herbertgao/sol-pi/src/sol-pi/extensions/evidence-preserving-reducer/index.ts +220 -0
- package/node_modules/@herbertgao/sol-pi/src/sol-pi/extensions/evidence-preserving-reducer/journal.ts +25 -0
- package/node_modules/@herbertgao/sol-pi/src/sol-pi/extensions/evidence-preserving-reducer/provider.ts +164 -0
- package/node_modules/@herbertgao/sol-pi/src/sol-pi/extensions/evidence-preserving-reducer/receipt.ts +177 -0
- package/node_modules/@herbertgao/sol-pi/src/sol-pi/extensions/observation-pack/index.ts +227 -0
- package/node_modules/@herbertgao/sol-pi/src/sol-pi/extensions/observation-pack/ledger.ts +20 -0
- package/node_modules/@herbertgao/sol-pi/src/sol-pi/extensions/observation-pack/observation.ts +252 -0
- package/node_modules/@herbertgao/sol-pi/src/sol-pi/extensions/online-context-compact/economics.ts +237 -0
- package/node_modules/@herbertgao/sol-pi/src/sol-pi/extensions/online-context-compact/extension.ts +455 -0
- package/node_modules/@herbertgao/sol-pi/src/sol-pi/extensions/online-context-compact/index.ts +49 -0
- package/node_modules/@herbertgao/sol-pi/src/sol-pi/extensions/online-context-compact/plan.ts +79 -0
- package/node_modules/@herbertgao/sol-pi/src/sol-pi/extensions/online-context-compact/state.ts +208 -0
- package/node_modules/@herbertgao/sol-pi/src/sol-pi/extensions/online-context-compact/tools.ts +100 -0
- package/node_modules/@herbertgao/sol-pi/src/sol-pi/index.ts +42 -0
- package/node_modules/@herbertgao/sol-pi/src/sol-pi/runtime-paths.ts +17 -0
- package/node_modules/@herbertgao/sol-pi/src/sol-pi/tui.ts +71 -0
- package/node_modules/@narumitw/pi-btw/dist/index.ts +12 -2
- package/node_modules/@narumitw/pi-btw/dist/index.ts.map +2 -2
- package/node_modules/@narumitw/pi-btw/package.json +1 -1
- package/node_modules/@narumitw/pi-btw/src/btw.ts +13 -2
- package/node_modules/pi-antigravity/LICENSE +21 -0
- package/node_modules/pi-antigravity/README.md +194 -0
- package/node_modules/pi-antigravity/package.json +67 -0
- package/node_modules/pi-antigravity/src/auth/index.ts +14 -0
- package/node_modules/pi-antigravity/src/auth/oauth.ts +442 -0
- package/node_modules/pi-antigravity/src/client/client.ts +561 -0
- package/node_modules/pi-antigravity/src/client/index.ts +1 -0
- package/node_modules/pi-antigravity/src/diagnostics/diagnostics.ts +96 -0
- package/node_modules/pi-antigravity/src/diagnostics/index.ts +1 -0
- package/node_modules/pi-antigravity/src/image/image.ts +336 -0
- package/node_modules/pi-antigravity/src/image/index.ts +1 -0
- package/node_modules/pi-antigravity/src/index.ts +280 -0
- package/node_modules/pi-antigravity/src/models/discovery.ts +154 -0
- package/node_modules/pi-antigravity/src/models/grouping.ts +424 -0
- package/node_modules/pi-antigravity/src/models/index.ts +3 -0
- package/node_modules/pi-antigravity/src/models/models.ts +500 -0
- package/node_modules/pi-antigravity/src/stream/index.ts +1 -0
- package/node_modules/pi-antigravity/src/stream/stream.ts +1460 -0
- package/node_modules/pi-antigravity/src/types/enums.ts +42 -0
- package/node_modules/pi-antigravity/src/types/index.ts +2 -0
- package/node_modules/pi-antigravity/src/types/types.ts +292 -0
- package/node_modules/pi-antigravity/src/usage/index.ts +1 -0
- package/node_modules/pi-antigravity/src/usage/usage.ts +371 -0
- package/node_modules/pi-antigravity/src/utils/http.ts +91 -0
- package/node_modules/pi-antigravity/src/utils/index.ts +3 -0
- package/node_modules/pi-antigravity/src/utils/security.ts +73 -0
- package/node_modules/pi-antigravity/src/utils/util.ts +132 -0
- package/node_modules/pi-antigravity/tsconfig.json +21 -0
- package/node_modules/pi-lens/CHANGELOG.md +176 -0
- package/node_modules/pi-lens/README.md +13 -8
- package/node_modules/pi-lens/config/dependency-cruiser-eager-allowlist.json +3 -1
- package/node_modules/pi-lens/dist/clients/analysed-root.js +1 -0
- package/node_modules/pi-lens/dist/clients/ast-grep-tool-logger.js +1 -1
- package/node_modules/pi-lens/dist/clients/biome-client.js +13 -2
- package/node_modules/pi-lens/dist/clients/bounded-telemetry.js +2 -0
- package/node_modules/pi-lens/dist/clients/cache-observability.js +122 -1
- package/node_modules/pi-lens/dist/clients/complexity-client.js +5 -0
- package/node_modules/pi-lens/dist/clients/config-diagnostic-codes.js +2 -0
- package/node_modules/pi-lens/dist/clients/config-resolve.js +6 -3
- package/node_modules/pi-lens/dist/clients/config-schema.js +17 -0
- package/node_modules/pi-lens/dist/clients/config-warn.js +2 -2
- package/node_modules/pi-lens/dist/clients/dead-code-client.js +8 -1
- package/node_modules/pi-lens/dist/clients/dependency-checker.js +5 -1
- package/node_modules/pi-lens/dist/clients/dispatch/dispatcher.js +38 -15
- package/node_modules/pi-lens/dist/clients/dispatch/facts/function-facts.js +1 -1
- package/node_modules/pi-lens/dist/clients/dispatch/facts/import-facts.js +5 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/actionlint.js +2 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/biome-check.js +2 -3
- package/node_modules/pi-lens/dist/clients/dispatch/runners/cpp-check.js +4 -3
- package/node_modules/pi-lens/dist/clients/dispatch/runners/credo.js +2 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/cue-vet.js +2 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/dart-analyze.js +2 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/detekt.js +2 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/dotnet-build.js +2 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/elixir-check.js +2 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/eslint.js +2 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/fish-indent.js +2 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/gleam-check.js +2 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/go-vet.js +2 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/golangci-lint.js +2 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/hadolint.js +2 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/helm-lint.js +3 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/helm-render.js +2 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/htmlhint.js +2 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/javac.js +2 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/ktlint.js +2 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/lsp.js +19 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/markdownlint.js +3 -2
- package/node_modules/pi-lens/dist/clients/dispatch/runners/mypy.js +2 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/oxlint.js +2 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/php-lint.js +2 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/phpstan.js +2 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/prisma-validate.js +2 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/psscriptanalyzer.js +12 -12
- package/node_modules/pi-lens/dist/clients/dispatch/runners/pyright.js +2 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/rubocop.js +2 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/ruff.js +2 -2
- package/node_modules/pi-lens/dist/clients/dispatch/runners/rust-clippy.js +12 -11
- package/node_modules/pi-lens/dist/clients/dispatch/runners/shellcheck.js +2 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/shfmt.js +2 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/spellcheck.js +2 -2
- package/node_modules/pi-lens/dist/clients/dispatch/runners/spotbugs.js +2 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/sqlfluff.js +3 -2
- package/node_modules/pi-lens/dist/clients/dispatch/runners/stylelint.js +3 -2
- package/node_modules/pi-lens/dist/clients/dispatch/runners/swiftlint.js +2 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/taplo.js +2 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/terragrunt.js +2 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/tflint.js +2 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/trivy-config.js +2 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/availability-policy.js +5 -0
- package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/candidate-probe.js +2 -2
- package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/runner-helpers.js +8 -2
- package/node_modules/pi-lens/dist/clients/dispatch/runners/vale.js +2 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/yamllint.js +4 -3
- package/node_modules/pi-lens/dist/clients/dispatch/runners/zig-check.js +2 -1
- package/node_modules/pi-lens/dist/clients/effective-config.js +12 -2
- package/node_modules/pi-lens/dist/clients/extension-log.js +2 -0
- package/node_modules/pi-lens/dist/clients/file-role.js +18 -1
- package/node_modules/pi-lens/dist/clients/finding-delivery-gate.js +4 -0
- package/node_modules/pi-lens/dist/clients/formatters.js +54 -188
- package/node_modules/pi-lens/dist/clients/generation-guard.js +4 -0
- package/node_modules/pi-lens/dist/clients/gitleaks-client.js +42 -6
- package/node_modules/pi-lens/dist/clients/govulncheck-client.js +48 -7
- package/node_modules/pi-lens/dist/clients/installer/index.js +196 -52
- package/node_modules/pi-lens/dist/clients/jscpd-client.js +9 -1
- package/node_modules/pi-lens/dist/clients/knip-client.js +9 -2
- package/node_modules/pi-lens/dist/clients/language-profile.js +12 -2
- package/node_modules/pi-lens/dist/clients/latency-logger.js +2 -0
- package/node_modules/pi-lens/dist/clients/lens-config.js +4 -0
- package/node_modules/pi-lens/dist/clients/lsp/config.js +36 -7
- package/node_modules/pi-lens/dist/clients/lsp/diagnostic-binding.js +24 -1
- package/node_modules/pi-lens/dist/clients/lsp/document-drift.js +78 -0
- package/node_modules/pi-lens/dist/clients/lsp/index.js +281 -45
- package/node_modules/pi-lens/dist/clients/lsp/server.js +62 -15
- package/node_modules/pi-lens/dist/clients/lsp/workspace-diagnostics-cache.js +37 -4
- package/node_modules/pi-lens/dist/clients/mcp/analyze.js +3 -0
- package/node_modules/pi-lens/dist/clients/mcp/session.js +2 -0
- package/node_modules/pi-lens/dist/clients/opengrep-client.js +2 -0
- package/node_modules/pi-lens/dist/clients/package-manager.js +2 -1
- package/node_modules/pi-lens/dist/clients/php-cs-fixer-config.js +8 -3
- package/node_modules/pi-lens/dist/clients/pipeline.js +3 -2
- package/node_modules/pi-lens/dist/clients/project-diagnostics/fresh-fetch.js +38 -10
- package/node_modules/pi-lens/dist/clients/project-diagnostics/runner-adapters/gitleaks.js +10 -9
- package/node_modules/pi-lens/dist/clients/project-lens-config.js +3 -0
- package/node_modules/pi-lens/dist/clients/read-guard-logger.js +2 -0
- package/node_modules/pi-lens/dist/clients/review-graph-logger.js +2 -0
- package/node_modules/pi-lens/dist/clients/ruff-client.js +18 -9
- package/node_modules/pi-lens/dist/clients/runtime-agent-end.js +9 -0
- package/node_modules/pi-lens/dist/clients/runtime-coordinator.js +5 -0
- package/node_modules/pi-lens/dist/clients/runtime-session.js +13 -4
- package/node_modules/pi-lens/dist/clients/runtime-tool-result.js +4 -1
- package/node_modules/pi-lens/dist/clients/runtime-turn.js +48 -1
- package/node_modules/pi-lens/dist/clients/security-scan-client.js +2 -2
- package/node_modules/pi-lens/dist/clients/session-event-guard.js +69 -1
- package/node_modules/pi-lens/dist/clients/sg-runner.js +2 -1
- package/node_modules/pi-lens/dist/clients/situational-tool-telemetry.js +108 -0
- package/node_modules/pi-lens/dist/clients/test-runner-client.js +291 -76
- package/node_modules/pi-lens/dist/clients/tool-config.js +199 -0
- package/node_modules/pi-lens/dist/clients/tool-cwd.js +299 -0
- package/node_modules/pi-lens/dist/clients/tool-probe.js +53 -0
- package/node_modules/pi-lens/dist/clients/tool-set-policy.js +42 -0
- package/node_modules/pi-lens/dist/clients/trivy-client.js +9 -1
- package/node_modules/pi-lens/dist/clients/turn-context.js +52 -0
- package/node_modules/pi-lens/dist/clients/widget-state.js +4 -3
- package/node_modules/pi-lens/dist/index.js +21969 -20005
- package/node_modules/pi-lens/dist/mcp/analyze-cli.js +3 -2
- package/node_modules/pi-lens/dist/mcp/server.js +135 -159
- package/node_modules/pi-lens/dist/tools/activate-tools.js +10 -7
- package/node_modules/pi-lens/dist/tools/ast-grep-outline.js +2 -13
- package/node_modules/pi-lens/dist/tools/ast-grep-replace.js +8 -13
- package/node_modules/pi-lens/dist/tools/ast-grep-search.js +83 -39
- package/node_modules/pi-lens/dist/tools/effective-config.js +4 -3
- package/node_modules/pi-lens/dist/tools/lens-diagnostic-mark.js +1 -10
- package/node_modules/pi-lens/dist/tools/lens-diagnostics.js +353 -85
- package/node_modules/pi-lens/dist/tools/lsp-diagnostics.js +21 -110
- package/node_modules/pi-lens/dist/tools/lsp-navigation.js +20 -44
- package/node_modules/pi-lens/dist/tools/module-report.js +5 -9
- package/node_modules/pi-lens/dist/tools/project-report.js +9 -17
- package/node_modules/pi-lens/dist/tools/render-compact.js +280 -1
- package/node_modules/pi-lens/dist/tools/symbol-search.js +2 -2
- package/node_modules/pi-lens/docs/agent-guide.md +3 -4
- package/node_modules/pi-lens/docs/agent-tools.md +46 -22
- package/node_modules/pi-lens/docs/configuration.md +9 -0
- package/node_modules/pi-lens/docs/environment-variables.md +0 -5
- package/node_modules/pi-lens/docs/features.md +5 -5
- package/node_modules/pi-lens/docs/globalconfig.md +11 -1
- package/node_modules/pi-lens/docs/mcp.md +13 -1
- package/node_modules/pi-lens/docs/pi-lens-fixer.md +71 -0
- package/node_modules/pi-lens/docs/pi-lens-investigator.md +15 -0
- package/node_modules/pi-lens/docs/pi-lens-monitor.md +88 -0
- package/node_modules/pi-lens/docs/pi-lens-reviewer.md +35 -0
- package/node_modules/pi-lens/docs/pi-lens-subagent.md +12 -4
- package/node_modules/pi-lens/docs/public-api-stability.md +1 -0
- package/node_modules/pi-lens/docs/real-harness.md +46 -0
- package/node_modules/pi-lens/docs/release-qa-baseline.md +5 -1
- package/node_modules/pi-lens/docs/servercapabilities.md +1 -6
- package/node_modules/pi-lens/docs/settings.md +37 -2
- package/node_modules/pi-lens/docs/tools_improvement2.md +4 -4
- package/node_modules/pi-lens/docs/tree-sitter_rules_catalog.md +1 -1
- package/node_modules/pi-lens/docs/usage.md +13 -3
- package/node_modules/pi-lens/package.json +10 -1
- package/node_modules/pi-lens/rules/tree-sitter-queries/typescript/sql-injection.yml +7 -3
- package/node_modules/pi-lens/skills/pi-lens-ast-grep/SKILL.md +8 -6
- package/node_modules/pi-lens/skills/pi-lens-lsp-navigation/SKILL.md +19 -9
- package/node_modules/pi-web-access/CHANGELOG.md +29 -0
- package/node_modules/pi-web-access/README.md +33 -22
- package/node_modules/pi-web-access/abortable.ts +17 -0
- package/node_modules/pi-web-access/crawl4ai.ts +204 -0
- package/node_modules/pi-web-access/credential-source.ts +1 -0
- package/node_modules/pi-web-access/curator-page.ts +20 -2
- package/node_modules/pi-web-access/curator-run.ts +44 -0
- package/node_modules/pi-web-access/curator-server.ts +3 -1
- package/node_modules/pi-web-access/duckduckgo.ts +1 -1
- package/node_modules/pi-web-access/extract.ts +57 -12
- package/node_modules/pi-web-access/fetch-params.ts +1 -1
- package/node_modules/pi-web-access/gemini-search.ts +9 -5
- package/node_modules/pi-web-access/index.ts +25 -18
- package/node_modules/pi-web-access/openai-search.ts +35 -13
- package/node_modules/pi-web-access/package.json +2 -2
- package/node_modules/pi-web-access/page-query.ts +5 -2
- package/node_modules/pi-web-access/query-rewrite.ts +5 -2
- package/node_modules/pi-web-access/serpapi.ts +220 -0
- package/node_modules/pi-web-access/ssrf-protection.ts +5 -1
- package/node_modules/pi-web-access/storage.ts +10 -0
- package/node_modules/pi-web-access/summary-review.ts +34 -21
- package/node_modules/pi-web-access/utils.ts +6 -4
- package/package.json +11 -5
- package/node_modules/pi-lens/dist/tools/ast-dump.js +0 -103
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// Compact tool-result rendering (refs #345).
|
|
2
2
|
//
|
|
3
3
|
// The navigable/structural tools (module_report, read_symbol, read_enclosing,
|
|
4
|
-
// ast_grep_search,
|
|
4
|
+
// ast_grep_search, ast_grep_outline) return large bodies that are
|
|
5
5
|
// useful to the MODEL but flood the user's terminal. The pi host renders a tool's
|
|
6
6
|
// `content` verbatim only when the tool defines no `renderResult` (the
|
|
7
7
|
// createResultFallback path in tool-execution.ts). By supplying a `renderResult`
|
|
@@ -19,6 +19,285 @@
|
|
|
19
19
|
// (overrideExistingRenderers === false), so these renderers win and still coexist
|
|
20
20
|
// with a globally-installed renderer extension.
|
|
21
21
|
import { Text } from "../clients/deps/pi-tui.js";
|
|
22
|
+
import * as fs from "node:fs";
|
|
23
|
+
import * as path from "node:path";
|
|
24
|
+
import { randomUUID } from "node:crypto";
|
|
25
|
+
import { recordDegradationOnce } from "../clients/degradation-ledger.js";
|
|
26
|
+
import { getGlobalPiLensLogDir } from "../clients/probe-home-state.js";
|
|
27
|
+
/** The per-result delivery bound (#2848). Exported so surface gates and their
|
|
28
|
+
* pins assert the real budget instead of restating the literal. */
|
|
29
|
+
export const MAX_RESULT_BYTES = 40 * 1024;
|
|
30
|
+
// 2026-09-10: cap the complete MCP payload before it can retain or log an
|
|
31
|
+
// unbounded result; ordinary results keep the complete-log contract below it.
|
|
32
|
+
export const COMPLETE_MCP_RESULT_INPUT_BUDGET_BYTES = 8 * 1024 * 1024;
|
|
33
|
+
// #2800 item 7: the footer is stamped AFTER the payload bound, so the
|
|
34
|
+
// footer's own maximum size is reserved inside MAX_RESULT_BYTES. The reserve
|
|
35
|
+
// is computed from the footer's widest literal: the `result error` verdict, a
|
|
36
|
+
// bounded diag severity section, and maximum-width numeric fields with the
|
|
37
|
+
// wider `truncated=false` value.
|
|
38
|
+
const FOOTER_MAX_DIGITS = String(Number.MAX_SAFE_INTEGER).length;
|
|
39
|
+
/** One `diag severity=` line is width-bounded so the footer's maximum size
|
|
40
|
+
* stays finite and the reserve above stays sound. */
|
|
41
|
+
const FOOTER_DIAG_LINE_MAX_CHARS = 200;
|
|
42
|
+
const FOOTER_DIAG_SECTION_MAX_BYTES = 1024;
|
|
43
|
+
export const RESULT_FOOTER_RESERVE_BYTES = Buffer.byteLength(`\n\nresult error\n${"x".repeat(FOOTER_DIAG_SECTION_MAX_BYTES)}\nusage tokens=${"9".repeat(FOOTER_MAX_DIGITS)} elapsed-ms=${"9".repeat(FOOTER_MAX_DIGITS)} bytes=${"9".repeat(FOOTER_MAX_DIGITS)} truncated=false`, "utf8");
|
|
44
|
+
// The literal reserve intentionally leaves about 1 KiB below MAX_RESULT_BYTES
|
|
45
|
+
// for footer growth. Keep this conservative slack: deriving the bound by
|
|
46
|
+
// iterating over a changing footer caused both overflows and repeated log writes
|
|
47
|
+
// (round 2 F2/F7, refs #2862 and #2864).
|
|
48
|
+
/** The payload byte budget the footer is stamped into: the delivered result
|
|
49
|
+
* budget minus the reserved footer maximum (#2800 item 7). */
|
|
50
|
+
export const RESULT_PAYLOAD_BUDGET_BYTES = MAX_RESULT_BYTES - RESULT_FOOTER_RESERVE_BYTES;
|
|
51
|
+
function renderHeadTail(text, maxBytes, markerFor) {
|
|
52
|
+
const render = (kept) => {
|
|
53
|
+
const head = Math.floor(kept / 2);
|
|
54
|
+
const tail = kept - head;
|
|
55
|
+
return `${text.slice(0, head)}${markerFor(head, tail)}${text.slice(text.length - tail)}`;
|
|
56
|
+
};
|
|
57
|
+
let low = 0;
|
|
58
|
+
let high = text.length;
|
|
59
|
+
while (low < high) {
|
|
60
|
+
const kept = Math.ceil((low + high) / 2);
|
|
61
|
+
if (Buffer.byteLength(render(kept), "utf8") <= maxBytes)
|
|
62
|
+
low = kept;
|
|
63
|
+
else
|
|
64
|
+
high = kept - 1;
|
|
65
|
+
}
|
|
66
|
+
return { text: render(low), keptCharacters: low };
|
|
67
|
+
}
|
|
68
|
+
/** Bound model-facing result text while retaining both the useful head and tail.
|
|
69
|
+
* `maxBytes` defaults to the full result budget; the footer gate passes the
|
|
70
|
+
* payload budget that leaves room for the stamped footer (#2800 item 7). */
|
|
71
|
+
export function boundToolText(text, maxBytes = MAX_RESULT_BYTES) {
|
|
72
|
+
const totalBytes = Buffer.byteLength(text, "utf8");
|
|
73
|
+
if (totalBytes <= maxBytes) {
|
|
74
|
+
return { text, truncated: false, omittedCharacters: 0 };
|
|
75
|
+
}
|
|
76
|
+
const fullOutputPath = path.join(getGlobalPiLensLogDir(), `tool-result-${Date.now()}-${randomUUID()}.log`);
|
|
77
|
+
fs.mkdirSync(path.dirname(fullOutputPath), { recursive: true });
|
|
78
|
+
if (totalBytes > COMPLETE_MCP_RESULT_INPUT_BUDGET_BYTES) {
|
|
79
|
+
const omittedBytes = totalBytes - COMPLETE_MCP_RESULT_INPUT_BUDGET_BYTES;
|
|
80
|
+
recordDegradationOnce({
|
|
81
|
+
kind: "mcp-complete-result-budget-exceeded",
|
|
82
|
+
subject: "complete-result",
|
|
83
|
+
reason: `${totalBytes} input bytes exceeded ${COMPLETE_MCP_RESULT_INPUT_BUDGET_BYTES}-byte budget`,
|
|
84
|
+
metadata: {
|
|
85
|
+
totalBytes,
|
|
86
|
+
budgetBytes: COMPLETE_MCP_RESULT_INPUT_BUDGET_BYTES,
|
|
87
|
+
},
|
|
88
|
+
});
|
|
89
|
+
const logText = renderHeadTail(text, COMPLETE_MCP_RESULT_INPUT_BUDGET_BYTES, (head, tail) => {
|
|
90
|
+
const keptBytes = Buffer.byteLength(`${text.slice(0, head)}${text.slice(text.length - tail)}`, "utf8");
|
|
91
|
+
return `\n\n[incomplete: ${totalBytes - keptBytes} bytes omitted, budget ${COMPLETE_MCP_RESULT_INPUT_BUDGET_BYTES}]\n\n[Full output: ${fullOutputPath}]\n\n`;
|
|
92
|
+
});
|
|
93
|
+
fs.writeFileSync(fullOutputPath, logText.text, "utf8");
|
|
94
|
+
const output = renderHeadTail(logText.text, maxBytes, () => `\n\n[incomplete: ${omittedBytes} bytes omitted, budget ${COMPLETE_MCP_RESULT_INPUT_BUDGET_BYTES}]\n\n[Full output: ${fullOutputPath}]\n\n`);
|
|
95
|
+
return {
|
|
96
|
+
text: output.text,
|
|
97
|
+
truncated: true,
|
|
98
|
+
omittedCharacters: text.length - logText.keptCharacters,
|
|
99
|
+
fullOutputPath,
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
fs.writeFileSync(fullOutputPath, text, "utf8");
|
|
103
|
+
const output = renderHeadTail(text, maxBytes, (head, tail) => `\n\n[${text.length - head - tail} characters omitted. Full output: ${fullOutputPath}]\n\n`);
|
|
104
|
+
return {
|
|
105
|
+
text: output.text,
|
|
106
|
+
truncated: true,
|
|
107
|
+
omittedCharacters: text.length - output.keptCharacters,
|
|
108
|
+
fullOutputPath,
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
/** Matches an already-stamped contract footer at the end of the joined text.
|
|
112
|
+
* A result re-entering the gate must not gain a second footer (refs #2852 N4).
|
|
113
|
+
* The byte and truncated groups let the gate read the kept footer's own
|
|
114
|
+
* delivery figures on re-entry (round 2 F1). */
|
|
115
|
+
const CONTRACT_FOOTER_TAIL_RE = /(?:^|\n)result (?:ok|error)\n(?:diag severity=[^\n]*\n)*usage tokens=\d+ elapsed-ms=\d+ bytes=(\d+) truncated=(true|false)$/;
|
|
116
|
+
/**
|
|
117
|
+
* Add the stable, model-facing result footer shared by pi and MCP.
|
|
118
|
+
*
|
|
119
|
+
* The host adapters own transport and terminal styling; this function owns the
|
|
120
|
+
* textual contract. Defaults are deliberately deterministic because elapsed
|
|
121
|
+
* time is not a property of a projection and must not make parity tests flaky.
|
|
122
|
+
* Idempotent: a result whose text already ends with the footer is only
|
|
123
|
+
* `isError`-normalized, never stamped twice.
|
|
124
|
+
*
|
|
125
|
+
* When `delivery` is given (the gate path), `bytes=`/`truncated=` describe the
|
|
126
|
+
* already-bound payload; otherwise they describe this function's input text,
|
|
127
|
+
* which is the delivered payload because no bound has run.
|
|
128
|
+
*/
|
|
129
|
+
export function renderToolResultContract(result, delivery) {
|
|
130
|
+
const normalized = {
|
|
131
|
+
...result,
|
|
132
|
+
isError: result.isError === true,
|
|
133
|
+
};
|
|
134
|
+
const content = result.content ?? [];
|
|
135
|
+
const textBlocks = content
|
|
136
|
+
.filter((block) => block.type === "text" && typeof block.text === "string")
|
|
137
|
+
.map((block) => block.text);
|
|
138
|
+
if (textBlocks.length === 0)
|
|
139
|
+
return normalized;
|
|
140
|
+
const text = textBlocks.join("\n");
|
|
141
|
+
if (CONTRACT_FOOTER_TAIL_RE.test(text))
|
|
142
|
+
return normalized;
|
|
143
|
+
const details = normalized.details;
|
|
144
|
+
// The diag section is width- and byte-bounded (#2800 item 7) so the
|
|
145
|
+
// footer's maximum size — and therefore the reserved budget above — stays
|
|
146
|
+
// finite regardless of how many diagnostics a result carries.
|
|
147
|
+
const diagLines = [];
|
|
148
|
+
let diagSectionBytes = 0;
|
|
149
|
+
if (Array.isArray(details?.diagnostics)) {
|
|
150
|
+
for (const value of details.diagnostics) {
|
|
151
|
+
if (!value || typeof value !== "object")
|
|
152
|
+
continue;
|
|
153
|
+
const severity = value.severity;
|
|
154
|
+
if (typeof severity !== "string")
|
|
155
|
+
continue;
|
|
156
|
+
const line = `diag severity=${severity.slice(0, FOOTER_DIAG_LINE_MAX_CHARS)}`;
|
|
157
|
+
const lineBytes = Buffer.byteLength(line, "utf8") + 1;
|
|
158
|
+
if (diagSectionBytes + lineBytes > FOOTER_DIAG_SECTION_MAX_BYTES)
|
|
159
|
+
break;
|
|
160
|
+
diagLines.push(line);
|
|
161
|
+
diagSectionBytes += lineBytes;
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
const tokens = normalized.usage?.tokens ?? Math.ceil(Buffer.byteLength(text, "utf8") / 4);
|
|
165
|
+
const elapsedMs = normalized.usage?.elapsedMs ?? 0;
|
|
166
|
+
const deliveredBytes = delivery?.bytes ?? Buffer.byteLength(text, "utf8");
|
|
167
|
+
const truncated = delivery?.truncated === true;
|
|
168
|
+
const contractLines = [
|
|
169
|
+
`result ${normalized.isError ? "error" : "ok"}`,
|
|
170
|
+
...diagLines,
|
|
171
|
+
`usage tokens=${tokens} elapsed-ms=${elapsedMs} bytes=${deliveredBytes} truncated=${truncated ? "true" : "false"}`,
|
|
172
|
+
];
|
|
173
|
+
let lastTextIndex = -1;
|
|
174
|
+
for (let index = content.length - 1; index >= 0; index--) {
|
|
175
|
+
const block = content[index];
|
|
176
|
+
if (block?.type === "text" && typeof block.text === "string") {
|
|
177
|
+
lastTextIndex = index;
|
|
178
|
+
break;
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
if (lastTextIndex < 0)
|
|
182
|
+
return normalized;
|
|
183
|
+
return {
|
|
184
|
+
...normalized,
|
|
185
|
+
content: content.map((block, index) => index === lastTextIndex && block.type === "text"
|
|
186
|
+
? { ...block, text: `${block.text}\n\n${contractLines.join("\n")}` }
|
|
187
|
+
: block),
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
/** Bound the payload text blocks so the footer stamped afterwards still fits
|
|
191
|
+
* inside MAX_RESULT_BYTES (refs #2800 item 7): each block is bounded to the
|
|
192
|
+
* result budget minus the reserved footer maximum, and the delivered byte
|
|
193
|
+
* count plus the bound's truncated flag travel with the result so the footer
|
|
194
|
+
* can report them. Per-block bounding is inherited from #2852; production
|
|
195
|
+
* results carry a single text block (renderToolText). */
|
|
196
|
+
export function boundResultPayload(result) {
|
|
197
|
+
if (!result.content) {
|
|
198
|
+
return { result, deliveredBytes: 0, truncated: false };
|
|
199
|
+
}
|
|
200
|
+
const joined = fullTextOf(result);
|
|
201
|
+
// Reserve the widest footer once. This keeps the MAX_RESULT_BYTES invariant
|
|
202
|
+
// independent of payload contents and gives boundToolText one log write.
|
|
203
|
+
const bound = boundToolText(joined, RESULT_PAYLOAD_BUDGET_BYTES);
|
|
204
|
+
const firstText = result.content.findIndex((block) => block.type === "text" && typeof block.text === "string");
|
|
205
|
+
let retainedText = false;
|
|
206
|
+
const content = result.content
|
|
207
|
+
.filter((block, index) => block.type !== "text" ||
|
|
208
|
+
typeof block.text !== "string" ||
|
|
209
|
+
index === firstText)
|
|
210
|
+
.map((block) => {
|
|
211
|
+
if (block.type === "text" &&
|
|
212
|
+
typeof block.text === "string" &&
|
|
213
|
+
!retainedText) {
|
|
214
|
+
retainedText = true;
|
|
215
|
+
return { ...block, text: bound.text };
|
|
216
|
+
}
|
|
217
|
+
return block;
|
|
218
|
+
});
|
|
219
|
+
const deliveredBytes = Buffer.byteLength(fullTextOf({ content }), "utf8");
|
|
220
|
+
return {
|
|
221
|
+
result: { ...result, content },
|
|
222
|
+
deliveredBytes,
|
|
223
|
+
truncated: bound.truncated,
|
|
224
|
+
};
|
|
225
|
+
}
|
|
226
|
+
/** Build the raw result envelope shared by both host adapters: the summary is
|
|
227
|
+
* joined with the structured payload's fenced JSON, and the payload also rides
|
|
228
|
+
* along as `details` for surface-side consumers (pi's compact-line summarizer,
|
|
229
|
+
* the MCP gate's `diag severity=` footer lines). The contract footer and the
|
|
230
|
+
* #2848 byte bound are NOT applied here — each surface stamps them once, after
|
|
231
|
+
* the tool's own result exists (`finalizeToolResult` / the MCP dispatcher). */
|
|
232
|
+
export function renderToolText(summary, structured, compact = false) {
|
|
233
|
+
const rawText = structured === undefined
|
|
234
|
+
? summary
|
|
235
|
+
: `${summary}\n\n\`\`\`json\n${JSON.stringify(structured, compact ? undefined : null, compact ? undefined : 2)}\n\`\`\``;
|
|
236
|
+
return {
|
|
237
|
+
content: [{ type: "text", text: rawText }],
|
|
238
|
+
details: structured,
|
|
239
|
+
};
|
|
240
|
+
}
|
|
241
|
+
/** Drop the structured `details` field from a finished result. The MCP gate
|
|
242
|
+
* consumes `details` for the footer's `diag severity=` lines and then strips
|
|
243
|
+
* it before delivery, so the wire carries only the bounded text blocks
|
|
244
|
+
* (refs #2852 N1); pi keeps `details` for its compact-line summarizer. */
|
|
245
|
+
export function stripResultDetails(result) {
|
|
246
|
+
const { details: _details, ...rest } = result;
|
|
247
|
+
return rest;
|
|
248
|
+
}
|
|
249
|
+
/** Finish a host-adapter result after its status and all warnings exist
|
|
250
|
+
* (#2800 item 7): the payload bound runs FIRST with the footer's own maximum
|
|
251
|
+
* size reserved inside MAX_RESULT_BYTES, then the footer is stamped LAST with
|
|
252
|
+
* the delivered payload's byte count and the bound's truncated flag. So
|
|
253
|
+
* `bytes=`/`truncated=` describe what the model actually receives, and the
|
|
254
|
+
* delivered text — footer included — never exceeds MAX_RESULT_BYTES.
|
|
255
|
+
*
|
|
256
|
+
* Re-entry (refs #2852 N4, round 2 F1): the bound still runs on an
|
|
257
|
+
* already-stamped result — master applied the bound after the stamp-skip, and
|
|
258
|
+
* the kept tail carries the footer through it — so re-entry is never delivered
|
|
259
|
+
* unbounded. A stamped result within the delivered budget is kept as-is; the
|
|
260
|
+
* figures are the kept footer's own prior values, never a footer-inclusive
|
|
261
|
+
* re-measure and never a hard-coded `false`. */
|
|
262
|
+
export function finalizeToolResultWithDelivery(result) {
|
|
263
|
+
const normalized = { ...result, isError: result.isError === true };
|
|
264
|
+
const existingText = fullTextOf(normalized);
|
|
265
|
+
const existingFooter = CONTRACT_FOOTER_TAIL_RE.exec(existingText);
|
|
266
|
+
if (existingFooter &&
|
|
267
|
+
Buffer.byteLength(existingText, "utf8") <= MAX_RESULT_BYTES) {
|
|
268
|
+
return {
|
|
269
|
+
result: normalized,
|
|
270
|
+
deliveredBytes: Number(existingFooter[1]),
|
|
271
|
+
truncated: existingFooter[2] === "true",
|
|
272
|
+
};
|
|
273
|
+
}
|
|
274
|
+
const bound = boundResultPayload(normalized);
|
|
275
|
+
const text = fullTextOf(bound.result);
|
|
276
|
+
const keptFooter = CONTRACT_FOOTER_TAIL_RE.exec(text);
|
|
277
|
+
if (keptFooter) {
|
|
278
|
+
// The bound ran and the kept tail still carries the footer, so there is
|
|
279
|
+
// nothing to stamp; the kept footer's figures stay the delivery
|
|
280
|
+
// contract (row 6: prior value kept).
|
|
281
|
+
return {
|
|
282
|
+
result: bound.result,
|
|
283
|
+
deliveredBytes: Number(keptFooter[1]),
|
|
284
|
+
truncated: keptFooter[2] === "true",
|
|
285
|
+
};
|
|
286
|
+
}
|
|
287
|
+
const stamped = renderToolResultContract(bound.result, {
|
|
288
|
+
bytes: bound.deliveredBytes,
|
|
289
|
+
truncated: bound.truncated,
|
|
290
|
+
});
|
|
291
|
+
return {
|
|
292
|
+
result: stamped,
|
|
293
|
+
deliveredBytes: bound.deliveredBytes,
|
|
294
|
+
truncated: bound.truncated,
|
|
295
|
+
};
|
|
296
|
+
}
|
|
297
|
+
/** Finish a host-adapter result after its status and all warnings exist. */
|
|
298
|
+
export function finalizeToolResult(result) {
|
|
299
|
+
return finalizeToolResultWithDelivery(result).result;
|
|
300
|
+
}
|
|
22
301
|
// pi-lens brand colour: blue characters on whatever background the pi tool shell
|
|
23
302
|
// paints (default success/error background is left untouched). Truecolor bold
|
|
24
303
|
// foreground, theme-independent so the summary reads as ours regardless of the
|
|
@@ -23,8 +23,8 @@ export function createSymbolSearchTool(getProjectRoot) {
|
|
|
23
23
|
return {
|
|
24
24
|
name: "symbol_search",
|
|
25
25
|
label: "Symbol Search",
|
|
26
|
-
description: "
|
|
27
|
-
promptSnippet: "
|
|
26
|
+
description: "Find relevant files by ranked identifier search. On a cold cache, project_report and symbol_search return available: false with a retry hint and start a non-blocking background build; module_report degrades to outline-only with cache freshness explicit. Example: search `authenticate user` before module_report.",
|
|
27
|
+
promptSnippet: "Find files by identifier",
|
|
28
28
|
renderResult: compactRenderResult(({ details, isError }) => {
|
|
29
29
|
if (isError || details?.available === false) {
|
|
30
30
|
return `symbol_search "${details?.query ?? ""}" — unavailable${details?.hint ? `: ${details.hint}` : ""}`;
|
|
@@ -278,8 +278,7 @@ Registered as pi agent tools. Verified names:
|
|
|
278
278
|
|
|
279
279
|
| Tool | What it does | Use it to… |
|
|
280
280
|
|---|---|---|
|
|
281
|
-
| `lens_diagnostics` |
|
|
282
|
-
| `lsp_diagnostics` | LSP diagnostics for explicit files/dirs (per-file `clean`/`unavailable`/`unconfirmed`/… outcomes). | Targeted LSP check on specific files. |
|
|
281
|
+
| `lens_diagnostics` | Session-cache or LSP-probe diagnostics with `source` and `scope` selectors. | Use `source=lsp` with `scope=paths` for targeted checks; an empty cache is not proof of clean. |
|
|
283
282
|
| `lsp_navigation` | LSP navigation (definition/references/etc.). | Trace symbols semantically. |
|
|
284
283
|
| `symbol_search` | Ranked identifier search over the warm word index (BM25 + priors). | Entry point of the discovery funnel. |
|
|
285
284
|
| `module_report` | Navigable outline + signatures + decorators + imports + callbacks for a file; optional `blastRadius`. | Understand a module without reading the whole body. |
|
|
@@ -287,7 +286,7 @@ Registered as pi agent tools. Verified names:
|
|
|
287
286
|
| `read_enclosing` | Smallest enclosing symbol/callback body for a file+line (records coverage). | Bridge a diagnostic location → exact body. |
|
|
288
287
|
| `project_report` | Project-level structural report. | Orient in an unfamiliar project. |
|
|
289
288
|
| `ast_grep_search` / `ast_grep_replace` | Structural AST search / replace. | Find or rewrite by code shape, not regex. |
|
|
290
|
-
| `ast_grep_outline` / `
|
|
289
|
+
| `ast_grep_outline` / `ast_grep_search` (`dump=true`) | Outline / AST dump. | Inspect structure. |
|
|
291
290
|
| `lens_diagnostic_mark` | Mark a finding false-positive / suppressed / deferred / flagged-to-fix (honored across surfaces). | Triage a finding you've judged. |
|
|
292
291
|
|
|
293
292
|
Funnel discipline: **`symbol_search` → `module_report` → `read_symbol`/`read_enclosing`**
|
|
@@ -315,7 +314,7 @@ pi-lens is also an MCP server. The same capabilities are mirrored under a `pilen
|
|
|
315
314
|
prefix: `pilens_diagnostics`, `pilens_analyze`, `pilens_module_report`,
|
|
316
315
|
`pilens_symbol_search`, `pilens_read_symbol`, `pilens_read_enclosing`,
|
|
317
316
|
`pilens_project_report`, `pilens_project_scan`, `pilens_lsp_navigation`,
|
|
318
|
-
`
|
|
317
|
+
`pilens_ast_grep_search`/`pilens_ast_grep_replace`,
|
|
319
318
|
`pilens_session_start`/`pilens_turn_end`, `pilens_health`, `pilens_latency`,
|
|
320
319
|
`pilens_rebuild` (source checkouts only). Note MCP has **no read-guard** — mirror reads
|
|
321
320
|
don't record edit coverage. Prefer the **warm** review path; MCP `fresh` mode
|
|
@@ -2,43 +2,67 @@
|
|
|
2
2
|
|
|
3
3
|
pi-lens registers the following tools with the pi agent. Most are also exposed
|
|
4
4
|
through the MCP mirror (`clients/lens-engine.ts` is the seam both adapters
|
|
5
|
-
share) — current
|
|
5
|
+
share) — current exception: `ast_grep_outline`
|
|
6
6
|
(module_report supersedes them for discovery), and `lens_diagnostic_mark`
|
|
7
7
|
(pi-lens-internal for now). `read_enclosing` gained MCP parity
|
|
8
|
-
(`pilens_read_enclosing`) as of #536, closing #522 item 1.
|
|
8
|
+
(`pilens_read_enclosing`) as of #536, closing #522 item 1. The standalone
|
|
9
|
+
`lsp_diagnostics` tool was folded into `lens_diagnostics` (`source=lsp`,
|
|
10
|
+
#2860). The retired MCP name remains a one-release compatibility redirect to
|
|
11
|
+
`pilens_diagnostics` with `source=lsp` and `scope=paths` (`mcp/server.ts`),
|
|
12
|
+
logging one `lsp-diagnostics-compatibility` degradation per session. Callers
|
|
13
|
+
should move to `pilens_diagnostics`.
|
|
9
14
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
`
|
|
13
|
-
`
|
|
15
|
+
|
|
16
|
+
**Dynamic tooling.** Five tools stay always-active: `lens_diagnostics`,
|
|
17
|
+
`module_report`, `read_symbol`, `read_enclosing`,
|
|
18
|
+
`symbol_search`. Five situational tools — `ast_grep_search`, `ast_grep_replace`,
|
|
19
|
+
`ast_grep_outline`, `lsp_navigation`, `lens_diagnostic_mark` —
|
|
14
20
|
are registered but
|
|
15
21
|
inactive by default; the model activates the ones it needs via the always-active
|
|
16
22
|
loader tool `pi_lens_activate_tools`, per pi's dynamic-tool-loading API
|
|
17
23
|
(`pi.setActiveTools`/`pi.getActiveTools`). The loader explicitly reports
|
|
18
24
|
"Available starting next turn"; do not retry the tool in the same turn.
|
|
19
|
-
Feature-detected: on hosts without that API, the
|
|
25
|
+
Feature-detected: on hosts without that API, the five situational tools fall back
|
|
20
26
|
to being statically active, exactly as before (`tools/activate-tools.ts`, wired
|
|
21
27
|
in `index.ts`).
|
|
22
28
|
|
|
29
|
+
Tool descriptions contain the contract sentence and one example. Operational
|
|
30
|
+
guidance, including cache state, scan scope, safety details, and lifecycle
|
|
31
|
+
results, belongs in the returned result so it is paid only when the tool runs.
|
|
32
|
+
|
|
33
|
+
**Result contract.** One post-result gate per surface — `finalizeToolResult` /
|
|
34
|
+
`finalizeToolResultWithDelivery` in `tools/render-compact.ts`, wired into every
|
|
35
|
+
pi tool's `execute` wrapper in `index.ts` and into the MCP `tools/call`
|
|
36
|
+
dispatcher in `mcp/server.ts` — bounds every delivered result to 40 KiB
|
|
37
|
+
(`MAX_RESULT_BYTES`) and stamps a trailing usage footer describing what was
|
|
38
|
+
actually sent: `usage tokens=<n> elapsed-ms=<n> bytes=<n> truncated=<true|false>`.
|
|
39
|
+
A rejected call (e.g. MCP's "Unknown or disabled tool") is rendered through the
|
|
40
|
+
same gate rather than bypassing it.
|
|
41
|
+
|
|
23
42
|
## Per-edit
|
|
24
43
|
|
|
25
|
-
- **`lens_diagnostics`** —
|
|
26
|
-
|
|
27
|
-
|
|
44
|
+
- **`lens_diagnostics`** — Session-cache or LSP-probe diagnostic state, selected
|
|
45
|
+
by `source` (`session` default, or `lsp`) and `scope` (`paths` or
|
|
46
|
+
`workspace`; explicit `paths` always win over `scope`). `severity` is a
|
|
47
|
+
threshold, not an exact filter: `error` shows only errors; `warning` adds
|
|
48
|
+
warnings; `information` adds information; `hint`/`all` (default) show every
|
|
49
|
+
tier. Legacy `mode`: `delta` (current turn), `all` (resurfaces stale
|
|
50
|
+
blockers dropped from turn context), `full` (project-wide scan).
|
|
28
51
|
- **`lens_diagnostic_mark`** — Triage a diagnostic: `false-positive` /
|
|
29
52
|
`suppress` (writes an inline `pi-lens-ignore` comment) / `defer`
|
|
30
53
|
(session-only) / `flagged` (persists, rendered `📌 flagged-to-fix`).
|
|
31
54
|
Content-anchored so marks survive edits; every mark is logged and published
|
|
32
55
|
on the bus. See [dispositions.md](dispositions.md).
|
|
33
|
-
- **`
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
`
|
|
37
|
-
`
|
|
38
|
-
`
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
56
|
+
- **`lsp_navigation`** — IDE-style navigation, 19 operations: `definition`,
|
|
57
|
+
`typeDefinition`, `declaration`, `references`, `hover`, `signatureHelp`,
|
|
58
|
+
`documentSymbol`, `findSymbol`, `workspaceSymbol`, `codeAction`, `rename`,
|
|
59
|
+
`rename_file`, `implementation`, `prepareCallHierarchy`, `incomingCalls`,
|
|
60
|
+
`outgoingCalls`, `executeCommand`, `workspaceDiagnostics`, and `capabilities`
|
|
61
|
+
(`tools/lsp-navigation.ts` operation description). Position-based operations
|
|
62
|
+
accept a `path`/`line`/`character` triple. `documentSymbol` accepts a `kinds`
|
|
63
|
+
filter (e.g. `function`, `class`) and a `maxResults` cap (default 20, max 100)
|
|
64
|
+
to keep large files bounded. Full per-operation parameter reference:
|
|
65
|
+
[skills/pi-lens-lsp-navigation/SKILL.md](../skills/pi-lens-lsp-navigation/SKILL.md).
|
|
42
66
|
- **`ast_grep_search`** — AST-aware structural search across ~40 languages via
|
|
43
67
|
the `sg` CLI. Supports metavariables (`$VAR`, `$$$ARGS`), `strictness`
|
|
44
68
|
modes (`smart`, `relaxed`, `ast`, `cst`, `signature`, `template`), structural
|
|
@@ -48,7 +72,7 @@ in `index.ts`).
|
|
|
48
72
|
(per-call cap, default 50, max 200; also sets the pagination step).
|
|
49
73
|
`nodeKind` is an expert grammar-specific escape hatch: it finds every node of
|
|
50
74
|
the exact kind used by the target grammar. Node kinds are not universal across
|
|
51
|
-
languages; use `
|
|
75
|
+
languages; use `dump=true` with the representative snippet in `pattern` to discover the kind in the target language. It is mutually exclusive with `rule`.
|
|
52
76
|
`hasKind` retains ast-grep's immediate-child semantics; use
|
|
53
77
|
`hasDescendantKind` for an explicit recursive descendant search. A future
|
|
54
78
|
canonical `find`/`query` facade (call/function/import/etc.) should map to
|
|
@@ -60,11 +84,11 @@ in `index.ts`).
|
|
|
60
84
|
searches. `pattern` is optional when a `rule` or `nodeKind` is given.
|
|
61
85
|
Results include `details.matchLocations[]` — each hit carries a ready
|
|
62
86
|
`readSlice` (`path`/`offset`/`limit`) for a bounded context read; zero-match
|
|
63
|
-
results include a `suggestedDump` hint pointing at `
|
|
87
|
+
results include a `suggestedDump` hint pointing at `ast_grep_search` with `dump=true`.
|
|
64
88
|
- **`ast_grep_replace`** — AST-aware structural replace. Re-validates the pattern
|
|
65
89
|
against the current file before writing and reports a clear error if the
|
|
66
90
|
file changed since the preview.
|
|
67
|
-
- **`
|
|
91
|
+
- **`ast_grep_search` with `dump=true`** — Dumps the raw tree-sitter AST for a source snippet. Use
|
|
68
92
|
this when an `ast_grep_search` or `ast_grep_replace` pattern returns zero
|
|
69
93
|
matches and the correct node kind or field name is unknown. `includeAnonymous`
|
|
70
94
|
shows punctuation/CST nodes.
|
|
@@ -36,6 +36,15 @@ everything LSP-related lives under an `lsp` namespace inside them.
|
|
|
36
36
|
}
|
|
37
37
|
```
|
|
38
38
|
|
|
39
|
+
Each model-facing tool accepts `tools.<name>.enabled` in the config file. Valid
|
|
40
|
+
names include `ast_grep_search`, `ast_grep_replace`, `ast_grep_outline`,
|
|
41
|
+
`lsp_navigation`, `lens_diagnostics`,
|
|
42
|
+
`lens_diagnostic_mark`, `symbol_search`, `module_report`, `project_report`,
|
|
43
|
+
`read_symbol`, `read_enclosing`, `effective_config`, `analyze`, `health`,
|
|
44
|
+
`latency`, `project_scan`, and `rebuild`. The activation loader and MCP
|
|
45
|
+
lifecycle tools `session_start`, `turn_end`, and `session_end` remain enabled
|
|
46
|
+
because their host protocols require them.
|
|
47
|
+
|
|
39
48
|
**Some settings are global-only.** A handful of switches — `lsp.enabled`
|
|
40
49
|
(`--no-lsp`), `tests.enabled`, `delta.enabled` and the other session-wide
|
|
41
50
|
toggles — are decided once for the machine, not per project, so writing one in a
|
|
@@ -123,11 +123,6 @@ the `dispatch.runnerTimeoutFloorMs` config key, which wins when both are set.
|
|
|
123
123
|
|
|
124
124
|
## Install control
|
|
125
125
|
|
|
126
|
-
### `PI_LENS_AUTO_INSTALL`
|
|
127
|
-
|
|
128
|
-
Set to `1` to auto-approve tool installs non-interactively (same as
|
|
129
|
-
`--auto-install`). Off by default — installs prompt interactively.
|
|
130
|
-
|
|
131
126
|
### `PI_LENS_DISABLE_LSP_INSTALL`
|
|
132
127
|
|
|
133
128
|
Set to `1` to skip auto-installing language servers. Off by default.
|
|
@@ -12,20 +12,20 @@ pi-lens includes **45 language server definitions** (including four cross-cuttin
|
|
|
12
12
|
{ "warmFiles": ["src/main.cpp", "src/lib.cpp"] }
|
|
13
13
|
```
|
|
14
14
|
|
|
15
|
-
**Agent LSP tools:** `
|
|
15
|
+
**Agent LSP tools:** `lens_diagnostics` with `source=lsp` can check one file, a directory, or explicit paths with bounded concurrency. `lsp_navigation` provides definitions, references, hover, workspace symbols, call hierarchy, rename edits, and `findSymbol` for filtered document-symbol lookup. Key operations:
|
|
16
16
|
|
|
17
17
|
- **`rename`** — renames a symbol across all references; `apply: true` writes workspace edits to disk with per-file LSP re-sync.
|
|
18
18
|
- **`rename_file`** — LSP-aware file rename: sends `workspace/willRenameFiles` to collect import-path rewrites, applies them, renames the file on disk, and notifies servers via `workspace/didRenameFiles`. `apply: false` previews the workspace edits without touching the filesystem.
|
|
19
19
|
- **`capabilities`** — shows which operations are supported by the active LSP server(s) for a file, read directly from the cached `initialize` response (no round-trip).
|
|
20
20
|
- **Symbol column resolution** — passing `symbol: "myFunc"` instead of an exact `character` position resolves the correct column automatically. Use `symbol: "foo#2"` for the second occurrence of `foo` on the line.
|
|
21
21
|
|
|
22
|
-
LSP servers for: TypeScript, Deno, Python (pyright/basedpyright + jedi), Go, Rust, Ruby (ruby-lsp + solargraph), PHP, C# (omnisharp), F#, Java (JDT LS, with Lombok javaagent support when a Lombok jar is available), Kotlin, Swift, Dart, Lua, C/C++, Zig, Haskell, Elixir, Gleam, OCaml, Clojure, CUE (syntax and parse diagnostics; evaluation errors via the cue-vet auxiliary runner), Terraform, Nix, Bash, Docker, YAML, JSON, HTML, TOML, Prisma, Vue, Svelte, CSS.
|
|
22
|
+
LSP servers for: TypeScript, Deno, Python (pyright/basedpyright + jedi), Go, Rust, Ruby (ruby-lsp + solargraph), PHP, PowerShell, C# (omnisharp), F#, Java (JDT LS, with Lombok javaagent support when a Lombok jar is available), Kotlin, Swift, Dart, Lua, C/C++, Zig, Haskell, Elixir, Gleam, Markdown (marksman), OCaml, Clojure, CUE (syntax and parse diagnostics; evaluation errors via the cue-vet auxiliary runner), Terraform, Nix, Bash, Fish, CMake, Docker, YAML, JSON, HTML, TOML, Prisma, Vue, Svelte, CSS.
|
|
23
23
|
|
|
24
24
|
### Formatters
|
|
25
25
|
|
|
26
26
|
pi-lens auto-detects and runs **34 formatters** based on project config:
|
|
27
27
|
|
|
28
|
-
biome, prettier, oxfmt, ruff, black, sqlfluff, gofmt, rustfmt, zig fmt, dart format, shfmt, nixfmt, mix format, ocamlformat, clang-format, ktlint, rubocop, standardrb, gleam format, terraform fmt, php-cs-fixer, csharpier, fantomas, swiftformat, stylua, ormolu, taplo,
|
|
28
|
+
biome, prettier, oxfmt, ruff, black, sqlfluff, gofmt, rustfmt, zig fmt, dart format, shfmt, nixfmt, mix format, ocamlformat, clang-format, ktlint, ktfmt, rubocop, standardrb, gleam format, terraform fmt, terragrunt-hcl, php-cs-fixer, csharpier, fantomas, swiftformat, stylua, ormolu, taplo, google-java-format, cljfmt, cmake-format, cue fmt, psscriptanalyzer-format
|
|
29
29
|
|
|
30
30
|
Detection rules:
|
|
31
31
|
|
|
@@ -310,7 +310,7 @@ Covers JavaScript/TypeScript, Python, Go, Rust, Ruby, Shell, and CMake. A TypeSc
|
|
|
310
310
|
- **Strictness modes** — `strictness: "relaxed"` ignores optional punctuation (trailing commas, semicolons) that causes zero matches in `smart` mode. Also supports `ast`, `cst`, `signature`, `template`.
|
|
311
311
|
- **Pagination** — `skip: N` offsets into large result sets; truncated results include a next-page hint.
|
|
312
312
|
- **Stale-preview detection** — `ast_grep_replace` re-validates the pattern before writing; returns a clear error if files changed since the preview instead of applying against wrong content.
|
|
313
|
-
- **`
|
|
313
|
+
- **`ast_grep_search` with `dump=true`** — dumps the full tree-sitter AST for a source snippet. Use this when a pattern returns zero matches and the correct node kind or field name is unknown.
|
|
314
314
|
|
|
315
315
|
### Tree-sitter Rules
|
|
316
316
|
|
|
@@ -397,7 +397,7 @@ pi-lens ships an MCP (Model Context Protocol) server so Claude Code — or any M
|
|
|
397
397
|
|
|
398
398
|
| Layer | MCP tools | What they expose |
|
|
399
399
|
|---|---|---|
|
|
400
|
-
| **Per-edit** | `pilens_analyze`, `
|
|
400
|
+
| **Per-edit** | `pilens_analyze`, `pilens_lsp_navigation`, `pilens_ast_grep_search`, `pilens_ast_grep_replace`, `pilens_module_report`, `pilens_read_symbol`, `pilens_read_enclosing` | The fast pipeline (format → autofix → LSP diagnostics → parallel runners) plus the structured read-substitute pair. `analyze` accepts `mode: warm \| fresh` — `warm` reuses the server's in-process LSP, `fresh` forks a worker that loads freshly-built code from disk so the result reflects the latest commit. |
|
|
401
401
|
| **Per-turn** | `pilens_turn_end` | Drives the **real** `handleTurnEnd` (knip incremental, dep-circular, cascade, tests, actionable+code-quality warnings) — not a re-implementation. Caller-supplied edited files are auto-registered into turn-state via `addModifiedRange`. |
|
|
402
402
|
| **Per-session** | `pilens_session_start` | Drives the **real** `handleSessionStart` — full jscpd/knip/madge/govulncheck/gitleaks/trivy scans + complexity baselines + LSP warm. The error-debt baseline is not currently populated by the production session-start path. |
|
|
403
403
|
| **Project / observability** | `pilens_project_scan`, `pilens_project_report`, `pilens_diagnostics`, `pilens_health`, `pilens_latency`, `pilens_symbol_search`, `pilens_effective_config` | Cheap project-wide scans, cached diagnostic state, latency telemetry, ranked identifier search (BM25 over the persisted word index — see [docs/word-index.md](word-index.md)). Cross-file blast radius now lives in `pilens_module_report`'s `blastRadius` option. `pilens_health` (and its pi-side `/lens-health` counterpart) also reports a bounded, process-local **degradation ledger** — trust refusals, mode suppressions, LSP breaker trips, formatter skips/failures, TypeScript/word-index/review-graph/project-snapshot idle evictions, WASM aborts, and diagnostics-timeout tallies — so silently degraded behavior stays visible instead of vanishing into a log. `pilens_effective_config` answers **“why is X running / why is X not running”** from one query — the resolved configuration with the provenance of every setting, and for a file you name, its language plus every LSP server with the reason it was selected or denied and the runners that would dispatch. It reports sources, never values. |
|
|
@@ -18,6 +18,7 @@ Each runtime toggle is settable from the CLI *and* from `config.json`. The two a
|
|
|
18
18
|
| `--no-autofix` | `autofix.enabled` | `true` |
|
|
19
19
|
| `--no-lens-context` | `contextInjection.enabled` | `true` |
|
|
20
20
|
| `--lens-guard` | `guard.enabled` | `false` |
|
|
21
|
+
| `--lens-checkout-guard` | `guard.sharedCheckout` | `false` |
|
|
21
22
|
| `--immediate-format` | `format.mode` (`"immediate"`) | `"deferred"` |
|
|
22
23
|
| `--lens-turn-summary` | `turnSummary.enabled` | `false` |
|
|
23
24
|
| `--lens-actionable-warnings` | `actionableWarnings.enabled` | `false` |
|
|
@@ -42,7 +43,7 @@ Each runtime toggle is settable from the CLI *and* from `config.json`. The two a
|
|
|
42
43
|
| `startup.mode` | `full` | `quick`, `full`, or `minimal`; `PI_LENS_STARTUP_MODE` wins |
|
|
43
44
|
| `startup.scans.enabled` | `true` | `true` or `false` |
|
|
44
45
|
|
|
45
|
-
By default pi-lens registers
|
|
46
|
+
By default pi-lens registers five situational tools (the `ast_grep_*` family,
|
|
46
47
|
`lsp_navigation`, `lens_diagnostic_mark`) inactive and exposes a small loader,
|
|
47
48
|
`pi_lens_activate_tools`, that the model calls to activate the ones it needs.
|
|
48
49
|
`--no-lazy-tools` turns that off: every pi-lens tool is active from the first
|
|
@@ -123,6 +124,15 @@ Turn subsystems off globally instead of retyping flags every session:
|
|
|
123
124
|
|
|
124
125
|
`lens.enabled: false` starts every session with pi-lens off (the `--no-lens` equivalent); `/lens-toggle` still re-enables it for one session. `lsp.enabled: false` falls back to language-specific checkers such as pyright. `tests.enabled: false` skips the on-write test runner. `delta.enabled: false` reports every diagnostic rather than only ones introduced this turn. `opengrep.enabled: false` detaches the Opengrep security scanner. `readGuard.enabled: false` turns off the read-before-edit monitor. `guard.enabled: true` opts into the experimental commit/push blocker.
|
|
125
126
|
|
|
127
|
+
The `tools.<name>.enabled` setting controls each model-facing tool. Valid names
|
|
128
|
+
include `ast_grep_search`, `ast_grep_replace`, `ast_grep_outline`,
|
|
129
|
+
`lsp_navigation`, `lens_diagnostics`,
|
|
130
|
+
`lens_diagnostic_mark`, `symbol_search`, `module_report`, `project_report`,
|
|
131
|
+
`read_symbol`, `read_enclosing`, `effective_config`, `analyze`, `health`,
|
|
132
|
+
`latency`, `project_scan`, and `rebuild`. The activation loader and MCP
|
|
133
|
+
lifecycle tools `session_start`, `turn_end`, and `session_end` cannot be
|
|
134
|
+
disabled.
|
|
135
|
+
|
|
126
136
|
## Project Config
|
|
127
137
|
|
|
128
138
|
In addition to the user-level `~/.pi-lens/config.json` above, pi-lens reads a per-project `.pi-lens.json` (or `pi-lens.json`) at the project root. Walked upward from the cwd, so a monorepo can keep the config at the repo root and have every subdir pick it up. The schema is intentionally small — only fields pi-lens actually honors:
|
|
@@ -113,11 +113,23 @@ New files:
|
|
|
113
113
|
| tool | maps to | purpose |
|
|
114
114
|
|------|---------|---------|
|
|
115
115
|
| `pilens_analyze` | `analyzeFile` (warm) / worker (fresh) | run the per-edit pipeline on a file; returns diagnostics + timing. `mode: warm\|fresh`. **Correctness + mechanism probe.** |
|
|
116
|
-
| `
|
|
116
|
+
| `pilens_ast_grep_search` | `ast_grep_search` | AST-aware structural search. |
|
|
117
|
+
| `pilens_ast_grep_replace` | `ast_grep_replace` | AST-aware structural replace. |
|
|
118
|
+
| `pilens_diagnostics` | `lens_diagnostics` | session or LSP diagnostics, selected by `source`, `scope`, and `severity`. |
|
|
119
|
+
| `pilens_effective_config` | `effective_config` | resolved configuration and provenance. |
|
|
117
120
|
| `pilens_project_scan` | `scanProjectDiagnostics` | project-wide cheap scan. |
|
|
118
121
|
| `pilens_latency` | `getLatencyReports` | latency records (latency.log schema). **Review-loop measurement.** |
|
|
119
122
|
| `pilens_health` | `/lens-health` internals | runtime health snapshot. |
|
|
123
|
+
| `pilens_lsp_navigation` | `lsp_navigation` | IDE-style LSP navigation. |
|
|
124
|
+
| `pilens_module_report` | `module_report` | navigable module outline and dependents. |
|
|
125
|
+
| `pilens_project_report` | `project_report` | project-level structure and diagnostics report. |
|
|
126
|
+
| `pilens_read_enclosing` | `read_enclosing` | read the smallest enclosing symbol or callback. |
|
|
127
|
+
| `pilens_read_symbol` | `read_symbol` | read one symbol body. |
|
|
120
128
|
| `pilens_rebuild` | `npm run build:dist` | rebuild dist so `fresh` reflects the latest commit. Makes the loop self-contained. |
|
|
129
|
+
| `pilens_session_end` | session lifecycle | close the MCP session and flush lifecycle state. |
|
|
130
|
+
| `pilens_session_start` | session lifecycle | initialize session state and caches. |
|
|
131
|
+
| `pilens_symbol_search` | `symbol_search` | search the persisted symbol index. |
|
|
132
|
+
| `pilens_turn_end` | turn lifecycle | settle turn-end work and return findings. |
|
|
121
133
|
|
|
122
134
|
## Packaging / wiring
|
|
123
135
|
|