ultimate-pi 0.18.1 → 0.19.1

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 (325) hide show
  1. package/.agents/skills/harness-debate-plan/SKILL.md +1 -1
  2. package/.agents/skills/harness-decisions/SKILL.md +1 -2
  3. package/.agents/skills/harness-governor/SKILL.md +6 -5
  4. package/.agents/skills/web-retrieval/SKILL.md +163 -0
  5. package/.agents/skills/wiki-autoresearch/SKILL.md +6 -6
  6. package/.pi/PACKAGING.md +4 -4
  7. package/.pi/SYSTEM.md +75 -123
  8. package/.pi/agents/harness/incident-recorder.md +0 -1
  9. package/.pi/agents/harness/planning/decompose.md +0 -2
  10. package/.pi/agents/harness/planning/execution-plan-author.md +0 -2
  11. package/.pi/agents/harness/planning/hypothesis-validator.md +0 -2
  12. package/.pi/agents/harness/planning/hypothesis.md +0 -2
  13. package/.pi/agents/harness/planning/implementation-researcher.md +1 -3
  14. package/.pi/agents/harness/planning/plan-adversary.md +0 -2
  15. package/.pi/agents/harness/planning/plan-evaluator.md +1 -3
  16. package/.pi/agents/harness/planning/planning-context.md +0 -2
  17. package/.pi/agents/harness/planning/review-integrator.md +0 -2
  18. package/.pi/agents/harness/planning/sprint-contract-auditor.md +0 -2
  19. package/.pi/agents/harness/planning/stack-researcher.md +5 -3
  20. package/.pi/agents/harness/reviewing/adversary.md +0 -2
  21. package/.pi/agents/harness/reviewing/evaluator.md +0 -2
  22. package/.pi/agents/harness/reviewing/tie-breaker.md +0 -2
  23. package/.pi/agents/harness/running/executor.md +0 -2
  24. package/.pi/agents/harness/sentrux-bootstrap.md +0 -1
  25. package/.pi/agents/harness/sentrux-steward.md +0 -2
  26. package/.pi/agents/harness/trace-librarian.md +0 -1
  27. package/.pi/agents/harness/web-retrieval/web-answerer.md +35 -0
  28. package/.pi/agents/harness/web-retrieval/web-criteria-verifier.md +28 -0
  29. package/.pi/agents/harness/web-retrieval/web-gap-analyzer.md +31 -0
  30. package/.pi/agents/harness/web-retrieval/web-query-expander-fast.md +34 -0
  31. package/.pi/agents/harness/web-retrieval/web-query-expander.md +60 -0
  32. package/.pi/agents/harness/web-retrieval/web-summarizer.md +18 -0
  33. package/.pi/extensions/agt-kill-switch.ts +57 -0
  34. package/.pi/extensions/agt-prompt-guard.ts +32 -0
  35. package/.pi/extensions/custom-footer.ts +46 -145
  36. package/.pi/extensions/custom-header.ts +1 -1
  37. package/.pi/extensions/custom-system-prompt.ts +1 -1
  38. package/.pi/extensions/debate-orchestrator.ts +6 -6
  39. package/.pi/extensions/harness-ask-user.ts +7 -7
  40. package/.pi/extensions/harness-debate-tools.ts +26 -42
  41. package/.pi/extensions/harness-lens.ts +94 -0
  42. package/.pi/extensions/harness-plan-approval.ts +11 -11
  43. package/.pi/extensions/harness-run-context.ts +1070 -876
  44. package/.pi/extensions/harness-subagent-governance.ts +8 -0
  45. package/.pi/extensions/harness-subagent-submit.ts +34 -163
  46. package/.pi/extensions/harness-subagents.ts +3 -3
  47. package/.pi/extensions/harness-telemetry.ts +2 -2
  48. package/.pi/extensions/harness-web-guard.ts +2 -1
  49. package/.pi/extensions/harness-web-tools.ts +691 -53
  50. package/.pi/extensions/policy-gate.ts +25 -5
  51. package/.pi/extensions/sentrux-rules-sync.ts +1 -1
  52. package/.pi/extensions/subagent-governance.ts +92 -0
  53. package/.pi/extensions/trace-recorder.ts +1 -1
  54. package/.pi/extensions/{ultimate-pi-vcc.ts → vcc-compaction.ts} +1 -1
  55. package/.pi/harness/README.md +6 -2
  56. package/.pi/harness/agents.manifest.json +46 -25
  57. package/.pi/harness/agents.policy.yaml +309 -0
  58. package/.pi/harness/docs/adrs/0030-inhouse-vcc-compaction.md +1 -1
  59. package/.pi/harness/docs/adrs/0035-plan-phase-review-gate.md +1 -1
  60. package/.pi/harness/docs/adrs/0045-harness-lens-minimal-contract.md +49 -0
  61. package/.pi/harness/docs/adrs/0046-agt-policy-engine.md +51 -0
  62. package/.pi/harness/docs/adrs/0047-agt-layered-security.md +39 -0
  63. package/.pi/harness/docs/adrs/0048-tool-call-hook-order.md +25 -0
  64. package/.pi/harness/docs/adrs/0049-agents-policy-manifest.md +36 -0
  65. package/.pi/harness/docs/adrs/0050-agentic-web-retrieval-stack.md +46 -0
  66. package/.pi/harness/docs/adrs/README.md +5 -0
  67. package/.pi/harness/docs/harness-web-search.md +97 -0
  68. package/.pi/harness/env.harness.template +9 -1
  69. package/.pi/harness/evolution/README.md +1 -2
  70. package/.pi/harness/examples/agents.policy.project.yaml +19 -0
  71. package/.pi/harness/examples/policies/custom-deny-bash.yaml +9 -0
  72. package/.pi/harness/examples/web-heuristic-angles.project.yaml +22 -0
  73. package/.pi/harness/policies/bash-denylists.yaml +5 -0
  74. package/.pi/harness/policies/defaults.yaml +51 -0
  75. package/.pi/harness/policies/orchestrator.yaml +18 -0
  76. package/.pi/harness/policies/phases.yaml +10 -0
  77. package/.pi/harness/policies/roles.yaml +5 -0
  78. package/.pi/harness/policies/web-guard.yaml +5 -0
  79. package/.pi/harness/policies/workflow-sequences.yaml +9 -0
  80. package/.pi/harness/sentrux/architecture.manifest.json +26 -4
  81. package/.pi/harness/specs/observation.schema.json +2 -1
  82. package/.pi/harness/web-heuristic-angles.json +278 -0
  83. package/.pi/harness/web-heuristic-angles.yaml +182 -0
  84. package/.pi/lib/agents-policy.d.mts +70 -0
  85. package/.pi/lib/agents-policy.mjs +331 -0
  86. package/.pi/lib/agents-policy.ts +19 -0
  87. package/.pi/lib/agt/audit-run-sink.ts +52 -0
  88. package/.pi/lib/agt/build-evaluation-context.ts +285 -0
  89. package/.pi/lib/agt/config.ts +28 -0
  90. package/.pi/lib/agt/delegation.ts +69 -0
  91. package/.pi/lib/agt/evaluate-policy.ts +56 -0
  92. package/.pi/lib/agt/identity-registry.ts +41 -0
  93. package/.pi/lib/agt/index.ts +55 -0
  94. package/.pi/lib/agt/kill-switch-state.ts +11 -0
  95. package/.pi/lib/agt/legacy-evaluate.ts +101 -0
  96. package/.pi/lib/agt/policy-engine.ts +154 -0
  97. package/.pi/lib/agt/rings.ts +21 -0
  98. package/.pi/lib/agt/sre-hooks.ts +45 -0
  99. package/.pi/lib/agt/trust-run-store.ts +26 -0
  100. package/.pi/lib/agt/workflow-history.ts +29 -0
  101. package/.pi/lib/agt-governance-active.ts +14 -0
  102. package/.pi/lib/agt-tool-guard.ts +78 -0
  103. package/.pi/lib/ask-user/dialog.ts +314 -0
  104. package/.pi/{extensions/lib → lib}/debate-bus-core.ts +10 -10
  105. package/.pi/{extensions/lib → lib}/debate-bus-state.ts +1 -1
  106. package/.pi/{extensions/lib → lib}/extension-load-guard.ts +13 -2
  107. package/.pi/lib/harness-agt-tool-guard.ts +5 -0
  108. package/.pi/{extensions/lib → lib}/harness-artifact-gate.ts +1 -1
  109. package/.pi/lib/harness-debate-core-deps.ts +14 -0
  110. package/.pi/lib/harness-debate-workflow-deps.ts +43 -0
  111. package/.pi/lib/harness-lens/.gitattributes +1 -0
  112. package/.pi/lib/harness-lens/clients/edit-autopatch.ts +88 -0
  113. package/.pi/lib/harness-lens/clients/file-kinds.ts +380 -0
  114. package/.pi/lib/harness-lens/clients/file-time.ts +215 -0
  115. package/.pi/lib/harness-lens/clients/file-utils.ts +484 -0
  116. package/.pi/lib/harness-lens/clients/format-service.ts +276 -0
  117. package/.pi/lib/harness-lens/clients/formatters.ts +1000 -0
  118. package/.pi/lib/harness-lens/clients/git-guard.ts +31 -0
  119. package/.pi/lib/harness-lens/clients/indent-retarget.ts +90 -0
  120. package/.pi/lib/harness-lens/clients/installer/index.ts +2368 -0
  121. package/.pi/lib/harness-lens/clients/latency-logger.ts +80 -0
  122. package/.pi/lib/harness-lens/clients/lens-config.ts +43 -0
  123. package/.pi/lib/harness-lens/clients/lens-events.ts +164 -0
  124. package/.pi/lib/harness-lens/clients/lsp/aggregation.ts +91 -0
  125. package/.pi/lib/harness-lens/clients/lsp/client.ts +1466 -0
  126. package/.pi/lib/harness-lens/clients/lsp/config.ts +216 -0
  127. package/.pi/lib/harness-lens/clients/lsp/edits.ts +297 -0
  128. package/.pi/lib/harness-lens/clients/lsp/index.ts +1355 -0
  129. package/.pi/lib/harness-lens/clients/lsp/interactive-install.ts +424 -0
  130. package/.pi/lib/harness-lens/clients/lsp/language.ts +223 -0
  131. package/.pi/lib/harness-lens/clients/lsp/launch.ts +939 -0
  132. package/.pi/lib/harness-lens/clients/lsp/lsp-index.ts +11 -0
  133. package/.pi/lib/harness-lens/clients/lsp/path-utils.ts +12 -0
  134. package/.pi/lib/harness-lens/clients/lsp/server-strategies.ts +81 -0
  135. package/.pi/lib/harness-lens/clients/lsp/server.ts +1971 -0
  136. package/.pi/lib/harness-lens/clients/path-utils.ts +182 -0
  137. package/.pi/lib/harness-lens/clients/pipeline.ts +360 -0
  138. package/.pi/lib/harness-lens/clients/project-profile.ts +117 -0
  139. package/.pi/lib/harness-lens/clients/runtime-agent-end.ts +112 -0
  140. package/.pi/lib/harness-lens/clients/runtime-config.ts +33 -0
  141. package/.pi/lib/harness-lens/clients/runtime-coordinator.ts +186 -0
  142. package/.pi/lib/harness-lens/clients/runtime-tool-result.ts +171 -0
  143. package/.pi/lib/harness-lens/clients/safe-spawn.ts +339 -0
  144. package/.pi/lib/harness-lens/clients/secrets-scanner.ts +214 -0
  145. package/.pi/lib/harness-lens/clients/tool-policy.ts +2072 -0
  146. package/.pi/lib/harness-lens/clients/types.ts +59 -0
  147. package/.pi/lib/harness-lens/clients/widget-state.ts +283 -0
  148. package/.pi/lib/harness-lens/index.ts +532 -0
  149. package/.pi/lib/harness-lens/tools/lsp-diagnostics.ts +706 -0
  150. package/.pi/lib/harness-lens/tools/lsp-navigation.ts +1246 -0
  151. package/.pi/{extensions/lib → lib}/harness-posthog.ts +3 -0
  152. package/.pi/lib/harness-run-context-responses.ts +9 -0
  153. package/.pi/lib/harness-run-context.ts +0 -2
  154. package/.pi/{extensions/lib/spawn-policy.ts → lib/harness-spawn-policy.ts} +1 -0
  155. package/.pi/{extensions/lib → lib}/harness-spawn-topology.ts +1 -1
  156. package/.pi/lib/harness-subagent-auth.ts +81 -0
  157. package/.pi/{extensions/lib → lib}/harness-subagent-precheck.ts +10 -7
  158. package/.pi/{extensions/lib → lib}/harness-subagent-submit-pipeline.ts +3 -3
  159. package/.pi/lib/harness-subagent-submit-register.ts +163 -0
  160. package/.pi/{extensions/lib → lib}/harness-subagent-submit-registry.ts +1 -37
  161. package/.pi/{extensions/lib → lib}/harness-subagents-bridge.ts +74 -14
  162. package/.pi/{extensions/lib → lib}/harness-subprocess-bootstrap.ts +1 -1
  163. package/.pi/lib/harness-web/artifacts.ts +200 -0
  164. package/.pi/lib/harness-web/cache.ts +369 -0
  165. package/.pi/{extensions/lib → lib}/harness-web/run-cli.ts +42 -2
  166. package/.pi/{extensions/lib → lib}/plan-approval/create-plan.ts +2 -2
  167. package/.pi/{extensions/lib → lib}/plan-approval/format-plan.ts +2 -2
  168. package/.pi/{extensions/lib → lib}/plan-approval/plan-review.ts +162 -201
  169. package/.pi/{extensions/lib → lib}/plan-approval/render.ts +1 -1
  170. package/.pi/{extensions/lib → lib}/plan-approval/resolve-disk.ts +2 -2
  171. package/.pi/{extensions/lib → lib}/plan-approval/types.ts +1 -1
  172. package/.pi/{extensions/lib → lib}/plan-approval/validate.ts +3 -3
  173. package/.pi/{extensions/lib → lib}/plan-debate-envelope.ts +1 -1
  174. package/.pi/{extensions/lib → lib}/plan-debate-gate.ts +1 -1
  175. package/.pi/{extensions/lib → lib}/plan-debate-lane.ts +1 -4
  176. package/.pi/{extensions/lib → lib}/plan-messenger.ts +1 -1
  177. package/.pi/prompts/harness-plan.md +2 -1
  178. package/.pi/prompts/harness-setup.md +40 -65
  179. package/.pi/scripts/README.md +2 -5
  180. package/.pi/scripts/gen-web-heuristic-angles-json.mjs +24 -0
  181. package/.pi/scripts/generate-agents-policy-yaml.mjs +148 -0
  182. package/.pi/scripts/harness-agents-manifest.mjs +60 -3
  183. package/.pi/scripts/harness-agt-doctor.ts +36 -0
  184. package/.pi/scripts/harness-cli-verify.sh +14 -2
  185. package/.pi/scripts/harness-verify.mjs +191 -39
  186. package/.pi/scripts/harness-web-policy-guard.mjs +3 -3
  187. package/.pi/scripts/harness-web.py +218 -15
  188. package/.pi/scripts/harness_web/deep_search.py +55 -0
  189. package/.pi/scripts/harness_web/evidence_bundle.py +47 -0
  190. package/.pi/scripts/harness_web/find_similar.py +88 -0
  191. package/.pi/scripts/harness_web/heuristic_angles_shipped.py +85 -0
  192. package/.pi/scripts/harness_web/heuristic_config.py +251 -0
  193. package/.pi/scripts/harness_web/highlights.py +47 -0
  194. package/.pi/scripts/harness_web/multi_search.py +59 -0
  195. package/.pi/scripts/harness_web/output.py +24 -0
  196. package/.pi/scripts/harness_web/query_angles.py +116 -0
  197. package/.pi/scripts/harness_web/rank.py +163 -0
  198. package/.pi/scripts/harness_web/scrape.py +30 -0
  199. package/.pi/scripts/tests/test_harness_web_heuristic_config.py +132 -0
  200. package/.pi/scripts/tests/test_harness_web_query_angles.py +45 -0
  201. package/.pi/scripts/tests/test_harness_web_rank.py +56 -0
  202. package/.pi/scripts/validate-plan-dag.mjs +65 -74
  203. package/.pi/scripts/vendor-pi-vcc-settings.stub.ts +2 -2
  204. package/.pi/scripts/vendor-sync-pi-vcc.sh +1 -1
  205. package/.pi/skills/architecture/broker-domain/SKILL.md +65 -0
  206. package/.pi/skills/architecture/cqrs/SKILL.md +63 -0
  207. package/.pi/skills/architecture/event-driven/SKILL.md +60 -0
  208. package/.pi/skills/architecture/hexagonal-ports-adapters/SKILL.md +66 -0
  209. package/.pi/skills/architecture/layered/SKILL.md +68 -0
  210. package/.pi/skills/architecture/microkernel/SKILL.md +62 -0
  211. package/.pi/skills/architecture/microservices/SKILL.md +64 -0
  212. package/.pi/skills/architecture/modular-monolith/SKILL.md +65 -0
  213. package/.pi/skills/architecture/orchestration-driven-soa/SKILL.md +61 -0
  214. package/.pi/skills/architecture/pipeline/SKILL.md +63 -0
  215. package/.pi/skills/architecture/service-based/SKILL.md +64 -0
  216. package/.pi/skills/architecture/service-mesh/SKILL.md +60 -0
  217. package/.pi/skills/architecture/space-based/SKILL.md +60 -0
  218. package/.pi/skills/ast-grep/SKILL.md +40 -321
  219. package/.pi/skills/delivery/debugging-discipline/SKILL.md +36 -0
  220. package/.pi/skills/delivery/documentation-update/SKILL.md +33 -0
  221. package/.pi/skills/delivery/requirements-to-implementation/SKILL.md +34 -0
  222. package/.pi/skills/delivery/risk-based-verification/SKILL.md +43 -0
  223. package/.pi/skills/delivery/tradeoff-analysis/SKILL.md +34 -0
  224. package/.pi/skills/engineering/api-contract-design/SKILL.md +38 -0
  225. package/.pi/skills/engineering/cohesion-coupling/SKILL.md +43 -0
  226. package/.pi/skills/engineering/complexity-control/SKILL.md +31 -0
  227. package/.pi/skills/engineering/defensive-programming/SKILL.md +38 -0
  228. package/.pi/skills/engineering/dependency-management/SKILL.md +29 -0
  229. package/.pi/skills/engineering/domain-modeling/SKILL.md +32 -0
  230. package/.pi/skills/engineering/error-handling/SKILL.md +37 -0
  231. package/.pi/skills/engineering/legacy-code-seams/SKILL.md +35 -0
  232. package/.pi/skills/engineering/naming-and-intent/SKILL.md +29 -0
  233. package/.pi/skills/engineering/refactoring-safe-evolution/SKILL.md +35 -0
  234. package/.pi/skills/engineering/routine-function-design/SKILL.md +34 -0
  235. package/.pi/skills/engineering/small-change-discipline/SKILL.md +35 -0
  236. package/.pi/skills/lsp-navigation/SKILL.md +89 -0
  237. package/.pi/skills/quality/code-review-self-check/SKILL.md +35 -0
  238. package/.pi/skills/quality/privacy-data-handling/SKILL.md +26 -0
  239. package/.pi/skills/quality/security-review/SKILL.md +34 -0
  240. package/.pi/skills/quality/test-strategy/SKILL.md +33 -0
  241. package/.pi/skills/quality/testability-design/SKILL.md +33 -0
  242. package/.pi/skills/systems/concurrency-safety/SKILL.md +32 -0
  243. package/.pi/skills/systems/data-modeling-migrations/SKILL.md +31 -0
  244. package/.pi/skills/systems/observability-instrumentation/SKILL.md +32 -0
  245. package/.pi/skills/systems/performance-measurement/SKILL.md +35 -0
  246. package/.pi/skills/systems/reliability-design/SKILL.md +32 -0
  247. package/.sentrux/rules.toml +20 -4
  248. package/AGENTS.md +7 -2
  249. package/CHANGELOG.md +20 -0
  250. package/README.md +3 -12
  251. package/THIRD_PARTY_NOTICES.md +12 -21
  252. package/package.json +17 -7
  253. package/vendor/pi-subagents/src/agents.ts +45 -1
  254. package/vendor/pi-subagents/src/subagents.ts +866 -811
  255. package/vendor/pi-vcc/src/core/brief.ts +68 -99
  256. package/vendor/pi-vcc/src/core/settings.ts +2 -2
  257. package/.agents/skills/caveman/SKILL.md +0 -67
  258. package/.agents/skills/scrapling-web/SKILL.md +0 -98
  259. package/.pi/agents/harness/meta-optimizer.md +0 -36
  260. package/.pi/extensions/00-posthog-network-bootstrap.ts +0 -11
  261. package/.pi/extensions/lib/ask-user/dialog.ts +0 -260
  262. package/.pi/extensions/lib/harness-subagent-auth.ts +0 -207
  263. package/.pi/extensions/lib/harness-subagent-policy.ts +0 -236
  264. package/.pi/extensions/pi-model-router-harness.ts +0 -42
  265. package/.pi/harness/evolution/meta-optimizer.mjs +0 -99
  266. package/.pi/harness/specs/router-tuning-proposal.schema.json +0 -114
  267. package/.pi/model-router.example.json +0 -36
  268. package/.pi/prompts/harness-critic.md +0 -10
  269. package/.pi/prompts/harness-eval.md +0 -10
  270. package/.pi/prompts/harness-router-tune.md +0 -52
  271. package/.pi/scripts/harness-generate-model-router.mjs +0 -327
  272. package/.pi/scripts/harness-model-router-routing.test.mjs +0 -97
  273. package/.pi/scripts/harness-sync-model-router.mjs +0 -97
  274. package/.pi/scripts/harness_web/__pycache__/__init__.cpython-314.pyc +0 -0
  275. package/.pi/scripts/harness_web/__pycache__/config.cpython-314.pyc +0 -0
  276. package/.pi/scripts/harness_web/__pycache__/output.cpython-314.pyc +0 -0
  277. package/.pi/scripts/harness_web/__pycache__/scrape.cpython-314.pyc +0 -0
  278. package/.pi/scripts/harness_web/__pycache__/search.cpython-314.pyc +0 -0
  279. package/.pi/scripts/harness_web/__pycache__/search_ddg.cpython-314.pyc +0 -0
  280. package/.pi/scripts/harness_web/__pycache__/search_searxng.cpython-314.pyc +0 -0
  281. package/.pi/scripts/vendor-sync-pi-model-router.sh +0 -47
  282. package/vendor/pi-model-router/.prettierignore +0 -4
  283. package/vendor/pi-model-router/.prettierrc +0 -5
  284. package/vendor/pi-model-router/AGENTS.md +0 -39
  285. package/vendor/pi-model-router/LICENSE +0 -21
  286. package/vendor/pi-model-router/README.md +0 -99
  287. package/vendor/pi-model-router/UPSTREAM_PIN.md +0 -10
  288. package/vendor/pi-model-router/docs/ARCHITECTURE.md +0 -54
  289. package/vendor/pi-model-router/extensions/commands.ts +0 -720
  290. package/vendor/pi-model-router/extensions/config.ts +0 -348
  291. package/vendor/pi-model-router/extensions/constants.ts +0 -1
  292. package/vendor/pi-model-router/extensions/index.ts +0 -478
  293. package/vendor/pi-model-router/extensions/provider.ts +0 -580
  294. package/vendor/pi-model-router/extensions/routing.ts +0 -564
  295. package/vendor/pi-model-router/extensions/state.ts +0 -52
  296. package/vendor/pi-model-router/extensions/types.ts +0 -95
  297. package/vendor/pi-model-router/extensions/ui.ts +0 -144
  298. package/vendor/pi-model-router/model-router.example.json +0 -48
  299. package/vendor/pi-model-router/package.json +0 -48
  300. package/vendor/pi-model-router/tsconfig.json +0 -16
  301. /package/.pi/{prompts → harness/docs}/planning-rubrics.md +0 -0
  302. /package/.pi/{extensions/lib → lib}/ask-user/fallback.ts +0 -0
  303. /package/.pi/{extensions/lib → lib}/ask-user/render.ts +0 -0
  304. /package/.pi/{extensions/lib → lib}/ask-user/schema.ts +0 -0
  305. /package/.pi/{extensions/lib → lib}/ask-user/types.ts +0 -0
  306. /package/.pi/{extensions/lib → lib}/ask-user/validate-core.mjs +0 -0
  307. /package/.pi/{extensions/lib → lib}/ask-user/validate.ts +0 -0
  308. /package/.pi/{extensions/lib → lib}/harness-cocoindex-refresh.ts +0 -0
  309. /package/.pi/{extensions/lib → lib}/harness-paths.ts +0 -0
  310. /package/.pi/{extensions/lib → lib}/harness-spawn-budget.ts +0 -0
  311. /package/.pi/{extensions/lib → lib}/harness-vcc-settings.ts +0 -0
  312. /package/.pi/{extensions/lib → lib}/plan-approval/dialog.ts +0 -0
  313. /package/.pi/{extensions/lib → lib}/plan-approval/schema.ts +0 -0
  314. /package/.pi/{extensions/lib → lib}/plan-approval-readiness.ts +0 -0
  315. /package/.pi/{extensions/lib → lib}/plan-debate-eligibility.ts +0 -0
  316. /package/.pi/{extensions/lib → lib}/plan-debate-focus.ts +0 -0
  317. /package/.pi/{extensions/lib → lib}/plan-debate-id.ts +0 -0
  318. /package/.pi/{extensions/lib → lib}/plan-debate-lanes.ts +0 -0
  319. /package/.pi/{extensions/lib → lib}/plan-debate-round-status.ts +0 -0
  320. /package/.pi/{extensions/lib → lib}/plan-debate-write-guard.ts +0 -0
  321. /package/.pi/{extensions/lib → lib}/plan-review-gate.ts +0 -0
  322. /package/.pi/{extensions/lib → lib}/plan-review-integrator-rules.ts +0 -0
  323. /package/.pi/{extensions/lib → lib}/plan-scope-guard.ts +0 -0
  324. /package/.pi/{extensions/lib → lib}/posthog-client.ts +0 -0
  325. /package/.pi/{extensions/lib → lib}/posthog-node.d.ts +0 -0
