@herbertgao/pi-extensions 2026.9.1 → 2026.9.2

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.
Files changed (166) hide show
  1. package/node_modules/@narumitw/pi-btw/README.md +57 -141
  2. package/node_modules/@narumitw/pi-btw/dist/index.ts +466 -41
  3. package/node_modules/@narumitw/pi-btw/dist/index.ts.map +4 -4
  4. package/node_modules/@narumitw/pi-btw/docs/workflows.md +74 -0
  5. package/node_modules/@narumitw/pi-btw/package.json +2 -1
  6. package/node_modules/@narumitw/pi-btw/src/btw.ts +15 -1
  7. package/node_modules/@narumitw/pi-btw/src/fullscreen-ui.ts +32 -2
  8. package/node_modules/@narumitw/pi-btw/src/keybindings.ts +330 -0
  9. package/node_modules/@narumitw/pi-btw/src/menu.ts +136 -19
  10. package/node_modules/@narumitw/pi-btw/src/settings.ts +32 -0
  11. package/node_modules/@narumitw/pi-btw/src/side-thread.ts +57 -5
  12. package/node_modules/@narumitw/pi-btw/src/transcript-pager.ts +52 -30
  13. package/node_modules/pi-lens/CHANGELOG.md +84 -0
  14. package/node_modules/pi-lens/dist/clients/actionable-warnings-logger.js +0 -7
  15. package/node_modules/pi-lens/dist/clients/advisory-provenance.js +1 -1
  16. package/node_modules/pi-lens/dist/clients/ast-grep-client.js +1 -1
  17. package/node_modules/pi-lens/dist/clients/ast-grep-tool-logger.js +0 -7
  18. package/node_modules/pi-lens/dist/clients/ast-grep-types.js +2 -0
  19. package/node_modules/pi-lens/dist/clients/bash-file-access.js +133 -1
  20. package/node_modules/pi-lens/dist/clients/blocker-freshness.js +0 -6
  21. package/node_modules/pi-lens/dist/clients/bootstrap.js +4 -4
  22. package/node_modules/pi-lens/dist/clients/bounded-pid-file-lock.js +1 -1
  23. package/node_modules/pi-lens/dist/clients/bounded-telemetry.js +0 -5
  24. package/node_modules/pi-lens/dist/clients/bus-events-logger.js +1 -9
  25. package/node_modules/pi-lens/dist/clients/cache-observability.js +1 -6
  26. package/node_modules/pi-lens/dist/clients/cargo-manifest.js +2 -2
  27. package/node_modules/pi-lens/dist/clients/cascade-logger.js +0 -7
  28. package/node_modules/pi-lens/dist/clients/child-unref.js +1 -19
  29. package/node_modules/pi-lens/dist/clients/config-core/index.js +11 -8
  30. package/node_modules/pi-lens/dist/clients/config-core/process-spec.js +1 -1
  31. package/node_modules/pi-lens/dist/clients/config-core/provenance.js +2 -9
  32. package/node_modules/pi-lens/dist/clients/config-core/records.js +1 -1
  33. package/node_modules/pi-lens/dist/clients/config-core/schema.js +3 -5
  34. package/node_modules/pi-lens/dist/clients/config-diagnostic-codes.js +1 -1
  35. package/node_modules/pi-lens/dist/clients/config-resolve.js +4 -4
  36. package/node_modules/pi-lens/dist/clients/config-schema.js +39 -2
  37. package/node_modules/pi-lens/dist/clients/dead-code-logger.js +0 -4
  38. package/node_modules/pi-lens/dist/clients/diagnostic-dispositions.js +1 -1
  39. package/node_modules/pi-lens/dist/clients/diagnostic-logger.js +1 -1
  40. package/node_modules/pi-lens/dist/clients/dispatch/collect-later-tier.js +0 -4
  41. package/node_modules/pi-lens/dist/clients/dispatch/dispatcher.js +0 -26
  42. package/node_modules/pi-lens/dist/clients/dispatch/integration.js +18 -1
  43. package/node_modules/pi-lens/dist/clients/dispatch/lazy.js +0 -6
  44. package/node_modules/pi-lens/dist/clients/dispatch/pending-runner-findings.js +1 -1
  45. package/node_modules/pi-lens/dist/clients/dispatch/plan.js +0 -3
  46. package/node_modules/pi-lens/dist/clients/dispatch/rules/high-complexity.js +2 -2
  47. package/node_modules/pi-lens/dist/clients/dispatch/rules/high-fan-out.js +1 -1
  48. package/node_modules/pi-lens/dist/clients/dispatch/runners/ast-grep-napi.js +3 -1
  49. package/node_modules/pi-lens/dist/clients/dispatch/runners/cpp-check.js +4 -0
  50. package/node_modules/pi-lens/dist/clients/dispatch/runners/helm-render.js +1 -1
  51. package/node_modules/pi-lens/dist/clients/dispatch/runners/oxlint.js +1 -0
  52. package/node_modules/pi-lens/dist/clients/dispatch/runners/psscriptanalyzer.js +6 -2
  53. package/node_modules/pi-lens/dist/clients/dispatch/runners/ruff.js +1 -1
  54. package/node_modules/pi-lens/dist/clients/dispatch/runners/shellcheck.js +1 -1
  55. package/node_modules/pi-lens/dist/clients/dispatch/runners/spellcheck.js +4 -2
  56. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/diagnostic-parsers.js +1 -64
  57. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils.js +0 -21
  58. package/node_modules/pi-lens/dist/clients/dispatch/runners/yaml-rule-parser.js +2 -20
  59. package/node_modules/pi-lens/dist/clients/dispatch/runners/yamllint.js +1 -0
  60. package/node_modules/pi-lens/dist/clients/dispatch/utils/format-utils.js +2 -2
  61. package/node_modules/pi-lens/dist/clients/disposition-logger.js +0 -7
  62. package/node_modules/pi-lens/dist/clients/event-loop-monitor.js +2 -2
  63. package/node_modules/pi-lens/dist/clients/file-kinds.js +1 -38
  64. package/node_modules/pi-lens/dist/clients/file-time.js +1 -1
  65. package/node_modules/pi-lens/dist/clients/file-utils.js +5 -1
  66. package/node_modules/pi-lens/dist/clients/format-service.js +0 -1
  67. package/node_modules/pi-lens/dist/clients/formatters.js +110 -3
  68. package/node_modules/pi-lens/dist/clients/generated-artifacts.js +3 -4
  69. package/node_modules/pi-lens/dist/clients/git-guard.js +5 -0
  70. package/node_modules/pi-lens/dist/clients/grammar-source.js +3 -3
  71. package/node_modules/pi-lens/dist/clients/installer/index.js +220 -9
  72. package/node_modules/pi-lens/dist/clients/installer/managed-tool-refresh.js +84 -18
  73. package/node_modules/pi-lens/dist/clients/instance-reaper.js +6 -6
  74. package/node_modules/pi-lens/dist/clients/language-profile.js +0 -9
  75. package/node_modules/pi-lens/dist/clients/language-registry.js +5 -1
  76. package/node_modules/pi-lens/dist/clients/latency-logger.js +5 -7
  77. package/node_modules/pi-lens/dist/clients/ledger-bounds.js +21 -1
  78. package/node_modules/pi-lens/dist/clients/lens-config.js +19 -0
  79. package/node_modules/pi-lens/dist/clients/lens-engine.js +3 -4
  80. package/node_modules/pi-lens/dist/clients/lens-events.js +1 -1
  81. package/node_modules/pi-lens/dist/clients/lens-flag-registry.js +18 -0
  82. package/node_modules/pi-lens/dist/clients/lens-map.js +1 -1
  83. package/node_modules/pi-lens/dist/clients/log-cleanup.js +0 -36
  84. package/node_modules/pi-lens/dist/clients/lsp/client.js +4 -3
  85. package/node_modules/pi-lens/dist/clients/lsp/config.js +1 -0
  86. package/node_modules/pi-lens/dist/clients/lsp/document-drift.js +1 -1
  87. package/node_modules/pi-lens/dist/clients/lsp/edits.js +3 -3
  88. package/node_modules/pi-lens/dist/clients/lsp/index.js +216 -25
  89. package/node_modules/pi-lens/dist/clients/lsp/inferred-project.js +1 -1
  90. package/node_modules/pi-lens/dist/clients/lsp/language.js +0 -8
  91. package/node_modules/pi-lens/dist/clients/lsp/path-utils.js +1 -1
  92. package/node_modules/pi-lens/dist/clients/lsp/pending-aux-coverage.js +0 -2
  93. package/node_modules/pi-lens/dist/clients/lsp/sync-kind.js +0 -1
  94. package/node_modules/pi-lens/dist/clients/lsp/tsserver-sync.js +5 -5
  95. package/node_modules/pi-lens/dist/clients/lsp/wait-policy/classification.js +6 -0
  96. package/node_modules/pi-lens/dist/clients/lsp/wait-policy/strategies.js +2 -1
  97. package/node_modules/pi-lens/dist/clients/lsp-document-symbols.js +1 -1
  98. package/node_modules/pi-lens/dist/clients/metrics-history.js +28 -107
  99. package/node_modules/pi-lens/dist/clients/middle-man-analysis.js +1 -1
  100. package/node_modules/pi-lens/dist/clients/module-report.js +1 -1
  101. package/node_modules/pi-lens/dist/clients/mutating-tool.js +3 -3
  102. package/node_modules/pi-lens/dist/clients/mutation-attribution.js +3 -3
  103. package/node_modules/pi-lens/dist/clients/ndjson-logger.js +0 -4
  104. package/node_modules/pi-lens/dist/clients/observed-mutation.js +9 -9
  105. package/node_modules/pi-lens/dist/clients/opaque-mutation-scan.js +2 -6
  106. package/node_modules/pi-lens/dist/clients/opengrep-config.js +1 -1
  107. package/node_modules/pi-lens/dist/clients/path-utils.js +23 -2
  108. package/node_modules/pi-lens/dist/clients/performance-report.js +2 -2
  109. package/node_modules/pi-lens/dist/clients/php-cs-fixer-config.js +1 -1
  110. package/node_modules/pi-lens/dist/clients/pipeline.js +2 -2
  111. package/node_modules/pi-lens/dist/clients/project-changes.js +1 -1
  112. package/node_modules/pi-lens/dist/clients/project-diagnostics/extractors.js +1 -1
  113. package/node_modules/pi-lens/dist/clients/project-diagnostics/runner-adapters/jscpd.js +1 -1
  114. package/node_modules/pi-lens/dist/clients/project-diagnostics/runner-adapters/knip.js +1 -1
  115. package/node_modules/pi-lens/dist/clients/project-diagnostics/runner-adapters/madge.js +1 -1
  116. package/node_modules/pi-lens/dist/clients/project-diagnostics/runner-adapters/opengrep.js +1 -1
  117. package/node_modules/pi-lens/dist/clients/project-diagnostics/runner-adapters/runner-findings.js +1 -1
  118. package/node_modules/pi-lens/dist/clients/project-lens-config.js +46 -1
  119. package/node_modules/pi-lens/dist/clients/project-scan-policy.js +3 -51
  120. package/node_modules/pi-lens/dist/clients/python-provenance.js +1 -1
  121. package/node_modules/pi-lens/dist/clients/read-guard-logger.js +1 -1
  122. package/node_modules/pi-lens/dist/clients/read-guard.js +5 -2
  123. package/node_modules/pi-lens/dist/clients/recent-touches.js +2 -2
  124. package/node_modules/pi-lens/dist/clients/review-graph/builder.js +3 -5
  125. package/node_modules/pi-lens/dist/clients/review-graph/import-resolvers.js +1 -1
  126. package/node_modules/pi-lens/dist/clients/runtime-agent-end.js +1 -1
  127. package/node_modules/pi-lens/dist/clients/runtime-session.js +45 -10
  128. package/node_modules/pi-lens/dist/clients/runtime-tool-call.js +11 -1
  129. package/node_modules/pi-lens/dist/clients/sanitize.js +0 -11
  130. package/node_modules/pi-lens/dist/clients/scan-utils.js +1 -56
  131. package/node_modules/pi-lens/dist/clients/session-summary.js +0 -24
  132. package/node_modules/pi-lens/dist/clients/sgconfig.js +1 -5
  133. package/node_modules/pi-lens/dist/clients/shared-checkout-guard.js +1 -1
  134. package/node_modules/pi-lens/dist/clients/smells-rollup.js +2 -2
  135. package/node_modules/pi-lens/dist/clients/startup-scan.js +2 -10
  136. package/node_modules/pi-lens/dist/clients/test-runner-delivery.js +77 -72
  137. package/node_modules/pi-lens/dist/clients/tool-policy.js +1 -1
  138. package/node_modules/pi-lens/dist/clients/tree-sitter-client.js +1 -52
  139. package/node_modules/pi-lens/dist/clients/tui-fit.js +0 -4
  140. package/node_modules/pi-lens/dist/clients/user-notify.js +0 -4
  141. package/node_modules/pi-lens/dist/clients/word-index-logger.js +0 -7
  142. package/node_modules/pi-lens/dist/clients/word-index-store.js +3 -7
  143. package/node_modules/pi-lens/dist/clients/word-index.js +9 -16
  144. package/node_modules/pi-lens/dist/clients/zizmor-config.js +1 -1
  145. package/node_modules/pi-lens/dist/index.js +1062 -538
  146. package/node_modules/pi-lens/dist/mcp/build-staleness.js +1 -1
  147. package/node_modules/pi-lens/dist/scripts/lib/process-scan.mjs +1 -1
  148. package/node_modules/pi-lens/dist/scripts/lib/skills-predicate.mjs +1 -1
  149. package/node_modules/pi-lens/dist/tools/lens-diagnostics.js +1 -1
  150. package/node_modules/pi-lens/dist/tools/lsp-diagnostics.js +37 -6
  151. package/node_modules/pi-lens/dist/tools/lsp-structured-output.js +3 -3
  152. package/node_modules/pi-lens/dist/tools/render-compact.js +1 -1
  153. package/node_modules/pi-lens/dist/tools/symbol-search.js +1 -2
  154. package/node_modules/pi-lens/docs/globalconfig.md +20 -0
  155. package/node_modules/pi-lens/docs/lsp-capability-matrix.md +9 -1
  156. package/node_modules/pi-lens/docs/pi-lens-reviewer.md +6 -0
  157. package/node_modules/pi-lens/docs/pi-lens-subagent.md +12 -1
  158. package/node_modules/pi-lens/docs/release-qa-baseline.md +12 -4
  159. package/node_modules/pi-lens/docs/servercapabilities.md +7 -3
  160. package/node_modules/pi-lens/docs/word-index.md +1 -1
  161. package/node_modules/pi-lens/package.json +6 -15
  162. package/node_modules/pi-lens/rules/typos/_typos.toml +4 -2
  163. package/node_modules/pi-lens/scripts/lib/skills-predicate.mjs +1 -1
  164. package/node_modules/pi-lens/scripts/lib/warm-loader-cache.mjs +1 -1
  165. package/node_modules/pi-lens/scripts/warm-loader-cache.mjs +4 -2
  166. package/package.json +3 -3
