ultimate-pi 0.18.1 → 0.19.0

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 (284) 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/.pi/PACKAGING.md +4 -4
  5. package/.pi/SYSTEM.md +54 -120
  6. package/.pi/agents/harness/incident-recorder.md +0 -1
  7. package/.pi/agents/harness/planning/decompose.md +0 -2
  8. package/.pi/agents/harness/planning/execution-plan-author.md +0 -2
  9. package/.pi/agents/harness/planning/hypothesis-validator.md +0 -2
  10. package/.pi/agents/harness/planning/hypothesis.md +0 -2
  11. package/.pi/agents/harness/planning/implementation-researcher.md +0 -2
  12. package/.pi/agents/harness/planning/plan-adversary.md +0 -2
  13. package/.pi/agents/harness/planning/plan-evaluator.md +1 -3
  14. package/.pi/agents/harness/planning/planning-context.md +0 -2
  15. package/.pi/agents/harness/planning/review-integrator.md +0 -2
  16. package/.pi/agents/harness/planning/sprint-contract-auditor.md +0 -2
  17. package/.pi/agents/harness/planning/stack-researcher.md +0 -2
  18. package/.pi/agents/harness/reviewing/adversary.md +0 -2
  19. package/.pi/agents/harness/reviewing/evaluator.md +0 -2
  20. package/.pi/agents/harness/reviewing/tie-breaker.md +0 -2
  21. package/.pi/agents/harness/running/executor.md +0 -2
  22. package/.pi/agents/harness/sentrux-bootstrap.md +0 -1
  23. package/.pi/agents/harness/sentrux-steward.md +0 -2
  24. package/.pi/agents/harness/trace-librarian.md +0 -1
  25. package/.pi/extensions/00-posthog-network-bootstrap.ts +1 -1
  26. package/.pi/extensions/agt-kill-switch.ts +57 -0
  27. package/.pi/extensions/agt-prompt-guard.ts +32 -0
  28. package/.pi/extensions/custom-footer.ts +46 -145
  29. package/.pi/extensions/custom-header.ts +1 -1
  30. package/.pi/extensions/custom-system-prompt.ts +1 -1
  31. package/.pi/extensions/debate-orchestrator.ts +6 -6
  32. package/.pi/extensions/harness-ask-user.ts +7 -7
  33. package/.pi/extensions/harness-debate-tools.ts +26 -42
  34. package/.pi/extensions/harness-lens.ts +94 -0
  35. package/.pi/extensions/harness-plan-approval.ts +11 -11
  36. package/.pi/extensions/harness-run-context.ts +1070 -876
  37. package/.pi/extensions/harness-subagent-governance.ts +8 -0
  38. package/.pi/extensions/harness-subagent-submit.ts +34 -163
  39. package/.pi/extensions/harness-subagents.ts +3 -3
  40. package/.pi/extensions/harness-telemetry.ts +2 -2
  41. package/.pi/extensions/harness-web-tools.ts +2 -2
  42. package/.pi/extensions/policy-gate.ts +25 -5
  43. package/.pi/extensions/sentrux-rules-sync.ts +1 -1
  44. package/.pi/extensions/subagent-governance.ts +92 -0
  45. package/.pi/extensions/trace-recorder.ts +1 -1
  46. package/.pi/extensions/{ultimate-pi-vcc.ts → vcc-compaction.ts} +1 -1
  47. package/.pi/harness/README.md +6 -2
  48. package/.pi/harness/agents.manifest.json +22 -25
  49. package/.pi/harness/agents.policy.yaml +275 -0
  50. package/.pi/harness/docs/adrs/0030-inhouse-vcc-compaction.md +1 -1
  51. package/.pi/harness/docs/adrs/0035-plan-phase-review-gate.md +1 -1
  52. package/.pi/harness/docs/adrs/0045-harness-lens-minimal-contract.md +49 -0
  53. package/.pi/harness/docs/adrs/0046-agt-policy-engine.md +51 -0
  54. package/.pi/harness/docs/adrs/0047-agt-layered-security.md +39 -0
  55. package/.pi/harness/docs/adrs/0048-tool-call-hook-order.md +25 -0
  56. package/.pi/harness/docs/adrs/0049-agents-policy-manifest.md +36 -0
  57. package/.pi/harness/docs/adrs/README.md +5 -0
  58. package/.pi/harness/evolution/README.md +1 -2
  59. package/.pi/harness/examples/agents.policy.project.yaml +19 -0
  60. package/.pi/harness/examples/policies/custom-deny-bash.yaml +9 -0
  61. package/.pi/harness/policies/bash-denylists.yaml +5 -0
  62. package/.pi/harness/policies/defaults.yaml +51 -0
  63. package/.pi/harness/policies/orchestrator.yaml +18 -0
  64. package/.pi/harness/policies/phases.yaml +10 -0
  65. package/.pi/harness/policies/roles.yaml +5 -0
  66. package/.pi/harness/policies/web-guard.yaml +5 -0
  67. package/.pi/harness/policies/workflow-sequences.yaml +9 -0
  68. package/.pi/harness/sentrux/architecture.manifest.json +26 -4
  69. package/.pi/harness/specs/observation.schema.json +2 -1
  70. package/.pi/lib/agents-policy.d.mts +70 -0
  71. package/.pi/lib/agents-policy.mjs +325 -0
  72. package/.pi/lib/agents-policy.ts +19 -0
  73. package/.pi/lib/agt/audit-run-sink.ts +52 -0
  74. package/.pi/lib/agt/build-evaluation-context.ts +285 -0
  75. package/.pi/lib/agt/config.ts +28 -0
  76. package/.pi/lib/agt/delegation.ts +69 -0
  77. package/.pi/lib/agt/evaluate-policy.ts +56 -0
  78. package/.pi/lib/agt/identity-registry.ts +41 -0
  79. package/.pi/lib/agt/index.ts +55 -0
  80. package/.pi/lib/agt/kill-switch-state.ts +11 -0
  81. package/.pi/lib/agt/legacy-evaluate.ts +101 -0
  82. package/.pi/lib/agt/policy-engine.ts +154 -0
  83. package/.pi/lib/agt/rings.ts +21 -0
  84. package/.pi/lib/agt/sre-hooks.ts +45 -0
  85. package/.pi/lib/agt/trust-run-store.ts +26 -0
  86. package/.pi/lib/agt/workflow-history.ts +29 -0
  87. package/.pi/lib/agt-governance-active.ts +14 -0
  88. package/.pi/lib/agt-tool-guard.ts +78 -0
  89. package/.pi/lib/ask-user/dialog.ts +314 -0
  90. package/.pi/{extensions/lib → lib}/debate-bus-core.ts +10 -10
  91. package/.pi/{extensions/lib → lib}/debate-bus-state.ts +1 -1
  92. package/.pi/{extensions/lib → lib}/extension-load-guard.ts +13 -2
  93. package/.pi/lib/harness-agt-tool-guard.ts +5 -0
  94. package/.pi/{extensions/lib → lib}/harness-artifact-gate.ts +1 -1
  95. package/.pi/lib/harness-debate-core-deps.ts +14 -0
  96. package/.pi/lib/harness-debate-workflow-deps.ts +43 -0
  97. package/.pi/lib/harness-lens/.gitattributes +1 -0
  98. package/.pi/lib/harness-lens/clients/edit-autopatch.ts +88 -0
  99. package/.pi/lib/harness-lens/clients/file-kinds.ts +380 -0
  100. package/.pi/lib/harness-lens/clients/file-time.ts +215 -0
  101. package/.pi/lib/harness-lens/clients/file-utils.ts +484 -0
  102. package/.pi/lib/harness-lens/clients/format-service.ts +276 -0
  103. package/.pi/lib/harness-lens/clients/formatters.ts +1000 -0
  104. package/.pi/lib/harness-lens/clients/git-guard.ts +31 -0
  105. package/.pi/lib/harness-lens/clients/indent-retarget.ts +90 -0
  106. package/.pi/lib/harness-lens/clients/installer/index.ts +2368 -0
  107. package/.pi/lib/harness-lens/clients/latency-logger.ts +80 -0
  108. package/.pi/lib/harness-lens/clients/lens-config.ts +43 -0
  109. package/.pi/lib/harness-lens/clients/lens-events.ts +164 -0
  110. package/.pi/lib/harness-lens/clients/lsp/aggregation.ts +91 -0
  111. package/.pi/lib/harness-lens/clients/lsp/client.ts +1466 -0
  112. package/.pi/lib/harness-lens/clients/lsp/config.ts +216 -0
  113. package/.pi/lib/harness-lens/clients/lsp/edits.ts +297 -0
  114. package/.pi/lib/harness-lens/clients/lsp/index.ts +1355 -0
  115. package/.pi/lib/harness-lens/clients/lsp/interactive-install.ts +424 -0
  116. package/.pi/lib/harness-lens/clients/lsp/language.ts +223 -0
  117. package/.pi/lib/harness-lens/clients/lsp/launch.ts +939 -0
  118. package/.pi/lib/harness-lens/clients/lsp/lsp-index.ts +11 -0
  119. package/.pi/lib/harness-lens/clients/lsp/path-utils.ts +12 -0
  120. package/.pi/lib/harness-lens/clients/lsp/server-strategies.ts +81 -0
  121. package/.pi/lib/harness-lens/clients/lsp/server.ts +1971 -0
  122. package/.pi/lib/harness-lens/clients/path-utils.ts +182 -0
  123. package/.pi/lib/harness-lens/clients/pipeline.ts +360 -0
  124. package/.pi/lib/harness-lens/clients/project-profile.ts +117 -0
  125. package/.pi/lib/harness-lens/clients/runtime-agent-end.ts +112 -0
  126. package/.pi/lib/harness-lens/clients/runtime-config.ts +33 -0
  127. package/.pi/lib/harness-lens/clients/runtime-coordinator.ts +186 -0
  128. package/.pi/lib/harness-lens/clients/runtime-tool-result.ts +171 -0
  129. package/.pi/lib/harness-lens/clients/safe-spawn.ts +339 -0
  130. package/.pi/lib/harness-lens/clients/secrets-scanner.ts +214 -0
  131. package/.pi/lib/harness-lens/clients/tool-policy.ts +2072 -0
  132. package/.pi/lib/harness-lens/clients/types.ts +59 -0
  133. package/.pi/lib/harness-lens/clients/widget-state.ts +283 -0
  134. package/.pi/lib/harness-lens/index.ts +532 -0
  135. package/.pi/lib/harness-lens/tools/lsp-diagnostics.ts +706 -0
  136. package/.pi/lib/harness-lens/tools/lsp-navigation.ts +1246 -0
  137. package/.pi/{extensions/lib → lib}/harness-posthog.ts +3 -0
  138. package/.pi/lib/harness-run-context-responses.ts +9 -0
  139. package/.pi/lib/harness-run-context.ts +0 -2
  140. package/.pi/{extensions/lib/spawn-policy.ts → lib/harness-spawn-policy.ts} +1 -0
  141. package/.pi/{extensions/lib → lib}/harness-spawn-topology.ts +1 -1
  142. package/.pi/lib/harness-subagent-auth.ts +51 -0
  143. package/.pi/{extensions/lib → lib}/harness-subagent-precheck.ts +10 -7
  144. package/.pi/{extensions/lib → lib}/harness-subagent-submit-pipeline.ts +3 -3
  145. package/.pi/lib/harness-subagent-submit-register.ts +163 -0
  146. package/.pi/{extensions/lib → lib}/harness-subagent-submit-registry.ts +1 -37
  147. package/.pi/{extensions/lib → lib}/harness-subagents-bridge.ts +53 -14
  148. package/.pi/{extensions/lib → lib}/harness-subprocess-bootstrap.ts +1 -1
  149. package/.pi/{extensions/lib → lib}/plan-approval/create-plan.ts +2 -2
  150. package/.pi/{extensions/lib → lib}/plan-approval/format-plan.ts +2 -2
  151. package/.pi/{extensions/lib → lib}/plan-approval/plan-review.ts +162 -201
  152. package/.pi/{extensions/lib → lib}/plan-approval/render.ts +1 -1
  153. package/.pi/{extensions/lib → lib}/plan-approval/resolve-disk.ts +2 -2
  154. package/.pi/{extensions/lib → lib}/plan-approval/types.ts +1 -1
  155. package/.pi/{extensions/lib → lib}/plan-approval/validate.ts +3 -3
  156. package/.pi/{extensions/lib → lib}/plan-debate-envelope.ts +1 -1
  157. package/.pi/{extensions/lib → lib}/plan-debate-gate.ts +1 -1
  158. package/.pi/{extensions/lib → lib}/plan-debate-lane.ts +1 -4
  159. package/.pi/{extensions/lib → lib}/plan-messenger.ts +1 -1
  160. package/.pi/prompts/harness-plan.md +1 -1
  161. package/.pi/prompts/harness-setup.md +37 -64
  162. package/.pi/scripts/README.md +2 -5
  163. package/.pi/scripts/generate-agents-policy-yaml.mjs +148 -0
  164. package/.pi/scripts/harness-agents-manifest.mjs +60 -3
  165. package/.pi/scripts/harness-agt-doctor.ts +36 -0
  166. package/.pi/scripts/harness-cli-verify.sh +9 -2
  167. package/.pi/scripts/harness-verify.mjs +113 -39
  168. package/.pi/scripts/harness-web-policy-guard.mjs +2 -2
  169. package/.pi/scripts/validate-plan-dag.mjs +65 -74
  170. package/.pi/scripts/vendor-pi-vcc-settings.stub.ts +2 -2
  171. package/.pi/scripts/vendor-sync-pi-vcc.sh +1 -1
  172. package/.pi/skills/architecture/broker-domain/SKILL.md +65 -0
  173. package/.pi/skills/architecture/cqrs/SKILL.md +63 -0
  174. package/.pi/skills/architecture/event-driven/SKILL.md +60 -0
  175. package/.pi/skills/architecture/hexagonal-ports-adapters/SKILL.md +66 -0
  176. package/.pi/skills/architecture/layered/SKILL.md +68 -0
  177. package/.pi/skills/architecture/microkernel/SKILL.md +62 -0
  178. package/.pi/skills/architecture/microservices/SKILL.md +64 -0
  179. package/.pi/skills/architecture/modular-monolith/SKILL.md +65 -0
  180. package/.pi/skills/architecture/orchestration-driven-soa/SKILL.md +61 -0
  181. package/.pi/skills/architecture/pipeline/SKILL.md +63 -0
  182. package/.pi/skills/architecture/service-based/SKILL.md +64 -0
  183. package/.pi/skills/architecture/service-mesh/SKILL.md +60 -0
  184. package/.pi/skills/architecture/space-based/SKILL.md +60 -0
  185. package/.pi/skills/ast-grep/SKILL.md +40 -321
  186. package/.pi/skills/delivery/debugging-discipline/SKILL.md +36 -0
  187. package/.pi/skills/delivery/documentation-update/SKILL.md +33 -0
  188. package/.pi/skills/delivery/requirements-to-implementation/SKILL.md +34 -0
  189. package/.pi/skills/delivery/risk-based-verification/SKILL.md +43 -0
  190. package/.pi/skills/delivery/tradeoff-analysis/SKILL.md +34 -0
  191. package/.pi/skills/engineering/api-contract-design/SKILL.md +38 -0
  192. package/.pi/skills/engineering/cohesion-coupling/SKILL.md +43 -0
  193. package/.pi/skills/engineering/complexity-control/SKILL.md +31 -0
  194. package/.pi/skills/engineering/defensive-programming/SKILL.md +38 -0
  195. package/.pi/skills/engineering/dependency-management/SKILL.md +29 -0
  196. package/.pi/skills/engineering/domain-modeling/SKILL.md +32 -0
  197. package/.pi/skills/engineering/error-handling/SKILL.md +37 -0
  198. package/.pi/skills/engineering/legacy-code-seams/SKILL.md +35 -0
  199. package/.pi/skills/engineering/naming-and-intent/SKILL.md +29 -0
  200. package/.pi/skills/engineering/refactoring-safe-evolution/SKILL.md +35 -0
  201. package/.pi/skills/engineering/routine-function-design/SKILL.md +34 -0
  202. package/.pi/skills/engineering/small-change-discipline/SKILL.md +35 -0
  203. package/.pi/skills/lsp-navigation/SKILL.md +89 -0
  204. package/.pi/skills/quality/code-review-self-check/SKILL.md +35 -0
  205. package/.pi/skills/quality/privacy-data-handling/SKILL.md +26 -0
  206. package/.pi/skills/quality/security-review/SKILL.md +34 -0
  207. package/.pi/skills/quality/test-strategy/SKILL.md +33 -0
  208. package/.pi/skills/quality/testability-design/SKILL.md +33 -0
  209. package/.pi/skills/systems/concurrency-safety/SKILL.md +32 -0
  210. package/.pi/skills/systems/data-modeling-migrations/SKILL.md +31 -0
  211. package/.pi/skills/systems/observability-instrumentation/SKILL.md +32 -0
  212. package/.pi/skills/systems/performance-measurement/SKILL.md +35 -0
  213. package/.pi/skills/systems/reliability-design/SKILL.md +32 -0
  214. package/.sentrux/rules.toml +20 -4
  215. package/AGENTS.md +5 -0
  216. package/CHANGELOG.md +14 -0
  217. package/README.md +3 -12
  218. package/THIRD_PARTY_NOTICES.md +12 -21
  219. package/package.json +15 -7
  220. package/vendor/pi-subagents/src/agents.ts +45 -1
  221. package/vendor/pi-subagents/src/subagents.ts +866 -811
  222. package/vendor/pi-vcc/src/core/brief.ts +68 -99
  223. package/vendor/pi-vcc/src/core/settings.ts +2 -2
  224. package/.agents/skills/caveman/SKILL.md +0 -67
  225. package/.pi/agents/harness/meta-optimizer.md +0 -36
  226. package/.pi/extensions/lib/ask-user/dialog.ts +0 -260
  227. package/.pi/extensions/lib/harness-subagent-auth.ts +0 -207
  228. package/.pi/extensions/lib/harness-subagent-policy.ts +0 -236
  229. package/.pi/extensions/pi-model-router-harness.ts +0 -42
  230. package/.pi/harness/evolution/meta-optimizer.mjs +0 -99
  231. package/.pi/harness/specs/router-tuning-proposal.schema.json +0 -114
  232. package/.pi/model-router.example.json +0 -36
  233. package/.pi/prompts/harness-critic.md +0 -10
  234. package/.pi/prompts/harness-eval.md +0 -10
  235. package/.pi/prompts/harness-router-tune.md +0 -52
  236. package/.pi/scripts/harness-generate-model-router.mjs +0 -327
  237. package/.pi/scripts/harness-model-router-routing.test.mjs +0 -97
  238. package/.pi/scripts/harness-sync-model-router.mjs +0 -97
  239. package/.pi/scripts/vendor-sync-pi-model-router.sh +0 -47
  240. package/vendor/pi-model-router/.prettierignore +0 -4
  241. package/vendor/pi-model-router/.prettierrc +0 -5
  242. package/vendor/pi-model-router/AGENTS.md +0 -39
  243. package/vendor/pi-model-router/LICENSE +0 -21
  244. package/vendor/pi-model-router/README.md +0 -99
  245. package/vendor/pi-model-router/UPSTREAM_PIN.md +0 -10
  246. package/vendor/pi-model-router/docs/ARCHITECTURE.md +0 -54
  247. package/vendor/pi-model-router/extensions/commands.ts +0 -720
  248. package/vendor/pi-model-router/extensions/config.ts +0 -348
  249. package/vendor/pi-model-router/extensions/constants.ts +0 -1
  250. package/vendor/pi-model-router/extensions/index.ts +0 -478
  251. package/vendor/pi-model-router/extensions/provider.ts +0 -580
  252. package/vendor/pi-model-router/extensions/routing.ts +0 -564
  253. package/vendor/pi-model-router/extensions/state.ts +0 -52
  254. package/vendor/pi-model-router/extensions/types.ts +0 -95
  255. package/vendor/pi-model-router/extensions/ui.ts +0 -144
  256. package/vendor/pi-model-router/model-router.example.json +0 -48
  257. package/vendor/pi-model-router/package.json +0 -48
  258. package/vendor/pi-model-router/tsconfig.json +0 -16
  259. /package/.pi/{prompts → harness/docs}/planning-rubrics.md +0 -0
  260. /package/.pi/{extensions/lib → lib}/ask-user/fallback.ts +0 -0
  261. /package/.pi/{extensions/lib → lib}/ask-user/render.ts +0 -0
  262. /package/.pi/{extensions/lib → lib}/ask-user/schema.ts +0 -0
  263. /package/.pi/{extensions/lib → lib}/ask-user/types.ts +0 -0
  264. /package/.pi/{extensions/lib → lib}/ask-user/validate-core.mjs +0 -0
  265. /package/.pi/{extensions/lib → lib}/ask-user/validate.ts +0 -0
  266. /package/.pi/{extensions/lib → lib}/harness-cocoindex-refresh.ts +0 -0
  267. /package/.pi/{extensions/lib → lib}/harness-paths.ts +0 -0
  268. /package/.pi/{extensions/lib → lib}/harness-spawn-budget.ts +0 -0
  269. /package/.pi/{extensions/lib → lib}/harness-vcc-settings.ts +0 -0
  270. /package/.pi/{extensions/lib → lib}/harness-web/run-cli.ts +0 -0
  271. /package/.pi/{extensions/lib → lib}/plan-approval/dialog.ts +0 -0
  272. /package/.pi/{extensions/lib → lib}/plan-approval/schema.ts +0 -0
  273. /package/.pi/{extensions/lib → lib}/plan-approval-readiness.ts +0 -0
  274. /package/.pi/{extensions/lib → lib}/plan-debate-eligibility.ts +0 -0
  275. /package/.pi/{extensions/lib → lib}/plan-debate-focus.ts +0 -0
  276. /package/.pi/{extensions/lib → lib}/plan-debate-id.ts +0 -0
  277. /package/.pi/{extensions/lib → lib}/plan-debate-lanes.ts +0 -0
  278. /package/.pi/{extensions/lib → lib}/plan-debate-round-status.ts +0 -0
  279. /package/.pi/{extensions/lib → lib}/plan-debate-write-guard.ts +0 -0
  280. /package/.pi/{extensions/lib → lib}/plan-review-gate.ts +0 -0
  281. /package/.pi/{extensions/lib → lib}/plan-review-integrator-rules.ts +0 -0
  282. /package/.pi/{extensions/lib → lib}/plan-scope-guard.ts +0 -0
  283. /package/.pi/{extensions/lib → lib}/posthog-client.ts +0 -0
  284. /package/.pi/{extensions/lib → lib}/posthog-node.d.ts +0 -0