@@ -0,0 +1,706 @@
1
+ /**
2
+ * lsp_diagnostics tool definition
3
+ *
4
+ * Proactive LSP diagnostics check — single files or directories.
5
+ * Adopted from code-yeongyu/pi-lsp-client design.
6
+ */
7
+
8
+ import * as fs from "node:fs";
9
+ import * as path from "node:path";
10
+ import { Type } from "typebox";
11
+ import type { LSPDiagnostic } from "../clients/lsp/client.js";
12
+ import { getLSPService } from "../clients/lsp/index.js";
13
+
14
+ const SKIP_DIRS = new Set([
15
+ "node_modules",
16
+ ".git",
17
+ "dist",
18
+ "build",
19
+ ".next",
20
+ "out",
21
+ "target",
22
+ "__pycache__",
23
+ ".venv",
24
+ "venv",
25
+ ]);
26
+
27
+ const LANG_EXTENSIONS: Record<string, string[]> = {
28
+ ".ts": [".ts", ".tsx", ".mts", ".cts"],
29
+ ".tsx": [".ts", ".tsx", ".mts", ".cts"],
30
+ ".js": [".js", ".jsx", ".mjs", ".cjs"],
31
+ ".py": [".py", ".pyi"],
32
+ ".rs": [".rs"],
33
+ ".go": [".go"],
34
+ ".rb": [".rb", ".rake", ".gemspec"],
35
+ ".java": [".java"],
36
+ ".kt": [".kt", ".kts"],
37
+ ".swift": [".swift"],
38
+ ".cs": [".cs"],
39
+ ".cpp": [".cpp", ".cc", ".cxx", ".hpp", ".hxx"],
40
+ ".c": [".c", ".h"],
41
+ ".zig": [".zig", ".zon"],
42
+ ".hs": [".hs", ".lhs"],
43
+ ".ex": [".ex", ".exs"],
44
+ ".gleam": [".gleam"],
45
+ ".tf": [".tf", ".tfvars"],
46
+ ".nix": [".nix"],
47
+ ".sh": [".sh", ".bash", ".zsh"],
48
+ ".php": [".php"],
49
+ ".lua": [".lua"],
50
+ ".dart": [".dart"],
51
+ ".vue": [".vue"],
52
+ ".svelte": [".svelte"],
53
+ ".css": [".css", ".scss", ".less"],
54
+ ".html": [".html", ".htm"],
55
+ ".json": [".json", ".jsonc"],
56
+ ".yaml": [".yaml", ".yml"],
57
+ ".toml": [".toml"],
58
+ ".prisma": [".prisma"],
59
+ };
60
+
61
+ const MAX_FILES = 50;
62
+ const MAX_BATCH_FILES = 100;
63
+ const MAX_DIAGNOSTICS = 200;
64
+ const DEFAULT_BATCH_CONCURRENCY = 8;
65
+ const MAX_BATCH_CONCURRENCY = 16;
66
+
67
+ // LSP severities: 1=Error, 2=Warning, 3=Information, 4=Hint
68
+ const SEVERITY_NAMES: Record<number, string> = {
69
+ 1: "error",
70
+ 2: "warning",
71
+ 3: "information",
72
+ 4: "hint",
73
+ };
74
+
75
+ type LspHealthLike = {
76
+ health?: string;
77
+ serverCountAttempted?: number;
78
+ serverCountReady?: number;
79
+ candidateServerIds?: string[];
80
+ mergedCount?: number;
81
+ };
82
+
83
+ type BatchOptions = {
84
+ concurrency: number;
85
+ waitMs?: number;
86
+ };
87
+
88
+ type FileDiag = {
89
+ file: string;
90
+ line?: number;
91
+ character?: number;
92
+ severity: number;
93
+ message: string;
94
+ source?: string;
95
+ code?: string | number;
96
+ };
97
+
98
+ type FileDiagnosticResult = {
99
+ file: string;
100
+ diagnostics: FileDiag[];
101
+ unavailable?: string;
102
+ error?: string;
103
+ };
104
+
105
+ function lspUnavailableMessage(
106
+ filePath: string,
107
+ health: LspHealthLike | undefined,
108
+ ): string | undefined {
109
+ if (!health || !String(health.health ?? "").startsWith("no_clients")) {
110
+ return undefined;
111
+ }
112
+ const candidates = health.candidateServerIds?.length
113
+ ? ` candidates=${health.candidateServerIds.join(",")}`
114
+ : "";
115
+ const reason =
116
+ (health.serverCountAttempted ?? 0) === 0
117
+ ? "no LSP server configured"
118
+ : "no LSP client is currently ready";
119
+ const stale =
120
+ (health.mergedCount ?? 0) > 0
121
+ ? " Showing stale last-known diagnostics below."
122
+ : " No diagnostics were collected.";
123
+ return `LSP unavailable for ${filePath}: ${reason}; ready=${health.serverCountReady ?? 0}/${health.serverCountAttempted ?? 0}.${candidates}.${stale}`;
124
+ }
125
+
126
+ function boundedPositiveInt(
127
+ value: unknown,
128
+ fallback: number,
129
+ min: number,
130
+ max: number,
131
+ ): number {
132
+ const parsed = typeof value === "number" ? Math.floor(value) : Number.NaN;
133
+ if (!Number.isFinite(parsed)) return fallback;
134
+ return Math.max(min, Math.min(max, parsed));
135
+ }
136
+
137
+ async function mapWithConcurrency<T, R>(
138
+ items: T[],
139
+ concurrency: number,
140
+ mapper: (item: T, index: number) => Promise<R>,
141
+ ): Promise<R[]> {
142
+ const results: R[] = [];
143
+ let nextIndex = 0;
144
+ const workers = Math.min(Math.max(1, concurrency), items.length);
145
+ await Promise.all(
146
+ Array.from({ length: workers }, async () => {
147
+ while (true) {
148
+ const index = nextIndex;
149
+ nextIndex += 1;
150
+ if (index >= items.length) return;
151
+ results[index] = await mapper(items[index]!, index);
152
+ }
153
+ }),
154
+ );
155
+ return results;
156
+ }
157
+
158
+ function collectFiles(
159
+ dir: string,
160
+ extensions: string[],
161
+ maxFiles: number,
162
+ ): string[] {
163
+ const files: string[] = [];
164
+ function walk(current: string): void {
165
+ if (files.length >= maxFiles) return;
166
+ let entries: fs.Dirent[];
167
+ try {
168
+ entries = fs.readdirSync(current, { withFileTypes: true });
169
+ } catch {
170
+ return;
171
+ }
172
+ for (const entry of entries) {
173
+ if (files.length >= maxFiles) return;
174
+ if (entry.isSymbolicLink()) continue;
175
+ const full = path.join(current, entry.name);
176
+ if (entry.isDirectory()) {
177
+ if (!SKIP_DIRS.has(entry.name)) walk(full);
178
+ } else if (entry.isFile() && extensions.includes(path.extname(full))) {
179
+ files.push(full);
180
+ }
181
+ }
182
+ }
183
+ walk(dir);
184
+ return files;
185
+ }
186
+
187
+ export function createLspDiagnosticsTool() {
188
+ return {
189
+ name: "lsp_diagnostics" as const,
190
+ label: "LSP Diagnostics",
191
+ description:
192
+ "Get errors, warnings, and hints from language servers for a file or directory. " +
193
+ "Use BEFORE running builds to proactively check for issues. " +
194
+ "Works on directories by auto-detecting file extensions and scanning all matching files.",
195
+ promptSnippet:
196
+ "Get LSP diagnostics for a file or directory (use before builds)",
197
+ parameters: Type.Object({
198
+ filePath: Type.Optional(
199
+ Type.String({
200
+ description:
201
+ "File or directory path to check. For directories, all matching source files are scanned.",
202
+ }),
203
+ ),
204
+ filePaths: Type.Optional(
205
+ Type.Array(Type.String(), {
206
+ minItems: 1,
207
+ maxItems: MAX_BATCH_FILES,
208
+ description:
209
+ "Explicit files to check as a bounded-concurrency batch. When provided, filePath is ignored.",
210
+ }),
211
+ ),
212
+ severity: Type.Optional(
213
+ Type.String({
214
+ enum: ["error", "warning", "information", "hint", "all"],
215
+ description: "Filter by severity level (default: all)",
216
+ }),
217
+ ),
218
+ concurrency: Type.Optional(
219
+ Type.Number({
220
+ description:
221
+ "Batch/directory concurrency for opening files and collecting diagnostics. Default 8, max 16.",
222
+ }),
223
+ ),
224
+ waitMs: Type.Optional(
225
+ Type.Number({
226
+ description:
227
+ "Optional per-file LSP wait budget for batch diagnostics. Uses server defaults when omitted.",
228
+ }),
229
+ ),
230
+ }),
231
+ async execute(
232
+ _toolCallId: string,
233
+ params: Record<string, unknown>,
234
+ _signal: AbortSignal,
235
+ _onUpdate: unknown,
236
+ ctx: { cwd?: string },
237
+ ) {
238
+ const typedParams = params as {
239
+ filePath?: string;
240
+ filePaths?: string[];
241
+ severity?: string;
242
+ concurrency?: number;
243
+ waitMs?: number;
244
+ };
245
+ const severity = (typedParams.severity ?? "all") as string;
246
+ const cwd = ctx.cwd ?? process.cwd();
247
+ const concurrency = boundedPositiveInt(
248
+ typedParams.concurrency,
249
+ DEFAULT_BATCH_CONCURRENCY,
250
+ 1,
251
+ MAX_BATCH_CONCURRENCY,
252
+ );
253
+ const waitMs =
254
+ typeof typedParams.waitMs === "number" && typedParams.waitMs >= 0
255
+ ? Math.floor(typedParams.waitMs)
256
+ : undefined;
257
+
258
+ const lspService = getLSPService();
259
+ if (!lspService) {
260
+ return {
261
+ content: [
262
+ { type: "text" as const, text: "LSP service not available." },
263
+ ],
264
+ isError: true,
265
+ details: {},
266
+ };
267
+ }
268
+
269
+ if (
270
+ Array.isArray(typedParams.filePaths) &&
271
+ typedParams.filePaths.length > 0
272
+ ) {
273
+ const absPaths = typedParams.filePaths
274
+ .filter(
275
+ (entry): entry is string =>
276
+ typeof entry === "string" && entry.trim().length > 0,
277
+ )
278
+ .slice(0, MAX_BATCH_FILES)
279
+ .map((entry) =>
280
+ path.isAbsolute(entry) ? entry : path.resolve(cwd, entry),
281
+ );
282
+ return runBatchFileDiagnostics(absPaths, severity, lspService, {
283
+ concurrency,
284
+ waitMs,
285
+ });
286
+ }
287
+
288
+ const rawPath = typedParams.filePath;
289
+ if (!rawPath || rawPath.trim().length === 0) {
290
+ return {
291
+ content: [
292
+ {
293
+ type: "text" as const,
294
+ text: "filePath or filePaths is required.",
295
+ },
296
+ ],
297
+ isError: true,
298
+ details: {},
299
+ };
300
+ }
301
+ const absPath = path.isAbsolute(rawPath)
302
+ ? rawPath
303
+ : path.resolve(cwd, rawPath);
304
+
305
+ let stat: fs.Stats;
306
+ try {
307
+ stat = fs.statSync(absPath);
308
+ } catch {
309
+ return {
310
+ content: [
311
+ { type: "text" as const, text: `Path not found: ${absPath}` },
312
+ ],
313
+ isError: true,
314
+ details: {},
315
+ };
316
+ }
317
+
318
+ if (stat.isDirectory()) {
319
+ return runDirectoryDiagnostics(absPath, severity, lspService, {
320
+ concurrency,
321
+ waitMs,
322
+ });
323
+ }
324
+ return runFileDiagnostics(absPath, severity, lspService, waitMs);
325
+ },
326
+ };
327
+ }
328
+
329
+ async function collectDiagnosticsForFile(
330
+ absPath: string,
331
+ lspService: NonNullable<ReturnType<typeof getLSPService>>,
332
+ waitMs?: number,
333
+ ): Promise<LSPDiagnostic[]> {
334
+ try {
335
+ const content = fs.readFileSync(absPath, "utf-8");
336
+ const serviceWithTouch = lspService as NonNullable<
337
+ ReturnType<typeof getLSPService>
338
+ > & {
339
+ touchFile?: (
340
+ filePath: string,
341
+ content: string,
342
+ options: {
343
+ diagnostics: "document";
344
+ collectDiagnostics: true;
345
+ maxClientWaitMs?: number;
346
+ source: string;
347
+ clientScope: "all";
348
+ },
349
+ ) => Promise<LSPDiagnostic[] | undefined>;
350
+ };
351
+ if (
352
+ waitMs !== undefined &&
353
+ typeof serviceWithTouch.touchFile === "function"
354
+ ) {
355
+ await serviceWithTouch.touchFile(absPath, content, {
356
+ diagnostics: "document",
357
+ collectDiagnostics: true,
358
+ maxClientWaitMs: waitMs,
359
+ source: "lsp_diagnostics",
360
+ clientScope: "all",
361
+ });
362
+ } else {
363
+ await lspService.openFile(absPath, content, {
364
+ preserveDiagnostics: false,
365
+ });
366
+ }
367
+ } catch {
368
+ // Non-fatal: getDiagnostics may still have stale/health information.
369
+ }
370
+
371
+ return lspService.getDiagnostics(
372
+ absPath,
373
+ waitMs !== undefined ? "document" : "full",
374
+ );
375
+ }
376
+
377
+ function diagnosticsToFileDiags(
378
+ file: string,
379
+ diagnostics: LSPDiagnostic[],
380
+ ): FileDiag[] {
381
+ return diagnostics.map((d) => ({
382
+ file,
383
+ line: d.range?.start?.line,
384
+ character: d.range?.start?.character,
385
+ severity: d.severity,
386
+ message: d.message,
387
+ source: d.source,
388
+ code: d.code,
389
+ }));
390
+ }
391
+
392
+ async function collectFileDiagnosticResult(
393
+ file: string,
394
+ severity: string,
395
+ lspService: NonNullable<ReturnType<typeof getLSPService>>,
396
+ waitMs?: number,
397
+ ): Promise<FileDiagnosticResult> {
398
+ try {
399
+ const stat = fs.statSync(file);
400
+ if (!stat.isFile()) {
401
+ return { file, diagnostics: [], error: `${file}: not a file` };
402
+ }
403
+ } catch {
404
+ return { file, diagnostics: [], error: `${file}: path not found` };
405
+ }
406
+
407
+ const rawDiags = await collectDiagnosticsForFile(file, lspService, waitMs);
408
+ const health = lspService.getDiagnosticsHealth?.(file) as
409
+ | LspHealthLike
410
+ | undefined;
411
+ return {
412
+ file,
413
+ diagnostics: diagnosticsToFileDiags(
414
+ file,
415
+ applySeverityFilter(rawDiags, severity),
416
+ ),
417
+ unavailable: lspUnavailableMessage(file, health),
418
+ };
419
+ }
420
+
421
+ async function runFileDiagnostics(
422
+ absPath: string,
423
+ severity: string,
424
+ lspService: NonNullable<ReturnType<typeof getLSPService>>,
425
+ waitMs?: number,
426
+ ) {
427
+ const rawDiags = await collectDiagnosticsForFile(absPath, lspService, waitMs);
428
+ const lspHealth = lspService.getDiagnosticsHealth?.(absPath) as
429
+ | LspHealthLike
430
+ | undefined;
431
+ const unavailable = lspUnavailableMessage(absPath, lspHealth);
432
+ const filtered = applySeverityFilter(rawDiags, severity);
433
+ const total = filtered.length;
434
+ const truncated = total > MAX_DIAGNOSTICS;
435
+ const limited = truncated ? filtered.slice(0, MAX_DIAGNOSTICS) : filtered;
436
+
437
+ let text: string;
438
+ if (total === 0) {
439
+ text = unavailable ?? "No diagnostics found.";
440
+ } else {
441
+ const lines = limited.map(formatDiag);
442
+ if (unavailable) lines.unshift(unavailable, "");
443
+ if (truncated) {
444
+ lines.unshift(
445
+ `Found ${total} diagnostics (showing first ${MAX_DIAGNOSTICS}):`,
446
+ );
447
+ }
448
+ text = lines.join("\n");
449
+ }
450
+
451
+ return {
452
+ content: [{ type: "text" as const, text }],
453
+ details: {
454
+ filePath: absPath,
455
+ mode: "file",
456
+ severity,
457
+ diagnostics: limited.map((d) => ({
458
+ line: d.range?.start?.line,
459
+ character: d.range?.start?.character,
460
+ severity: d.severity,
461
+ message: d.message,
462
+ source: d.source,
463
+ code: d.code,
464
+ })),
465
+ totalDiagnostics: total,
466
+ truncated,
467
+ lspHealth,
468
+ waitMs,
469
+ },
470
+ };
471
+ }
472
+
473
+ async function runBatchFileDiagnostics(
474
+ absPaths: string[],
475
+ severity: string,
476
+ lspService: NonNullable<ReturnType<typeof getLSPService>>,
477
+ options: BatchOptions,
478
+ ) {
479
+ if (absPaths.length === 0) {
480
+ return {
481
+ content: [{ type: "text" as const, text: "No file paths provided." }],
482
+ isError: true,
483
+ details: { mode: "batch", severity, filesChecked: 0 },
484
+ };
485
+ }
486
+
487
+ const results = await mapWithConcurrency(
488
+ absPaths,
489
+ options.concurrency,
490
+ (file) =>
491
+ collectFileDiagnosticResult(file, severity, lspService, options.waitMs),
492
+ );
493
+ const fileErrors = results.flatMap((result) =>
494
+ result.error ? [result.error] : [],
495
+ );
496
+ const lspHealthWarnings = results.flatMap((result) =>
497
+ result.unavailable ? [result.unavailable] : [],
498
+ );
499
+ const allDiags = results.flatMap((result) => result.diagnostics);
500
+ const total = allDiags.length;
501
+ const truncated = total > MAX_DIAGNOSTICS;
502
+ const display = truncated ? allDiags.slice(0, MAX_DIAGNOSTICS) : allDiags;
503
+
504
+ const lines: string[] = [
505
+ `Files checked: ${results.length}`,
506
+ `Total diagnostics: ${total}`,
507
+ `Concurrency: ${options.concurrency}`,
508
+ ];
509
+ if (options.waitMs !== undefined)
510
+ lines.push(`Wait budget: ${options.waitMs}ms`);
511
+ if (fileErrors.length > 0) lines.push("", "File errors:", ...fileErrors);
512
+ if (lspHealthWarnings.length > 0) {
513
+ lines.push("", "LSP health warnings:", ...lspHealthWarnings.slice(0, 10));
514
+ }
515
+ if (display.length === 0) {
516
+ lines.push("", "No diagnostics found.");
517
+ } else {
518
+ lines.push("");
519
+ for (const d of display) {
520
+ const sevName = SEVERITY_NAMES[d.severity] ?? "unknown";
521
+ const loc =
522
+ d.line !== undefined
523
+ ? `${d.file}:${d.line + 1}:${(d.character ?? 0) + 1}`
524
+ : d.file;
525
+ const src = d.source ? `[${d.source}]` : "";
526
+ const code = d.code ? ` (${d.code})` : "";
527
+ lines.push(`${loc}: ${sevName}${src}${code}: ${d.message}`);
528
+ }
529
+ if (truncated) {
530
+ lines.push(
531
+ "",
532
+ `... (${total - MAX_DIAGNOSTICS} more diagnostics not shown)`,
533
+ );
534
+ }
535
+ }
536
+
537
+ return {
538
+ content: [{ type: "text" as const, text: lines.join("\n") }],
539
+ details: {
540
+ mode: "batch",
541
+ severity,
542
+ filesChecked: results.length,
543
+ concurrency: options.concurrency,
544
+ waitMs: options.waitMs,
545
+ diagnostics: display,
546
+ totalDiagnostics: total,
547
+ truncated,
548
+ fileErrors: fileErrors.length > 0 ? fileErrors : undefined,
549
+ lspHealthWarnings:
550
+ lspHealthWarnings.length > 0 ? lspHealthWarnings : undefined,
551
+ },
552
+ };
553
+ }
554
+
555
+ async function runDirectoryDiagnostics(
556
+ absPath: string,
557
+ severity: string,
558
+ lspService: NonNullable<ReturnType<typeof getLSPService>>,
559
+ options: BatchOptions,
560
+ ) {
561
+ let extension: string | undefined;
562
+ let collectedFiles: string[] = [];
563
+
564
+ for (const [ext, exts] of Object.entries(LANG_EXTENSIONS)) {
565
+ collectedFiles = collectFiles(absPath, exts, MAX_FILES + 1);
566
+ if (collectedFiles.length > 0) {
567
+ extension = ext;
568
+ break;
569
+ }
570
+ }
571
+
572
+ if (!extension || collectedFiles.length === 0) {
573
+ return {
574
+ content: [
575
+ {
576
+ type: "text" as const,
577
+ text: `No supported source files found in: ${absPath}`,
578
+ },
579
+ ],
580
+ details: {
581
+ filePath: absPath,
582
+ mode: "directory",
583
+ severity,
584
+ filesScanned: 0,
585
+ },
586
+ };
587
+ }
588
+
589
+ const wasCapped = collectedFiles.length > MAX_FILES;
590
+ const filesToProcess = collectedFiles.slice(0, MAX_FILES);
591
+ const results = await mapWithConcurrency(
592
+ filesToProcess,
593
+ options.concurrency,
594
+ (file) =>
595
+ collectFileDiagnosticResult(file, severity, lspService, options.waitMs),
596
+ );
597
+ const fileErrors = results.flatMap((result) =>
598
+ result.error ? [result.error] : [],
599
+ );
600
+ const lspHealthWarnings = results.flatMap((result) =>
601
+ result.unavailable ? [result.unavailable] : [],
602
+ );
603
+ const allDiags = results.flatMap((result) => result.diagnostics);
604
+ const total = allDiags.length;
605
+ const truncated = total > MAX_DIAGNOSTICS;
606
+ const display = truncated ? allDiags.slice(0, MAX_DIAGNOSTICS) : allDiags;
607
+
608
+ let text: string;
609
+ if (total === 0) {
610
+ text = [
611
+ `Directory: ${absPath}`,
612
+ `Files scanned: ${filesToProcess.length}${wasCapped ? ` (capped at ${MAX_FILES})` : ""}`,
613
+ ...(lspHealthWarnings.length > 0
614
+ ? [
615
+ "LSP unavailable for one or more files:",
616
+ ...lspHealthWarnings.slice(0, 10),
617
+ ]
618
+ : ["No diagnostics found."]),
619
+ ].join("\n");
620
+ } else {
621
+ const lines: string[] = [
622
+ `Directory: ${absPath}`,
623
+ `Files scanned: ${filesToProcess.length}${wasCapped ? ` (capped at ${MAX_FILES})` : ""}`,
624
+ `Files with errors: ${new Set(display.map((d) => d.file)).size}`,
625
+ `Total diagnostics: ${total}`,
626
+ ...(lspHealthWarnings.length > 0
627
+ ? ["", "LSP health warnings:", ...lspHealthWarnings.slice(0, 10)]
628
+ : []),
629
+ "",
630
+ ];
631
+ for (const d of display) {
632
+ const sevName = SEVERITY_NAMES[d.severity] ?? "unknown";
633
+ const relPath = path.relative(absPath, d.file);
634
+ const loc =
635
+ d.line !== undefined
636
+ ? `${relPath}:${d.line + 1}:${(d.character ?? 0) + 1}`
637
+ : d.file;
638
+ const src = d.source ? `[${d.source}]` : "";
639
+ const code = d.code ? ` (${d.code})` : "";
640
+ lines.push(`${loc}: ${sevName}${src}${code}: ${d.message}`);
641
+ }
642
+ if (truncated) {
643
+ lines.push(
644
+ "",
645
+ `... (${total - MAX_DIAGNOSTICS} more diagnostics not shown)`,
646
+ );
647
+ }
648
+ text = lines.join("\n");
649
+ }
650
+
651
+ return {
652
+ content: [{ type: "text" as const, text }],
653
+ details: {
654
+ filePath: absPath,
655
+ mode: "directory",
656
+ severity,
657
+ filesScanned: filesToProcess.length,
658
+ capped: wasCapped,
659
+ diagnostics: display.map((d) => ({
660
+ file: path.relative(absPath, d.file),
661
+ line: d.line,
662
+ character: d.character,
663
+ severity: d.severity,
664
+ message: d.message,
665
+ source: d.source,
666
+ code: d.code,
667
+ })),
668
+ totalDiagnostics: total,
669
+ truncated,
670
+ fileErrors: fileErrors.length > 0 ? fileErrors : undefined,
671
+ lspHealthWarnings:
672
+ lspHealthWarnings.length > 0 ? lspHealthWarnings : undefined,
673
+ concurrency: options.concurrency,
674
+ waitMs: options.waitMs,
675
+ },
676
+ };
677
+ }
678
+
679
+ // ── helpers ─────────────────────────────────────────────────────────────
680
+
681
+ function applySeverityFilter<T extends { severity: number }>(
682
+ diags: T[],
683
+ severity: string,
684
+ ): T[] {
685
+ if (severity === "all") return diags;
686
+ const maxLevel: Record<string, number> = {
687
+ error: 1,
688
+ warning: 2,
689
+ information: 3,
690
+ hint: 4,
691
+ };
692
+ const max = maxLevel[severity] ?? 0;
693
+ if (max === 0) return diags;
694
+ return diags.filter((d) => (d.severity ?? 3) <= max);
695
+ }
696
+
697
+ function formatDiag(diag: LSPDiagnostic): string {
698
+ const loc =
699
+ diag.range?.start?.line !== undefined
700
+ ? `L${diag.range.start.line + 1}:${(diag.range.start.character ?? 0) + 1}`
701
+ : "";
702
+ const src = diag.source ? `[${diag.source}]` : "";
703
+ const code = diag.code ? ` (${diag.code})` : "";
704
+ const sevName = SEVERITY_NAMES[diag.severity] ?? "unknown";
705
+ return `${loc}: ${sevName}${src}${code}: ${diag.message}`;
706
+ }