@@ -37,7 +37,7 @@ import { getProjectDataDir } from "./file-utils.js";
37
37
  import { normalizeFilePath } from "./path-utils.js";
38
38
  export const RECENT_TOUCHES_MAX_ENTRIES = 50;
39
39
  /** Freshness window for the CHILD session_start consumer (15 minutes). */
40
- export const RECENT_TOUCHES_FRESHNESS_MS = 15 * 60 * 1000;
40
+ const RECENT_TOUCHES_FRESHNESS_MS = 15 * 60 * 1000;
41
41
  function recentTouchesPath(cwd) {
42
42
  return path.join(getProjectDataDir(cwd), "recent-touches.json");
43
43
  }
@@ -46,7 +46,7 @@ function recentTouchesPath(cwd) {
46
46
  // INTO the same nudge pipeline, so one switch governs the whole path
47
47
  // (producer, both consumers) as specified in the issue.
48
48
  let _enabledCache;
49
- export function isRecentTouchesEnabled() {
49
+ function isRecentTouchesEnabled() {
50
50
  if (_enabledCache === undefined) {
51
51
  _enabledCache = process.env.PI_LENS_AGENT_NUDGE !== "0";
52
52
  }
@@ -92,8 +92,6 @@ const MAIN_KINDS = new Set([
92
92
  // so a repo heavy in JSON/YAML/Markdown doesn't trip the cap on files the graph
93
93
  // would have filtered out anyway (the cap is on the walk, not on noise). #250.
94
94
  const MAIN_KIND_EXTENSIONS = Array.from(MAIN_KINDS).flatMap((kind) => KIND_EXTENSIONS[kind] ?? []);
95
- /** The bounded, source-filtered extension set shared by graph cache readers. */
96
- export const REVIEW_GRAPH_SOURCE_EXTENSIONS = MAIN_KIND_EXTENSIONS;
97
95
  const CHANGED_SYMBOLS_PREFIX = "session.reviewGraph.changedSymbols:";
98
96
  const extractorCache = new Map();
99
97
  const REVIEW_GRAPH_MAX_WARM_WORKSPACES = 8;
@@ -407,7 +405,7 @@ export function estimateReviewGraphStoreBytes(totalNodes, totalEdges) {
407
405
  // cleared). Bounding only the cache left the full graph resident on the fact, so
408
406
  // both sites go through `retainedGraph` below, memoized per graph instance so the
409
407
  // two sites share ONE bounded object instead of trimming twice (#2255 review F2).
410
- export const GRAPH_MAX_IN_MEMORY_BYTES_DEFAULT = 512 * 1024 * 1024;
408
+ const GRAPH_MAX_IN_MEMORY_BYTES_DEFAULT = 512 * 1024 * 1024;
411
409
  function graphMaxInMemoryBytes() {
412
410
  const raw = Number(process.env.PI_LENS_GRAPH_MAX_IN_MEMORY_BYTES);
413
411
  return Number.isFinite(raw) && raw > 0
@@ -967,7 +965,7 @@ function diffSignatureMaps(previous, next) {
967
965
  // `RUNTIME_CONFIG.reviewGraph.maxFiles` constant as the fallback — the ratio
968
966
  // table reproduces that same 1,000-file default at the default base, so this
969
967
  // is behavior-neutral when nothing is configured.
970
- export function getReviewGraphMaxFiles(cwd) {
968
+ function getReviewGraphMaxFiles(cwd) {
971
969
  const override = Number.parseInt(process.env.PI_LENS_REVIEW_GRAPH_MAX_FILES ?? "", 10);
972
970
  return Number.isFinite(override) && override > 0
973
971
  ? override
@@ -3481,7 +3479,7 @@ function addTreeSitterFile(graph, cwd, filePath, languageId, extracted, ignoredI
3481
3479
  * SymbolInformation results (including native TypeScript 7) recover the same
3482
3480
  * containment through `containerName` when the owner is present in the result.
3483
3481
  */
3484
- export function addLspFallbackSymbols(graph, filePath, languageId, symbols) {
3482
+ function addLspFallbackSymbols(graph, filePath, languageId, symbols) {
3485
3483
  const normalized = normalizeMapKey(filePath);
3486
3484
  const fileNodeId = `file:${normalized}`;
3487
3485
  let added = 0;
@@ -90,7 +90,7 @@ function resolveDart(cwd, filePath, source) {
90
90
  // JS/TS extension from the specifier before re-appending candidate extensions
91
91
  // lets that universal pattern resolve to the real source file. Exported so
92
92
  // builder.ts's warm `localImportToFile` shares the exact same regex (#694).
93
- export const JS_TS_EXT_RE = /\.(mjs|cjs|mts|cts|jsx?|tsx?)$/i;
93
+ const JS_TS_EXT_RE = /\.(mjs|cjs|mts|cts|jsx?|tsx?)$/i;
94
94
  /**
95
95
  * Ordered candidate list for resolving a relative JS/TS import specifier
96
96
  * (already resolved to an absolute base path) to an on-disk file, with
@@ -19,7 +19,7 @@ import { getAutofixPolicyForFile, hasBiomeConfig, hasDetektConfig, hasEslintConf
19
19
  * path at all (they match on `ownerSessionId`), so this only trades a little
20
20
  * extra staleness for guaranteed eventual formatting. (#791)
21
21
  */
22
- export const DEFERRED_FORMAT_STALE_AFTER_MS = 10 * 60_000;
22
+ const DEFERRED_FORMAT_STALE_AFTER_MS = 10 * 60_000;
23
23
  const DEFERRED_FORMAT_CONCURRENCY = 3;
24
24
  function recordProjectChange(args) {
25
25
  // One mutation seam (#2000 phase 1): bump + receipt + change-log live in
@@ -5,7 +5,7 @@ import { rotateMessageEndAttribution } from "./message-end-attribution.js";
5
5
  import { createDeadline, yieldIfOverBudget } from "./cooperative-budget.js";
6
6
  import { deadCodeIssueCount } from "./dead-code-client.js";
7
7
  import { logDeadCodeScan } from "./dead-code-logger.js";
8
- import { incrementDegradationCount, resetDegradationLedger, } from "./degradation-ledger.js";
8
+ import { incrementDegradationCount, recordDegradationOnce, resetDegradationLedger, } from "./degradation-ledger.js";
9
9
  import { getDiagnosticTracker } from "./diagnostic-tracker.js";
10
10
  import { resetPsScriptAnalyzerAvailability } from "./dispatch/runners/psscriptanalyzer.js";
11
11
  import { resetInstallRetryLatches } from "./dispatch/runners/utils/availability-policy.js";
@@ -256,11 +256,14 @@ function logProjectSnapshotProbe(args) {
256
256
  args.dbg(`project_snapshot: miss reason=${args.missReason}`);
257
257
  }
258
258
  }
259
- function resolveStartupMode() {
259
+ function resolveStartupMode(projectConfig, globalConfig) {
260
260
  const envMode = (process.env.PI_LENS_STARTUP_MODE ?? "").trim().toLowerCase();
261
261
  if (envMode === "full" || envMode === "minimal" || envMode === "quick") {
262
262
  return envMode;
263
263
  }
264
+ const configMode = projectConfig?.startup?.mode ?? globalConfig?.startup?.mode;
265
+ if (configMode)
266
+ return configMode;
264
267
  if (isPrintMode()) {
265
268
  return "quick";
266
269
  }
@@ -813,6 +816,15 @@ function scheduleStartupScans(deps, runtime, sessionGeneration, analysisRoot, sn
813
816
  /** The scan bodies, once the analyzers they need are resolved. */
814
817
  function scheduleStartupScansWithClients(deps, runtime, sessionGeneration, analysisRoot, snapshotRoot, languageProfile, dbg) {
815
818
  const { todoScanner, cacheManager, knipClient, jscpdClient, deadCodeClients, govulncheckClient, gitleaksClient, trivyClient, opengrepClient, astGrepClient, depChecker, } = deps;
819
+ const analyzerEnabled = (flag) => !deps.getFlag(flag);
820
+ if (!analyzerEnabled("no-complexity")) {
821
+ dbg("session_start complexity: skipped (disabled by config)");
822
+ recordDegradationOnce({
823
+ kind: "startup-analyzer-disabled",
824
+ subject: "complexity",
825
+ reason: "skipped (disabled by config)",
826
+ });
827
+ }
816
828
  // Some background scans are CPU-heavy and arrive on the event loop
817
829
  // just as the user is most likely typing (right after /new). Defer
818
830
  // those by a few seconds so the perceptible 50-100ms sync bursts they
@@ -869,7 +881,7 @@ function scheduleStartupScansWithClients(deps, runtime, sessionGeneration, analy
869
881
  scanNames.push("knip", "jscpd", "ast-grep exports", "project index");
870
882
  }
871
883
  dbg(`session_start: launching background scans (${scanNames.join(", ")})`);
872
- runTask("todo", async () => {
884
+ void runTask("todo", async () => {
873
885
  if (!runtime.isCurrentSession(sessionGeneration))
874
886
  return;
875
887
  // The original implementation called todoScanner.scanDirectory(), which
@@ -906,9 +918,19 @@ function scheduleStartupScansWithClients(deps, runtime, sessionGeneration, analy
906
918
  const isSubagent = isSubagentSession();
907
919
  const skipHeavyweightScans = isSlowFs(analysisRoot) || isSubagent;
908
920
  const runHeavyweightTask = (name, task) => {
921
+ const flag = `no-${name}`;
922
+ if (name !== "opengrep" && name !== "trivy" && !analyzerEnabled(flag)) {
923
+ dbg(`session_start ${name}: skipped (disabled by config)`);
924
+ recordDegradationOnce({
925
+ kind: "startup-analyzer-disabled",
926
+ subject: name,
927
+ reason: "skipped (disabled by config)",
928
+ });
929
+ return;
930
+ }
909
931
  if (skipHeavyweightScans)
910
932
  return;
911
- runTask(name, task);
933
+ void runTask(name, task);
912
934
  };
913
935
  if (skipHeavyweightScans) {
914
936
  dbg(`session_start: skipping knip/jscpd/madge/dead-code/govulncheck/gitleaks/trivy/opengrep (${isSubagent ? "subagent" : "slow-fs"})`);
@@ -1239,7 +1261,7 @@ function scheduleStartupScansWithClients(deps, runtime, sessionGeneration, analy
1239
1261
  // racing its independently deferred timer. Otherwise a slow graph build leaves
1240
1262
  // `runtime.callGraph` unset at this task's start and loses the model for the
1241
1263
  // entire session (#1070).
1242
- runTask("codebase-model", async () => {
1264
+ void runTask("codebase-model", async () => {
1243
1265
  await callGraphTask;
1244
1266
  if (!runtime.isCurrentSession(sessionGeneration))
1245
1267
  return;
@@ -1258,7 +1280,7 @@ function scheduleStartupScansWithClients(deps, runtime, sessionGeneration, analy
1258
1280
  `${model.totalTokens} tokens, top symbols: ${top3 || "(none)"}`);
1259
1281
  });
1260
1282
  // ast-grep — export scan for duplicate detection
1261
- runTask("ast-grep-exports", async () => {
1283
+ void runTask("ast-grep-exports", async () => {
1262
1284
  if (await astGrepClient.ensureAvailable()) {
1263
1285
  if (!runtime.isCurrentSession(sessionGeneration))
1264
1286
  return;
@@ -1275,7 +1297,7 @@ function scheduleStartupScansWithClients(deps, runtime, sessionGeneration, analy
1275
1297
  // word-index — identifier inverted index + BM25 for ranked symbol search
1276
1298
  // (#162). Load -> rebuild-if-stale -> persist lifecycle (#348), shared with
1277
1299
  // the quick-mode cold-start warmup pass below.
1278
- runTask("word-index", async () => {
1300
+ void runTask("word-index", async () => {
1279
1301
  await buildOrRefreshWordIndex({
1280
1302
  runtime,
1281
1303
  sessionGeneration,
@@ -1423,7 +1445,7 @@ export async function handleSessionStart(deps) {
1423
1445
  // entirely — but only when PI_LENS_STARTUP_MODE is unset in the env
1424
1446
  // (an explicit env var still takes highest precedence).
1425
1447
  // Tunable: PI_LENS_WARMUP_DELAY_MS adjusts the warmup delay.
1426
- let startupMode = resolveStartupMode();
1448
+ let startupMode = resolveStartupMode(deps.projectConfig, deps.globalConfig);
1427
1449
  // SAFETY: these two flags are process-lifetime state pi-lens stashes on
1428
1450
  // `globalThis` so a second extension instance in the same process sees the
1429
1451
  // first one's warmup. There is no ambient declaration for them, and adding
@@ -1433,7 +1455,9 @@ export async function handleSessionStart(deps) {
1433
1455
  const isFirstSessionOfProcess = !processGlobals.__piLensFirstSessionDone;
1434
1456
  if (isFirstSessionOfProcess &&
1435
1457
  process.env.PI_LENS_COLD_START_QUICK !== "0" &&
1436
- !process.env.PI_LENS_STARTUP_MODE) {
1458
+ !process.env.PI_LENS_STARTUP_MODE &&
1459
+ !deps.projectConfig?.startup?.mode &&
1460
+ !deps.globalConfig?.startup?.mode) {
1437
1461
  // Apply host-provided override (e.g. MCP server forces "full") before
1438
1462
  // falling back to the TUI quick-mode heuristic.
1439
1463
  startupMode = deps.startupModeOverride ?? "quick";
@@ -2271,7 +2295,10 @@ export async function handleSessionStart(deps) {
2271
2295
  else {
2272
2296
  dbg("session_start: no language defaults selected for pre-install");
2273
2297
  }
2274
- const startupScansWillRun = allowBootstrapTasks && startupScan.canWarmCaches;
2298
+ const startupScansEnabled = deps.projectConfig?.startup?.scans?.enabled ??
2299
+ deps.globalConfig?.startup?.scans?.enabled ??
2300
+ true;
2301
+ const startupScansWillRun = allowBootstrapTasks && startupScan.canWarmCaches && startupScansEnabled;
2275
2302
  const jstsHeavyScansWillRun = startupScansWillRun && canRunStartupHeavyScans(languageProfile, "jsts");
2276
2303
  if (allowBootstrapTasks) {
2277
2304
  scheduleDeferredToolProbes(deps, languageProfile, startupDefaults, jstsHeavyScansWillRun, dbg);
@@ -2326,6 +2353,14 @@ export async function handleSessionStart(deps) {
2326
2353
  if (!allowBootstrapTasks) {
2327
2354
  dbg("session_start: skipping startup background scans (startup mode)");
2328
2355
  }
2356
+ else if (!startupScansEnabled) {
2357
+ dbg("session_start: skipping startup background scans (disabled by config)");
2358
+ recordDegradationOnce({
2359
+ kind: "startup-analyzer-disabled",
2360
+ subject: "startup-scans",
2361
+ reason: "skipped (disabled by config)",
2362
+ });
2363
+ }
2329
2364
  else if (!startupScan.canWarmCaches) {
2330
2365
  dbg(`session_start: skipping heavy scans (${startupScan.reason ?? "unknown"})`);
2331
2366
  dbg(`session_start: skipping TODO scan (${startupScan.reason ?? "unknown"})`);
@@ -773,7 +773,8 @@ async function handleToolCallImpl(deps) {
773
773
  // is the client's own answer, hoisted into `tree-sitter-shared.ts` where it
774
774
  // needs nothing but the extension registry; `ComplexityClient.isSupportedFile`
775
775
  // delegates to it, so this is not a second copy.
776
- if (!isExternalOrVendor &&
776
+ if (!getFlag("no-complexity") &&
777
+ !isExternalOrVendor &&
777
778
  filePath &&
778
779
  !runtime.complexityBaselines.has(filePath) &&
779
780
  isComplexitySupportedFile(filePath)) {
@@ -803,6 +804,15 @@ async function handleToolCallImpl(deps) {
803
804
  });
804
805
  }
805
806
  }
807
+ else if (getFlag("no-complexity") &&
808
+ filePath &&
809
+ isComplexitySupportedFile(filePath)) {
810
+ recordDegradationOnce({
811
+ kind: "startup-analyzer-disabled",
812
+ subject: "complexity",
813
+ reason: "skipped (disabled by config)",
814
+ });
815
+ }
806
816
  // --- Read-Before-Edit Guard: check edits ---
807
817
  // write = full replacement; no prior read needed (you're starting fresh).
808
818
  // edit = partial modification; guard enforced to prevent blind overwrites.
@@ -43,17 +43,6 @@ const DETAIL_PATTERNS = [
43
43
  export function stripAnsi(text) {
44
44
  return text.replace(ANSI_ESCAPE, "").replace(ANSI_ESCAPE_EXTENDED, "");
45
45
  }
46
- /**
47
- * Normalize whitespace in a string.
48
- * Collapses multiple spaces/tabs to single space, trims lines.
49
- */
50
- export function normalizeWhitespace(text) {
51
- return text
52
- .split(/\r?\n/)
53
- .map((line) => line.replace(/\s+/g, " ").trim())
54
- .filter((line) => line.length > 0)
55
- .join("\n");
56
- }
57
46
  /**
58
47
  * Check if a line contains error indicators.
59
48
  */
@@ -1,59 +1,4 @@
1
- import { isExcludedDirName, isTestFile } from "./file-utils.js";
2
- import { collectSourceFiles, collectSourceFilesAsync, isBuildArtifact, } from "./source-filter.js";
3
- /**
4
- * Common parsing logic for ast-grep JSON output (handles both array and NDJSON).
5
- */
6
- // biome-ignore lint/suspicious/noExplicitAny: ast-grep JSON output is untyped
7
- export function parseAstGrepJson(raw) {
8
- if (!raw)
9
- return [];
10
- const trimmed = raw.trim();
11
- if (trimmed.startsWith("[")) {
12
- try {
13
- return JSON.parse(trimmed);
14
- }
15
- catch {
16
- return [];
17
- }
18
- }
19
- return trimmed.split("\n").flatMap((l) => {
20
- try {
21
- return [JSON.parse(l)];
22
- }
23
- catch {
24
- return [];
25
- }
26
- });
27
- }
28
- /**
29
- * Check if a file should be ignored based on project type and common patterns.
30
- *
31
- * @deprecated Use `isBuildArtifact()` from `source-filter.js` instead for artifact
32
- * detection, or compose your own filter using `collectSourceFiles()`. This function
33
- * is kept for backward compatibility.
34
- */
35
- export function shouldIgnoreFile(filePath, isTsProject) {
36
- const relPath = filePath.replace(/\\/g, "/");
37
- // Use new source-filter module for artifact detection
38
- if (isTsProject && isBuildArtifact(filePath))
39
- return true;
40
- // Legacy: simple JS check for non-TS projects (hand-written JS)
41
- const isJs = relPath.endsWith(".js") ||
42
- relPath.endsWith(".mjs") ||
43
- relPath.endsWith(".cjs");
44
- if (isTsProject && isJs)
45
- return true;
46
- // Ignore test scripts and common test patterns
47
- if (isTestFile(filePath))
48
- return true;
49
- // Ignore hidden directories and common build outputs
50
- const pathParts = relPath.split("/").filter(Boolean);
51
- for (const segment of pathParts.slice(0, -1)) {
52
- if (isExcludedDirName(segment))
53
- return true;
54
- }
55
- return false;
56
- }
1
+ import { collectSourceFiles, collectSourceFilesAsync, } from "./source-filter.js";
57
2
  /**
58
3
  * Recursively find source files in a directory, respecting common excludes.
59
4
  *
@@ -1,30 +1,6 @@
1
1
  /**
2
2
  * Session Summary — generates turn_end and session-end summaries
3
3
  */
4
- export function formatSessionSummary(stats) {
5
- if (stats.totalShown === 0)
6
- return "";
7
- const lines = [
8
- "## Diagnostic Summary",
9
- "",
10
- `Shown to agent: ${stats.totalShown}`,
11
- `Auto-fixed: ${stats.totalAutoFixed} (${pct(stats.totalAutoFixed, stats.totalShown)}%)`,
12
- `Fixed by agent: ${stats.totalAgentFixed} (${pct(stats.totalAgentFixed, stats.totalShown)}%)`,
13
- "",
14
- ];
15
- if (stats.topViolations.length > 0) {
16
- lines.push("Top violations:");
17
- for (const { ruleId, count } of stats.topViolations.slice(0, 5)) {
18
- lines.push(` - ${ruleId} (${count}x)`);
19
- }
20
- }
21
- return lines.join("\n");
22
- }
23
- function pct(part, total) {
24
- if (total === 0)
25
- return "0";
26
- return Math.round((part / total) * 100).toString();
27
- }
28
4
  export function formatSlopScoreSummary(summary) {
29
5
  if (summary.totalRuleDiagnostics === 0 || summary.totalKlocWritten <= 0) {
30
6
  return "";
@@ -8,7 +8,7 @@ import { findLocalToolConfig } from "./path-utils.js";
8
8
  // ast-grep's root config marker. The `ast-grep lsp` server is workspace-gated:
9
9
  // it only operates in a project that has an `sgconfig.y[a]ml` at (or above) the
10
10
  // file. These are the names ast-grep itself looks for as root markers.
11
- export const SGCONFIG_NAMES = ["sgconfig.yml", "sgconfig.yaml"];
11
+ const SGCONFIG_NAMES = ["sgconfig.yml", "sgconfig.yaml"];
12
12
  /**
13
13
  * Nearest `sgconfig.y[a]ml` walking up from `startDir`, or undefined if none.
14
14
  * Deliberately UNCEILINGED at $HOME (refs #2472 review round 3, F1): ast-grep
@@ -65,10 +65,6 @@ export function getAstGrepRuleSources(projectRoot = process.cwd()) {
65
65
  return true;
66
66
  });
67
67
  }
68
- /** Compatibility export retained for callers that only need ordered dirs. */
69
- export function shippedRuleDirsInPrecedenceOrder(projectRoot = process.cwd()) {
70
- return getAstGrepRuleSources(projectRoot).map((source) => source.dir);
71
- }
72
68
  function findYamlFiles(dir) {
73
69
  const files = [];
74
70
  let entries;
@@ -115,7 +115,7 @@ function isCleanDryRun(argsAfterVerb) {
115
115
  * replacement for the forbidden `git stash`. Blocking it would push
116
116
  * operators back toward stash.
117
117
  */
118
- export const WORKTREE_MUTATING_GIT_MATCHER = {
118
+ const WORKTREE_MUTATING_GIT_MATCHER = {
119
119
  id: "worktree-mutating",
120
120
  indirectAlwaysMatches: false,
121
121
  // `git checkout --help` opens documentation and changes nothing. This
@@ -58,11 +58,11 @@ import * as path from "node:path";
58
58
  import { getGlobalPiLensLogDir } from "./probe-home-state.js";
59
59
  /** Bounded tail-read budget PER source log file — never a full-file scan. */
60
60
  export const SMELLS_TAIL_BYTES_PER_FILE = 64 * 1024;
61
- export const SMELLS_ROLLING_WINDOW_MS = 24 * 60 * 60_000;
61
+ const SMELLS_ROLLING_WINDOW_MS = 24 * 60 * 60_000;
62
62
  /** Re-check cadence at `turn_end`, mirroring `memory-sampler.ts`'s pattern —
63
63
  * cheap enough (bounded ~128KB I/O) not to need finer throttling, but a
64
64
  * fixed interval keeps it from running on every single turn. */
65
- export const SMELLS_TURN_CHECK_INTERVAL = 20;
65
+ const SMELLS_TURN_CHECK_INTERVAL = 20;
66
66
  /** Trivial threshold constants (#1123 item 3's explicit design ask). Counts
67
67
  * strictly below these never surface anywhere. */
68
68
  export const SMELLS_THRESHOLDS = {
@@ -16,7 +16,7 @@ import { isAtOrAboveHomeDir } from "./path-utils.js";
16
16
  import { getStartupScanMaxSourceFilesDerived } from "./project-scale.js";
17
17
  import { shouldRecurseIntoDir, walkTreeStackAsync, walkTreeStackSync, } from "./source-walker.js";
18
18
  import { getDirectoryMarkers, registerWorkspaceTopologyReset, } from "./workspace-topology.js";
19
- export const PROJECT_ROOT_MARKERS = [
19
+ const PROJECT_ROOT_MARKERS = [
20
20
  ".git",
21
21
  "package.json",
22
22
  "pyproject.toml",
@@ -24,16 +24,8 @@ export const PROJECT_ROOT_MARKERS = [
24
24
  "go.mod",
25
25
  "composer.json",
26
26
  ];
27
- // Deprecated (#776): no longer read directly below — `computeStartupScanContext`
28
- // / `resolveStartupScanContextAsync` now default `maxSourceFiles` to
29
- // `getStartupScanMaxSourceFilesDerived(cwd)` (project-scale.ts's
30
- // `maxProjectFiles` knob), which reproduces this same 2,000 value at the
31
- // default base. Kept exported for tests/callers that still reference the
32
- // literal.
33
- export const MAX_STARTUP_SOURCE_FILES = 2000;
34
27
  // #758: hard ceiling on the number of directory entries the startup source
35
28
  // count walk will visit before it gives up and declares the tree too big to
36
- // warm. The source-file early-exit (MAX_STARTUP_SOURCE_FILES) only fires when
37
29
  // a project has MANY source files — a repo with FEW source files but a huge
38
30
  // pile of non-source files (e.g. a game mod: 300 scripts among 84k data files)
39
31
  // never trips it, so the pre-#758 walk traversed the entire tree, dominated by
@@ -77,7 +69,7 @@ export const _resetStartupScanMaxEntriesForTests = _maxEntries._resetForTests;
77
69
  *
78
70
  * The content-derived reasons `too-many-source-files` and `too-many-entries`
79
71
  * (#758) are the ones that are TTL'd. They can go stale on their own: the repo
80
- * can shrink below `MAX_STARTUP_SOURCE_FILES` (or below the entry ceiling)
72
+ * can shrink below the source-file cap (or below the entry ceiling)
81
73
  * between sessions, and nothing else would notice
82
74
  * — the seq-based freshness check that guards every other
83
75
  * `project-snapshot.json` field never fires for them, because pi-lens never