@@ -0,0 +1,532 @@
1
+ /**
2
+ * Harness-native lens extension entry.
3
+ * Edit autopatch, secrets block, deferred format, LSP delegate.
4
+ */
5
+
6
+ import * as nodeFs from "node:fs";
7
+ import * as path from "node:path";
8
+ import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
9
+ import { isToolCallEventType } from "@earendil-works/pi-coding-agent";
10
+ import {
11
+ tryCorrectIndentationMismatch,
12
+ tryCorrectIndentationMismatchFromContent,
13
+ } from "./clients/edit-autopatch.js";
14
+ import { detectFileKind } from "./clients/file-kinds.js";
15
+ import { isPathIgnoredByProject } from "./clients/file-utils.js";
16
+ import {
17
+ getFormatService,
18
+ resetFormatService,
19
+ } from "./clients/format-service.js";
20
+ import {
21
+ evaluateGitGuard,
22
+ isGitCommitOrPushAttempt,
23
+ } from "./clients/git-guard.js";
24
+ import { retargetReplacementIndentation } from "./clients/indent-retarget.js";
25
+ import { ensureTool } from "./clients/installer/index.js";
26
+ import {
27
+ loadPiLensGlobalConfig,
28
+ resolvePiLensFlag,
29
+ } from "./clients/lens-config.js";
30
+ import { initLensEvents } from "./clients/lens-events.js";
31
+ import { initLSPConfig } from "./clients/lsp/config.js";
32
+ import { getLSPService, resetLSPService } from "./clients/lsp/index.js";
33
+ import { isExternalOrVendorFile } from "./clients/path-utils.js";
34
+ import {
35
+ detectProjectProfile,
36
+ lspPreinstallTools,
37
+ } from "./clients/project-profile.js";
38
+ import { handleAgentEnd } from "./clients/runtime-agent-end.js";
39
+ import { RuntimeCoordinator } from "./clients/runtime-coordinator.js";
40
+ import {
41
+ clearLastAnalyzedStateCache,
42
+ handleToolResult,
43
+ } from "./clients/runtime-tool-result.js";
44
+ import { clearWidgetState } from "./clients/widget-state.js";
45
+ import { createLspDiagnosticsTool } from "./tools/lsp-diagnostics.js";
46
+ import { createLspNavigationTool } from "./tools/lsp-navigation.js";
47
+
48
+ const DEBUG_LOG_DIR = path.join(process.cwd(), ".pi", "harness", ".lens");
49
+ const DEBUG_LOG = path.join(DEBUG_LOG_DIR, "sessionstart.log");
50
+
51
+ function dbg(msg: string): void {
52
+ if (process.env.PI_LENS_TEST_MODE === "1" || process.env.VITEST) return;
53
+ const line = `[${new Date().toISOString()}] ${msg}\n`;
54
+ try {
55
+ nodeFs.mkdirSync(DEBUG_LOG_DIR, { recursive: true });
56
+ nodeFs.appendFileSync(DEBUG_LOG, line);
57
+ } catch {
58
+ // best-effort debug log
59
+ }
60
+ }
61
+
62
+ const runtime = new RuntimeCoordinator();
63
+ const lspConfigInitializedCwds = new Set<string>();
64
+
65
+ async function ensureLSPConfigInitialized(cwd: string): Promise<void> {
66
+ const normalized = path.resolve(cwd);
67
+ if (lspConfigInitializedCwds.has(normalized)) return;
68
+ await initLSPConfig(normalized);
69
+ lspConfigInitializedCwds.add(normalized);
70
+ }
71
+
72
+ function getLensFlag(
73
+ name: string,
74
+ getFlag: (name: string) => boolean | string | undefined,
75
+ global: ReturnType<typeof loadPiLensGlobalConfig>,
76
+ ): boolean | string | undefined {
77
+ return resolvePiLensFlag(name, getFlag, global);
78
+ }
79
+
80
+ function resolveToolCallFilePath(
81
+ rawFilePath: string | undefined,
82
+ cwd: string | undefined,
83
+ projectRoot: string,
84
+ ): string | undefined {
85
+ if (!rawFilePath) return undefined;
86
+ if (path.isAbsolute(rawFilePath)) return rawFilePath;
87
+ return path.resolve(cwd ?? projectRoot, rawFilePath);
88
+ }
89
+
90
+ function getToolCallRawFilePath(
91
+ toolName: string,
92
+ event: { input?: unknown },
93
+ ): string | undefined {
94
+ const inputObj = (event.input ?? {}) as Record<string, unknown>;
95
+ if (
96
+ isToolCallEventType(
97
+ "write",
98
+ event as Parameters<typeof isToolCallEventType>[1],
99
+ ) ||
100
+ isToolCallEventType(
101
+ "edit",
102
+ event as Parameters<typeof isToolCallEventType>[1],
103
+ )
104
+ ) {
105
+ const filePath = (event.input as { path?: unknown }).path;
106
+ return typeof filePath === "string" ? filePath : undefined;
107
+ }
108
+ if (toolName === "read") {
109
+ if (typeof inputObj.path === "string") return inputObj.path;
110
+ if (typeof inputObj.filePath === "string") return inputObj.filePath;
111
+ }
112
+ if (toolName === "lsp_navigation" && typeof inputObj.filePath === "string") {
113
+ return inputObj.filePath;
114
+ }
115
+ return undefined;
116
+ }
117
+
118
+ function shouldSkipLspAutoTouch(
119
+ filePath: string,
120
+ projectRoot: string,
121
+ ): boolean {
122
+ const normalized = path.resolve(filePath).replace(/\\/g, "/").toLowerCase();
123
+ if (normalized.includes("/.pi/harness/.lens/")) return true;
124
+ if (normalized.includes("/.harness/")) return true;
125
+ if (isExternalOrVendorFile(filePath, projectRoot)) return true;
126
+ return false;
127
+ }
128
+
129
+ function isLspCapableFile(filePath: string): boolean {
130
+ return getLSPService().supportsLSP(filePath) || !!detectFileKind(filePath);
131
+ }
132
+
133
+ function normalizeOldTextForMatch(text: string): string {
134
+ return text
135
+ .replace(/\r\n/g, "\n")
136
+ .split("\n")
137
+ .map((line) => line.trimEnd())
138
+ .join("\n");
139
+ }
140
+
141
+ function countTextOccurrences(haystack: string, needle: string): number {
142
+ if (!needle) return 0;
143
+ let count = 0;
144
+ let pos = 0;
145
+ while (pos < haystack.length) {
146
+ const idx = haystack.indexOf(needle, pos);
147
+ if (idx === -1) break;
148
+ count += 1;
149
+ pos = idx + needle.length;
150
+ }
151
+ return count;
152
+ }
153
+
154
+ function countOldTextMatches(
155
+ filePath: string,
156
+ oldText: string,
157
+ cachedNormalizedContent?: string,
158
+ ): number {
159
+ try {
160
+ const content =
161
+ cachedNormalizedContent ??
162
+ normalizeOldTextForMatch(nodeFs.readFileSync(filePath, "utf-8"));
163
+ return countTextOccurrences(content, normalizeOldTextForMatch(oldText));
164
+ } catch {
165
+ return 0;
166
+ }
167
+ }
168
+
169
+ function isIndentationOnlyChange(before: string, after: string): boolean {
170
+ const beforeLines = before.replace(/\r\n/g, "\n").split("\n");
171
+ const afterLines = after.replace(/\r\n/g, "\n").split("\n");
172
+ if (beforeLines.length !== afterLines.length) return false;
173
+ return beforeLines.every(
174
+ (line, index) => line.trim() === afterLines[index].trim(),
175
+ );
176
+ }
177
+
178
+ type EditIndentTarget = {
179
+ label: string;
180
+ value: string;
181
+ newText: string | undefined;
182
+ apply: (corrected: string) => void;
183
+ applyNewText: (corrected: string) => void;
184
+ };
185
+
186
+ function applyEditAutopatch(
187
+ filePath: string,
188
+ editInput: {
189
+ oldText?: string;
190
+ newText?: string;
191
+ edits?: Array<{ oldText?: string; newText?: string }>;
192
+ },
193
+ ): { block: true; reason: string } | undefined {
194
+ const oldTexts: EditIndentTarget[] = editInput.oldText
195
+ ? [
196
+ {
197
+ label: "oldText",
198
+ value: editInput.oldText,
199
+ newText: editInput.newText,
200
+ apply: (corrected) => {
201
+ editInput.oldText = corrected;
202
+ },
203
+ applyNewText: (corrected) => {
204
+ editInput.newText = corrected;
205
+ },
206
+ },
207
+ ]
208
+ : (editInput.edits ?? [])
209
+ .map((entry, index) =>
210
+ entry.oldText
211
+ ? {
212
+ label: `edits[${index}].oldText`,
213
+ value: entry.oldText,
214
+ newText: entry.newText,
215
+ apply: (corrected: string) => {
216
+ entry.oldText = corrected;
217
+ },
218
+ applyNewText: (corrected: string) => {
219
+ entry.newText = corrected;
220
+ },
221
+ }
222
+ : null,
223
+ )
224
+ .filter((entry): entry is EditIndentTarget => entry !== null);
225
+
226
+ let crlfContent: string | undefined;
227
+ let matchNormalizedContent: string | undefined;
228
+ try {
229
+ const raw = nodeFs.readFileSync(filePath, "utf-8");
230
+ crlfContent = raw.replace(/\r\n/g, "\n");
231
+ matchNormalizedContent = normalizeOldTextForMatch(raw);
232
+ } catch {
233
+ return undefined;
234
+ }
235
+
236
+ if (matchNormalizedContent !== undefined) {
237
+ for (const entry of oldTexts) {
238
+ const normalizedValue = entry.value.replace(/\r\n/g, "\n");
239
+ const stripped = normalizedValue
240
+ .split("\n")
241
+ .map((line) => line.trimEnd())
242
+ .join("\n");
243
+ if (stripped === normalizedValue) continue;
244
+ if (
245
+ countOldTextMatches(filePath, stripped, matchNormalizedContent) !== 1
246
+ ) {
247
+ continue;
248
+ }
249
+ entry.apply(stripped);
250
+ entry.value = stripped;
251
+ }
252
+ }
253
+
254
+ const correctedOldTexts = oldTexts
255
+ .map(({ label, value, newText, apply, applyNewText }) => {
256
+ const corrected =
257
+ crlfContent !== undefined
258
+ ? tryCorrectIndentationMismatchFromContent(value, crlfContent)
259
+ : tryCorrectIndentationMismatch(value, filePath);
260
+ if (corrected === undefined) return undefined;
261
+ return {
262
+ label,
263
+ value,
264
+ newText,
265
+ corrected,
266
+ apply,
267
+ applyNewText,
268
+ currentMatchCount: countOldTextMatches(
269
+ filePath,
270
+ value,
271
+ matchNormalizedContent,
272
+ ),
273
+ correctedMatchCount: countOldTextMatches(
274
+ filePath,
275
+ corrected,
276
+ matchNormalizedContent,
277
+ ),
278
+ indentationOnly: isIndentationOnlyChange(value, corrected),
279
+ };
280
+ })
281
+ .filter(
282
+ (
283
+ entry,
284
+ ): entry is EditIndentTarget & {
285
+ corrected: string;
286
+ currentMatchCount: number;
287
+ correctedMatchCount: number;
288
+ indentationOnly: boolean;
289
+ } => entry !== undefined,
290
+ );
291
+
292
+ if (correctedOldTexts.length === 0) return undefined;
293
+
294
+ const unsafe = correctedOldTexts.filter(
295
+ (entry) =>
296
+ !entry.indentationOnly ||
297
+ entry.currentMatchCount !== 0 ||
298
+ entry.correctedMatchCount !== 1,
299
+ );
300
+ if (unsafe.length > 0) {
301
+ const details = unsafe
302
+ .map(({ label, value, correctedMatchCount, indentationOnly }) => {
303
+ const preview = value.trimStart().slice(0, 60).replace(/\n/g, "↵");
304
+ const reason = !indentationOnly
305
+ ? "the proposed correction was not indentation-only"
306
+ : `the corrected oldText matches ${correctedMatchCount} locations`;
307
+ return `${label} ("${preview}…") cannot be auto-patched because ${reason}.`;
308
+ })
309
+ .join("\n");
310
+ return {
311
+ block: true,
312
+ reason:
313
+ `🔄 RETRYABLE — Indentation mismatch detected\n\n` +
314
+ `harness-lens can auto-patch indentation-only oldText mismatches when the corrected text matches exactly one location.\n\n` +
315
+ `${details}\n\n` +
316
+ `Next action: re-read the relevant section, then retry with oldText copied verbatim from the read output.`,
317
+ };
318
+ }
319
+
320
+ for (const entry of correctedOldTexts) {
321
+ entry.apply(entry.corrected);
322
+ const correctedNewText = entry.newText
323
+ ? retargetReplacementIndentation(
324
+ entry.newText,
325
+ entry.value,
326
+ entry.corrected,
327
+ )
328
+ : undefined;
329
+ if (correctedNewText !== undefined) entry.applyNewText(correctedNewText);
330
+ }
331
+ return undefined;
332
+ }
333
+
334
+ export default function harnessLensExtension(pi: ExtensionAPI): void {
335
+ initLensEvents(pi);
336
+ const globalConfig = loadPiLensGlobalConfig();
337
+ let lensEnabled = !globalConfig.noLens;
338
+
339
+ type PiWithFlags = ExtensionAPI & {
340
+ getFlag?: (name: string) => boolean | undefined;
341
+ };
342
+ const piFlags = pi as PiWithFlags;
343
+ const readCliFlag = (name: string): boolean | undefined => {
344
+ if (typeof piFlags.getFlag === "function") return piFlags.getFlag(name);
345
+ return process.argv.includes(`--${name}`) ? true : undefined;
346
+ };
347
+ const getFlag = (name: string) =>
348
+ getLensFlag(name, readCliFlag, globalConfig);
349
+
350
+ pi.registerFlag("no-lens", {
351
+ description: "Disable harness-lens for this session.",
352
+ type: "boolean",
353
+ default: false,
354
+ });
355
+ pi.registerFlag("no-lsp", {
356
+ description: "Disable LSP auto-touch and lsp_* tools backing servers.",
357
+ type: "boolean",
358
+ default: false,
359
+ });
360
+ pi.registerFlag("no-autoformat", {
361
+ description: "Disable auto-format on write/edit.",
362
+ type: "boolean",
363
+ default: false,
364
+ });
365
+ pi.registerFlag("immediate-format", {
366
+ description: "Format during tool_result instead of deferring to agent_end.",
367
+ type: "boolean",
368
+ default: false,
369
+ });
370
+ pi.registerFlag("lens-guard", {
371
+ description: "Block git commit/push while unresolved lens blockers exist.",
372
+ type: "boolean",
373
+ default: false,
374
+ });
375
+
376
+ pi.registerTool(createLspDiagnosticsTool());
377
+ pi.registerTool(createLspNavigationTool());
378
+
379
+ pi.on("session_start", async (_event, ctx) => {
380
+ if (getFlag("no-lens")) {
381
+ lensEnabled = false;
382
+ return;
383
+ }
384
+ lensEnabled = true;
385
+ runtime.resetForSession();
386
+ clearWidgetState();
387
+ resetFormatService();
388
+ if (!getFlag("no-lsp")) resetLSPService();
389
+
390
+ const cwd = ctx.cwd ?? process.cwd();
391
+ runtime.projectRoot = cwd;
392
+ clearLastAnalyzedStateCache();
393
+ lspConfigInitializedCwds.clear();
394
+
395
+ try {
396
+ await ensureLSPConfigInitialized(cwd);
397
+ } catch (err) {
398
+ dbg(`session_start lsp config init failed: ${err}`);
399
+ }
400
+
401
+ if (getFlag("no-lsp")) return;
402
+
403
+ const profile = detectProjectProfile(cwd);
404
+ const tools = lspPreinstallTools(profile);
405
+ dbg(
406
+ `session_start profile kinds=${profile.detectedKinds.join(",")} lsp=${tools.join(",")}`,
407
+ );
408
+ for (const toolId of tools) {
409
+ void ensureTool(toolId).catch((err) => {
410
+ dbg(`session_start lsp preinstall ${toolId} failed: ${err}`);
411
+ });
412
+ }
413
+ });
414
+
415
+ pi.on("turn_start", () => {
416
+ if (!lensEnabled) return;
417
+ runtime.beginTurn();
418
+ clearLastAnalyzedStateCache();
419
+ });
420
+
421
+ pi.on("tool_call", async (event, ctx) => {
422
+ if (!lensEnabled) return;
423
+
424
+ const toolName = (event as { toolName?: string }).toolName ?? "";
425
+ if (
426
+ getFlag("lens-guard") &&
427
+ isGitCommitOrPushAttempt(toolName, event.input)
428
+ ) {
429
+ const guard = evaluateGitGuard(runtime);
430
+ if (guard.block) return { block: true, reason: guard.reason };
431
+ }
432
+
433
+ const rawFilePath = getToolCallRawFilePath(toolName, event);
434
+ const filePath = resolveToolCallFilePath(
435
+ rawFilePath,
436
+ ctx.cwd,
437
+ runtime.projectRoot,
438
+ );
439
+
440
+ if (!getFlag("no-lsp")) {
441
+ try {
442
+ await ensureLSPConfigInitialized(
443
+ filePath ? path.dirname(filePath) : (ctx.cwd ?? runtime.projectRoot),
444
+ );
445
+ } catch (err) {
446
+ dbg(`tool_call lsp config init failed: ${err}`);
447
+ }
448
+ }
449
+
450
+ if (!filePath || !nodeFs.existsSync(filePath)) return;
451
+ if (isPathIgnoredByProject(filePath, runtime.projectRoot, false)) return;
452
+
453
+ if (
454
+ !getFlag("no-lsp") &&
455
+ isLspCapableFile(filePath) &&
456
+ !shouldSkipLspAutoTouch(filePath, runtime.projectRoot)
457
+ ) {
458
+ const shouldWarmRead =
459
+ toolName === "read" && runtime.shouldWarmLspOnRead(filePath);
460
+ const shouldTouch =
461
+ toolName === "write" ||
462
+ toolName === "edit" ||
463
+ toolName === "lsp_navigation" ||
464
+ shouldWarmRead;
465
+ if (shouldTouch) {
466
+ try {
467
+ const content = nodeFs.readFileSync(filePath, "utf-8");
468
+ if (toolName === "read") runtime.markLspReadWarmStarted(filePath);
469
+ void getLSPService()
470
+ .touchFile(filePath, content, {
471
+ diagnostics: "none",
472
+ source: `tool_call:${toolName}`,
473
+ })
474
+ .then((result) => {
475
+ if (toolName === "read") {
476
+ if (result === undefined) {
477
+ runtime.clearLspReadWarmState(filePath);
478
+ } else {
479
+ runtime.markLspReadWarmCompleted(filePath);
480
+ }
481
+ }
482
+ })
483
+ .catch((err) => {
484
+ if (toolName === "read") runtime.clearLspReadWarmState(filePath);
485
+ dbg(`lsp auto-touch failed: ${err}`);
486
+ });
487
+ } catch {
488
+ if (toolName === "read") runtime.clearLspReadWarmState(filePath);
489
+ }
490
+ }
491
+ }
492
+
493
+ if (
494
+ isToolCallEventType(
495
+ "edit",
496
+ event as Parameters<typeof isToolCallEventType>[1],
497
+ )
498
+ ) {
499
+ const editInput = (event as { input?: unknown }).input as {
500
+ oldText?: string;
501
+ newText?: string;
502
+ edits?: Array<{ oldText?: string; newText?: string }>;
503
+ };
504
+ const block = applyEditAutopatch(filePath, editInput);
505
+ if (block) return block;
506
+ }
507
+ });
508
+
509
+ pi.on("tool_result", async (event, _ctx) => {
510
+ if (!lensEnabled) return;
511
+ return handleToolResult({
512
+ event: event as Parameters<typeof handleToolResult>[0]["event"],
513
+ getFlag,
514
+ dbg,
515
+ runtime,
516
+ resetLSPService,
517
+ });
518
+ });
519
+
520
+ pi.on("agent_end", async (_event, ctx) => {
521
+ if (!lensEnabled) return;
522
+ await handleAgentEnd({
523
+ ctxCwd: ctx.cwd,
524
+ getFlag,
525
+ notify: (msg, level) => ctx.ui?.notify(msg, level),
526
+ dbg,
527
+ runtime,
528
+ getFormatService: () =>
529
+ getFormatService(runtime.telemetrySessionId, !getFlag("no-autoformat")),
530
+ });
531
+ });
532
+ }