ultimate-pi 0.18.0 → 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 (314) hide show
  1. package/.agents/skills/harness-debate-plan/SKILL.md +1 -1
  2. package/.agents/skills/harness-decisions/SKILL.md +2 -3
  3. package/.agents/skills/harness-governor/SKILL.md +6 -5
  4. package/.agents/skills/harness-orchestration/SKILL.md +4 -4
  5. package/.agents/skills/harness-review/SKILL.md +7 -7
  6. package/.agents/skills/harness-sentrux-setup/SKILL.md +4 -3
  7. package/.agents/skills/harness-steer/SKILL.md +1 -1
  8. package/.agents/skills/sentrux/SKILL.md +9 -9
  9. package/.pi/PACKAGING.md +4 -4
  10. package/.pi/SYSTEM.md +54 -120
  11. package/.pi/agents/harness/incident-recorder.md +0 -1
  12. package/.pi/agents/harness/planning/decompose.md +1 -3
  13. package/.pi/agents/harness/planning/execution-plan-author.md +0 -2
  14. package/.pi/agents/harness/planning/hypothesis-validator.md +0 -2
  15. package/.pi/agents/harness/planning/hypothesis.md +0 -2
  16. package/.pi/agents/harness/planning/implementation-researcher.md +0 -2
  17. package/.pi/agents/harness/planning/plan-adversary.md +0 -2
  18. package/.pi/agents/harness/planning/plan-evaluator.md +1 -3
  19. package/.pi/agents/harness/planning/planning-context.md +0 -2
  20. package/.pi/agents/harness/planning/review-integrator.md +0 -2
  21. package/.pi/agents/harness/planning/sprint-contract-auditor.md +0 -2
  22. package/.pi/agents/harness/planning/stack-researcher.md +0 -2
  23. package/.pi/agents/harness/{adversary.md → reviewing/adversary.md} +0 -2
  24. package/.pi/agents/harness/{evaluator.md → reviewing/evaluator.md} +0 -2
  25. package/.pi/agents/harness/{tie-breaker.md → reviewing/tie-breaker.md} +0 -2
  26. package/.pi/agents/harness/{executor.md → running/executor.md} +0 -2
  27. package/.pi/agents/harness/sentrux-bootstrap.md +0 -1
  28. package/.pi/agents/harness/sentrux-steward.md +0 -2
  29. package/.pi/agents/harness/trace-librarian.md +0 -1
  30. package/.pi/extensions/00-harness-project-control.ts +133 -0
  31. package/.pi/extensions/00-posthog-network-bootstrap.ts +1 -1
  32. package/.pi/extensions/agt-kill-switch.ts +57 -0
  33. package/.pi/extensions/agt-prompt-guard.ts +32 -0
  34. package/.pi/extensions/budget-guard.ts +2 -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 +7 -5
  39. package/.pi/extensions/harness-ask-user.ts +8 -8
  40. package/.pi/extensions/harness-debate-tools.ts +27 -43
  41. package/.pi/extensions/harness-lens.ts +94 -0
  42. package/.pi/extensions/harness-live-widget.ts +33 -2
  43. package/.pi/extensions/harness-plan-approval.ts +12 -12
  44. package/.pi/extensions/harness-run-context.ts +1214 -852
  45. package/.pi/extensions/harness-subagent-governance.ts +8 -0
  46. package/.pi/extensions/harness-subagent-submit.ts +36 -164
  47. package/.pi/extensions/harness-subagents.ts +4 -4
  48. package/.pi/extensions/harness-telemetry.ts +3 -1
  49. package/.pi/extensions/harness-web-tools.ts +3 -3
  50. package/.pi/extensions/observation-bus.ts +2 -0
  51. package/.pi/extensions/policy-gate.ts +27 -5
  52. package/.pi/extensions/review-integrity.ts +91 -10
  53. package/.pi/extensions/sentrux-rules-sync.ts +3 -1
  54. package/.pi/extensions/subagent-governance.ts +92 -0
  55. package/.pi/extensions/test-diff-integrity.ts +1 -0
  56. package/.pi/extensions/trace-recorder.ts +3 -1
  57. package/.pi/extensions/{ultimate-pi-vcc.ts → vcc-compaction.ts} +1 -1
  58. package/.pi/harness/README.md +6 -2
  59. package/.pi/harness/agents.manifest.json +38 -49
  60. package/.pi/harness/agents.policy.yaml +275 -0
  61. package/.pi/harness/corpus/graphify-kb-updater.config.json +55 -0
  62. package/.pi/harness/docs/adrs/0006-sentrux-dual-layer.md +2 -1
  63. package/.pi/harness/docs/adrs/0030-inhouse-vcc-compaction.md +1 -1
  64. package/.pi/harness/docs/adrs/0035-plan-phase-review-gate.md +1 -1
  65. package/.pi/harness/docs/adrs/0044-harness-steer-loop.md +3 -2
  66. package/.pi/harness/docs/adrs/0045-harness-lens-minimal-contract.md +49 -0
  67. package/.pi/harness/docs/adrs/0045-phase-scoped-agent-directories.md +33 -0
  68. package/.pi/harness/docs/adrs/0046-agt-policy-engine.md +51 -0
  69. package/.pi/harness/docs/adrs/0047-agt-layered-security.md +39 -0
  70. package/.pi/harness/docs/adrs/0048-tool-call-hook-order.md +25 -0
  71. package/.pi/harness/docs/adrs/0049-agents-policy-manifest.md +36 -0
  72. package/.pi/harness/docs/adrs/README.md +6 -0
  73. package/.pi/harness/docs/graphify-kb-updater-runbook.md +11 -5
  74. package/.pi/harness/docs/practice-map.md +2 -2
  75. package/.pi/harness/evolution/README.md +1 -2
  76. package/.pi/harness/examples/agents.policy.project.yaml +19 -0
  77. package/.pi/harness/examples/policies/custom-deny-bash.yaml +9 -0
  78. package/.pi/harness/policies/bash-denylists.yaml +5 -0
  79. package/.pi/harness/policies/defaults.yaml +51 -0
  80. package/.pi/harness/policies/orchestrator.yaml +18 -0
  81. package/.pi/harness/policies/phases.yaml +10 -0
  82. package/.pi/harness/policies/roles.yaml +5 -0
  83. package/.pi/harness/policies/web-guard.yaml +5 -0
  84. package/.pi/harness/policies/workflow-sequences.yaml +9 -0
  85. package/.pi/harness/sentrux/architecture.manifest.json +26 -4
  86. package/.pi/harness/specs/harness-spawn-context.schema.json +1 -1
  87. package/.pi/harness/specs/observation.schema.json +2 -1
  88. package/.pi/lib/agents-policy.d.mts +70 -0
  89. package/.pi/lib/agents-policy.mjs +325 -0
  90. package/.pi/lib/agents-policy.ts +19 -0
  91. package/.pi/lib/agt/audit-run-sink.ts +52 -0
  92. package/.pi/lib/agt/build-evaluation-context.ts +285 -0
  93. package/.pi/lib/agt/config.ts +28 -0
  94. package/.pi/lib/agt/delegation.ts +69 -0
  95. package/.pi/lib/agt/evaluate-policy.ts +56 -0
  96. package/.pi/lib/agt/identity-registry.ts +41 -0
  97. package/.pi/lib/agt/index.ts +55 -0
  98. package/.pi/lib/agt/kill-switch-state.ts +11 -0
  99. package/.pi/lib/agt/legacy-evaluate.ts +101 -0
  100. package/.pi/lib/agt/policy-engine.ts +154 -0
  101. package/.pi/lib/agt/rings.ts +21 -0
  102. package/.pi/lib/agt/sre-hooks.ts +45 -0
  103. package/.pi/lib/agt/trust-run-store.ts +26 -0
  104. package/.pi/lib/agt/workflow-history.ts +29 -0
  105. package/.pi/lib/agt-governance-active.ts +14 -0
  106. package/.pi/lib/agt-tool-guard.ts +78 -0
  107. package/.pi/lib/ask-user/dialog.ts +314 -0
  108. package/.pi/{extensions/lib → lib}/debate-bus-core.ts +10 -10
  109. package/.pi/{extensions/lib → lib}/debate-bus-state.ts +1 -1
  110. package/.pi/{extensions/lib → lib}/extension-load-guard.ts +21 -0
  111. package/.pi/lib/harness-agt-tool-guard.ts +5 -0
  112. package/.pi/{extensions/lib → lib}/harness-artifact-gate.ts +6 -16
  113. package/.pi/lib/harness-debate-core-deps.ts +14 -0
  114. package/.pi/lib/harness-debate-workflow-deps.ts +43 -0
  115. package/.pi/lib/harness-lens/.gitattributes +1 -0
  116. package/.pi/lib/harness-lens/clients/edit-autopatch.ts +88 -0
  117. package/.pi/lib/harness-lens/clients/file-kinds.ts +380 -0
  118. package/.pi/lib/harness-lens/clients/file-time.ts +215 -0
  119. package/.pi/lib/harness-lens/clients/file-utils.ts +484 -0
  120. package/.pi/lib/harness-lens/clients/format-service.ts +276 -0
  121. package/.pi/lib/harness-lens/clients/formatters.ts +1000 -0
  122. package/.pi/lib/harness-lens/clients/git-guard.ts +31 -0
  123. package/.pi/lib/harness-lens/clients/indent-retarget.ts +90 -0
  124. package/.pi/lib/harness-lens/clients/installer/index.ts +2368 -0
  125. package/.pi/lib/harness-lens/clients/latency-logger.ts +80 -0
  126. package/.pi/lib/harness-lens/clients/lens-config.ts +43 -0
  127. package/.pi/lib/harness-lens/clients/lens-events.ts +164 -0
  128. package/.pi/lib/harness-lens/clients/lsp/aggregation.ts +91 -0
  129. package/.pi/lib/harness-lens/clients/lsp/client.ts +1466 -0
  130. package/.pi/lib/harness-lens/clients/lsp/config.ts +216 -0
  131. package/.pi/lib/harness-lens/clients/lsp/edits.ts +297 -0
  132. package/.pi/lib/harness-lens/clients/lsp/index.ts +1355 -0
  133. package/.pi/lib/harness-lens/clients/lsp/interactive-install.ts +424 -0
  134. package/.pi/lib/harness-lens/clients/lsp/language.ts +223 -0
  135. package/.pi/lib/harness-lens/clients/lsp/launch.ts +939 -0
  136. package/.pi/lib/harness-lens/clients/lsp/lsp-index.ts +11 -0
  137. package/.pi/lib/harness-lens/clients/lsp/path-utils.ts +12 -0
  138. package/.pi/lib/harness-lens/clients/lsp/server-strategies.ts +81 -0
  139. package/.pi/lib/harness-lens/clients/lsp/server.ts +1971 -0
  140. package/.pi/lib/harness-lens/clients/path-utils.ts +182 -0
  141. package/.pi/lib/harness-lens/clients/pipeline.ts +360 -0
  142. package/.pi/lib/harness-lens/clients/project-profile.ts +117 -0
  143. package/.pi/lib/harness-lens/clients/runtime-agent-end.ts +112 -0
  144. package/.pi/lib/harness-lens/clients/runtime-config.ts +33 -0
  145. package/.pi/lib/harness-lens/clients/runtime-coordinator.ts +186 -0
  146. package/.pi/lib/harness-lens/clients/runtime-tool-result.ts +171 -0
  147. package/.pi/lib/harness-lens/clients/safe-spawn.ts +339 -0
  148. package/.pi/lib/harness-lens/clients/secrets-scanner.ts +214 -0
  149. package/.pi/lib/harness-lens/clients/tool-policy.ts +2072 -0
  150. package/.pi/lib/harness-lens/clients/types.ts +59 -0
  151. package/.pi/lib/harness-lens/clients/widget-state.ts +283 -0
  152. package/.pi/lib/harness-lens/index.ts +532 -0
  153. package/.pi/lib/harness-lens/tools/lsp-diagnostics.ts +706 -0
  154. package/.pi/lib/harness-lens/tools/lsp-navigation.ts +1246 -0
  155. package/.pi/{extensions/lib → lib}/harness-posthog.ts +3 -0
  156. package/.pi/lib/harness-project-config.ts +91 -0
  157. package/.pi/lib/harness-run-context-responses.ts +9 -0
  158. package/.pi/lib/harness-run-context.ts +1 -3
  159. package/.pi/{extensions/lib/spawn-policy.ts → lib/harness-spawn-policy.ts} +4 -3
  160. package/.pi/{extensions/lib → lib}/harness-spawn-topology.ts +5 -28
  161. package/.pi/lib/harness-subagent-auth.ts +51 -0
  162. package/.pi/{extensions/lib → lib}/harness-subagent-precheck.ts +13 -10
  163. package/.pi/{extensions/lib → lib}/harness-subagent-submit-pipeline.ts +3 -3
  164. package/.pi/lib/harness-subagent-submit-register.ts +163 -0
  165. package/.pi/{extensions/lib → lib}/harness-subagent-submit-registry.ts +1 -55
  166. package/.pi/{extensions/lib → lib}/harness-subagents-bridge.ts +53 -14
  167. package/.pi/{extensions/lib → lib}/harness-subprocess-bootstrap.ts +1 -1
  168. package/.pi/lib/harness-ui-state.ts +27 -12
  169. package/.pi/{extensions/lib → lib}/plan-approval/create-plan.ts +2 -2
  170. package/.pi/{extensions/lib → lib}/plan-approval/format-plan.ts +2 -2
  171. package/.pi/{extensions/lib → lib}/plan-approval/plan-review.ts +162 -201
  172. package/.pi/{extensions/lib → lib}/plan-approval/render.ts +1 -1
  173. package/.pi/{extensions/lib → lib}/plan-approval/resolve-disk.ts +2 -2
  174. package/.pi/{extensions/lib → lib}/plan-approval/types.ts +1 -1
  175. package/.pi/{extensions/lib → lib}/plan-approval/validate.ts +3 -3
  176. package/.pi/{extensions/lib → lib}/plan-approval-readiness.ts +3 -52
  177. package/.pi/{extensions/lib → lib}/plan-debate-envelope.ts +1 -1
  178. package/.pi/{extensions/lib → lib}/plan-debate-gate.ts +1 -1
  179. package/.pi/{extensions/lib → lib}/plan-debate-lane.ts +1 -4
  180. package/.pi/{extensions/lib → lib}/plan-messenger.ts +1 -1
  181. package/.pi/prompts/harness-auto.md +2 -2
  182. package/.pi/prompts/harness-plan.md +4 -6
  183. package/.pi/prompts/harness-review.md +9 -9
  184. package/.pi/prompts/harness-run.md +7 -7
  185. package/.pi/prompts/harness-setup.md +42 -68
  186. package/.pi/prompts/harness-steer.md +2 -2
  187. package/.pi/scripts/README.md +3 -5
  188. package/.pi/scripts/generate-agents-policy-yaml.mjs +148 -0
  189. package/.pi/scripts/graphify-kb-updater.mjs +48 -8
  190. package/.pi/scripts/harness-agents-manifest.mjs +61 -4
  191. package/.pi/scripts/harness-agt-doctor.ts +36 -0
  192. package/.pi/scripts/harness-cli-verify.sh +9 -2
  193. package/.pi/scripts/harness-project-toggle.mjs +129 -0
  194. package/.pi/scripts/harness-sentrux-cli.mjs +142 -0
  195. package/.pi/scripts/harness-verify.mjs +113 -39
  196. package/.pi/scripts/harness-web-policy-guard.mjs +2 -2
  197. package/.pi/scripts/validate-plan-dag.mjs +65 -74
  198. package/.pi/scripts/vendor-pi-vcc-settings.stub.ts +2 -2
  199. package/.pi/scripts/vendor-sync-pi-vcc.sh +1 -1
  200. package/.pi/skills/architecture/broker-domain/SKILL.md +65 -0
  201. package/.pi/skills/architecture/cqrs/SKILL.md +63 -0
  202. package/.pi/skills/architecture/event-driven/SKILL.md +60 -0
  203. package/.pi/skills/architecture/hexagonal-ports-adapters/SKILL.md +66 -0
  204. package/.pi/skills/architecture/layered/SKILL.md +68 -0
  205. package/.pi/skills/architecture/microkernel/SKILL.md +62 -0
  206. package/.pi/skills/architecture/microservices/SKILL.md +64 -0
  207. package/.pi/skills/architecture/modular-monolith/SKILL.md +65 -0
  208. package/.pi/skills/architecture/orchestration-driven-soa/SKILL.md +61 -0
  209. package/.pi/skills/architecture/pipeline/SKILL.md +63 -0
  210. package/.pi/skills/architecture/service-based/SKILL.md +64 -0
  211. package/.pi/skills/architecture/service-mesh/SKILL.md +60 -0
  212. package/.pi/skills/architecture/space-based/SKILL.md +60 -0
  213. package/.pi/skills/ast-grep/SKILL.md +40 -321
  214. package/.pi/skills/delivery/debugging-discipline/SKILL.md +36 -0
  215. package/.pi/skills/delivery/documentation-update/SKILL.md +33 -0
  216. package/.pi/skills/delivery/requirements-to-implementation/SKILL.md +34 -0
  217. package/.pi/skills/delivery/risk-based-verification/SKILL.md +43 -0
  218. package/.pi/skills/delivery/tradeoff-analysis/SKILL.md +34 -0
  219. package/.pi/skills/engineering/api-contract-design/SKILL.md +38 -0
  220. package/.pi/skills/engineering/cohesion-coupling/SKILL.md +43 -0
  221. package/.pi/skills/engineering/complexity-control/SKILL.md +31 -0
  222. package/.pi/skills/engineering/defensive-programming/SKILL.md +38 -0
  223. package/.pi/skills/engineering/dependency-management/SKILL.md +29 -0
  224. package/.pi/skills/engineering/domain-modeling/SKILL.md +32 -0
  225. package/.pi/skills/engineering/error-handling/SKILL.md +37 -0
  226. package/.pi/skills/engineering/legacy-code-seams/SKILL.md +35 -0
  227. package/.pi/skills/engineering/naming-and-intent/SKILL.md +29 -0
  228. package/.pi/skills/engineering/refactoring-safe-evolution/SKILL.md +35 -0
  229. package/.pi/skills/engineering/routine-function-design/SKILL.md +34 -0
  230. package/.pi/skills/engineering/small-change-discipline/SKILL.md +35 -0
  231. package/.pi/skills/lsp-navigation/SKILL.md +89 -0
  232. package/.pi/skills/quality/code-review-self-check/SKILL.md +35 -0
  233. package/.pi/skills/quality/privacy-data-handling/SKILL.md +26 -0
  234. package/.pi/skills/quality/security-review/SKILL.md +34 -0
  235. package/.pi/skills/quality/test-strategy/SKILL.md +33 -0
  236. package/.pi/skills/quality/testability-design/SKILL.md +33 -0
  237. package/.pi/skills/systems/concurrency-safety/SKILL.md +32 -0
  238. package/.pi/skills/systems/data-modeling-migrations/SKILL.md +31 -0
  239. package/.pi/skills/systems/observability-instrumentation/SKILL.md +32 -0
  240. package/.pi/skills/systems/performance-measurement/SKILL.md +35 -0
  241. package/.pi/skills/systems/reliability-design/SKILL.md +32 -0
  242. package/.sentrux/rules.toml +20 -4
  243. package/AGENTS.md +5 -0
  244. package/CHANGELOG.md +26 -0
  245. package/README.md +85 -58
  246. package/THIRD_PARTY_NOTICES.md +12 -21
  247. package/package.json +15 -7
  248. package/vendor/pi-subagents/src/agents.ts +45 -1
  249. package/vendor/pi-subagents/src/subagents.ts +866 -811
  250. package/vendor/pi-vcc/src/core/brief.ts +68 -99
  251. package/vendor/pi-vcc/src/core/settings.ts +2 -2
  252. package/.agents/skills/caveman/SKILL.md +0 -67
  253. package/.pi/agents/harness/meta-optimizer.md +0 -36
  254. package/.pi/agents/harness/planning/scout-graphify.md +0 -39
  255. package/.pi/agents/harness/planning/scout-semantic.md +0 -41
  256. package/.pi/agents/harness/planning/scout-structure.md +0 -37
  257. package/.pi/extensions/lib/ask-user/dialog.ts +0 -260
  258. package/.pi/extensions/lib/harness-subagent-auth.ts +0 -209
  259. package/.pi/extensions/lib/harness-subagent-policy.ts +0 -236
  260. package/.pi/extensions/pi-model-router-harness.ts +0 -42
  261. package/.pi/harness/evolution/meta-optimizer.mjs +0 -99
  262. package/.pi/harness/specs/router-tuning-proposal.schema.json +0 -114
  263. package/.pi/model-router.example.json +0 -36
  264. package/.pi/prompts/harness-critic.md +0 -10
  265. package/.pi/prompts/harness-eval.md +0 -10
  266. package/.pi/prompts/harness-router-tune.md +0 -52
  267. package/.pi/scripts/harness-generate-model-router.mjs +0 -327
  268. package/.pi/scripts/harness-model-router-routing.test.mjs +0 -97
  269. package/.pi/scripts/harness-sync-model-router.mjs +0 -97
  270. package/.pi/scripts/vendor-sync-pi-model-router.sh +0 -47
  271. package/vendor/pi-model-router/.prettierignore +0 -4
  272. package/vendor/pi-model-router/.prettierrc +0 -5
  273. package/vendor/pi-model-router/AGENTS.md +0 -39
  274. package/vendor/pi-model-router/LICENSE +0 -21
  275. package/vendor/pi-model-router/README.md +0 -99
  276. package/vendor/pi-model-router/UPSTREAM_PIN.md +0 -10
  277. package/vendor/pi-model-router/docs/ARCHITECTURE.md +0 -54
  278. package/vendor/pi-model-router/extensions/commands.ts +0 -720
  279. package/vendor/pi-model-router/extensions/config.ts +0 -348
  280. package/vendor/pi-model-router/extensions/constants.ts +0 -1
  281. package/vendor/pi-model-router/extensions/index.ts +0 -478
  282. package/vendor/pi-model-router/extensions/provider.ts +0 -580
  283. package/vendor/pi-model-router/extensions/routing.ts +0 -564
  284. package/vendor/pi-model-router/extensions/state.ts +0 -52
  285. package/vendor/pi-model-router/extensions/types.ts +0 -95
  286. package/vendor/pi-model-router/extensions/ui.ts +0 -144
  287. package/vendor/pi-model-router/model-router.example.json +0 -48
  288. package/vendor/pi-model-router/package.json +0 -48
  289. package/vendor/pi-model-router/tsconfig.json +0 -16
  290. /package/.pi/{prompts → harness/docs}/planning-rubrics.md +0 -0
  291. /package/.pi/{extensions/lib → lib}/ask-user/fallback.ts +0 -0
  292. /package/.pi/{extensions/lib → lib}/ask-user/render.ts +0 -0
  293. /package/.pi/{extensions/lib → lib}/ask-user/schema.ts +0 -0
  294. /package/.pi/{extensions/lib → lib}/ask-user/types.ts +0 -0
  295. /package/.pi/{extensions/lib → lib}/ask-user/validate-core.mjs +0 -0
  296. /package/.pi/{extensions/lib → lib}/ask-user/validate.ts +0 -0
  297. /package/.pi/{extensions/lib → lib}/harness-cocoindex-refresh.ts +0 -0
  298. /package/.pi/{extensions/lib → lib}/harness-paths.ts +0 -0
  299. /package/.pi/{extensions/lib → lib}/harness-spawn-budget.ts +0 -0
  300. /package/.pi/{extensions/lib → lib}/harness-vcc-settings.ts +0 -0
  301. /package/.pi/{extensions/lib → lib}/harness-web/run-cli.ts +0 -0
  302. /package/.pi/{extensions/lib → lib}/plan-approval/dialog.ts +0 -0
  303. /package/.pi/{extensions/lib → lib}/plan-approval/schema.ts +0 -0
  304. /package/.pi/{extensions/lib → lib}/plan-debate-eligibility.ts +0 -0
  305. /package/.pi/{extensions/lib → lib}/plan-debate-focus.ts +0 -0
  306. /package/.pi/{extensions/lib → lib}/plan-debate-id.ts +0 -0
  307. /package/.pi/{extensions/lib → lib}/plan-debate-lanes.ts +0 -0
  308. /package/.pi/{extensions/lib → lib}/plan-debate-round-status.ts +0 -0
  309. /package/.pi/{extensions/lib → lib}/plan-debate-write-guard.ts +0 -0
  310. /package/.pi/{extensions/lib → lib}/plan-review-gate.ts +0 -0
  311. /package/.pi/{extensions/lib → lib}/plan-review-integrator-rules.ts +0 -0
  312. /package/.pi/{extensions/lib → lib}/plan-scope-guard.ts +0 -0
  313. /package/.pi/{extensions/lib → lib}/posthog-client.ts +0 -0
  314. /package/.pi/{extensions/lib → lib}/posthog-node.d.ts +0 -0
@@ -0,0 +1,78 @@
1
+ import { join } from "node:path";
2
+ import { appendPolicyAuditEvent } from "./agt/audit-run-sink.js";
3
+ import type { BuildEvaluationContextInput } from "./agt/build-evaluation-context.js";
4
+ import { evaluateHarnessToolPolicy } from "./agt/evaluate-policy.js";
5
+ import { recordHarnessPolicyDeny } from "./agt/kill-switch-state.js";
6
+ import { recordPolicyAllow, recordPolicyDeny } from "./agt/trust-run-store.js";
7
+ import { getHarnessPackageRoot } from "./harness-paths.js";
8
+
9
+ type HarnessPhase = "plan" | "execute" | "evaluate" | "adversary" | "merge";
10
+
11
+ export interface PolicyStateSlice {
12
+ phase: HarnessPhase;
13
+ approvedPlan: boolean;
14
+ planId: string | null;
15
+ aborted: boolean;
16
+ budgetBypass: boolean;
17
+ }
18
+
19
+ export async function evaluateAgtToolCall(input: {
20
+ moduleUrl: string;
21
+ toolName: string;
22
+ toolInput: Record<string, unknown>;
23
+ policyState: PolicyStateSlice;
24
+ entries: unknown[];
25
+ sessionId: string;
26
+ projectRoot: string;
27
+ }): Promise<{ block: boolean; reason?: string } | undefined> {
28
+ const packageRoot = getHarnessPackageRoot(input.moduleUrl);
29
+ const evalInput: BuildEvaluationContextInput = {
30
+ toolName: input.toolName,
31
+ toolInput: input.toolInput,
32
+ packageRoot,
33
+ projectRoot: input.projectRoot,
34
+ sessionId: input.sessionId,
35
+ entries: input.entries,
36
+ policyState: input.policyState,
37
+ };
38
+
39
+ const result = await evaluateHarnessToolPolicy(packageRoot, evalInput);
40
+ const runId = process.env.HARNESS_RUN_ID?.trim();
41
+ const runDir =
42
+ process.env.HARNESS_RUN_DIR?.trim() ??
43
+ (runId ? join(packageRoot, ".pi", "harness", "runs", runId) : "");
44
+
45
+ if (runId && runDir) {
46
+ const agentDid =
47
+ process.env.HARNESS_AGENT_DID?.trim() ??
48
+ (process.env.HARNESS_AGENT_ID?.trim() || "parent-orchestrator");
49
+ appendPolicyAuditEvent({
50
+ runDir,
51
+ runId,
52
+ toolName: input.toolName,
53
+ allowed: result.allowed,
54
+ reason: result.reason,
55
+ agentDid,
56
+ phase: input.policyState.phase,
57
+ });
58
+ if (result.allowed) {
59
+ recordPolicyAllow(runId, agentDid);
60
+ } else {
61
+ recordPolicyDeny(runId, agentDid);
62
+ }
63
+ }
64
+
65
+ if (!result.allowed) {
66
+ recordHarnessPolicyDeny(input.sessionId);
67
+ return {
68
+ block: true,
69
+ reason: result.reason.startsWith("agt-policy")
70
+ ? result.reason
71
+ : `agt-policy: ${result.reason}`,
72
+ };
73
+ }
74
+ return undefined;
75
+ }
76
+
77
+ /** @deprecated Use evaluateAgtToolCall */
78
+ export const evaluateAgtHarnessToolCall = evaluateAgtToolCall;
@@ -0,0 +1,314 @@
1
+ import type { ExtensionUIContext } from "@earendil-works/pi-coding-agent";
2
+ import {
3
+ Editor,
4
+ type EditorTheme,
5
+ Key,
6
+ matchesKey,
7
+ truncateToWidth,
8
+ } from "@earendil-works/pi-tui";
9
+ import type { AskResponse, DialogResult, ValidatedAskParams } from "./types.js";
10
+
11
+ type DisplayOption = {
12
+ title: string;
13
+ description?: string;
14
+ isFreeform?: boolean;
15
+ };
16
+
17
+ interface CustomAnswer {
18
+ response: AskResponse;
19
+ }
20
+
21
+ type ThemeLike = { fg(color: string, text: string): string };
22
+ type TuiLike = ConstructorParameters<typeof Editor>[0] & {
23
+ requestRender(): void;
24
+ };
25
+ type Done = (answer: CustomAnswer | null) => void;
26
+
27
+ function withTimeout<T>(
28
+ promise: Promise<T | null>,
29
+ ms: number | undefined,
30
+ ): Promise<T | null> {
31
+ if (!ms) return promise;
32
+ return Promise.race([
33
+ promise,
34
+ new Promise<null>((resolve) => {
35
+ setTimeout(() => resolve(null), ms);
36
+ }),
37
+ ]);
38
+ }
39
+
40
+ function displayOptionsFor(validated: ValidatedAskParams): DisplayOption[] {
41
+ const displayOptions: DisplayOption[] = [...validated.options];
42
+ if (validated.allowFreeform) {
43
+ displayOptions.push({ title: "Type something…", isFreeform: true });
44
+ }
45
+ return displayOptions;
46
+ }
47
+
48
+ async function runFreeformOnly(
49
+ ui: ExtensionUIContext,
50
+ question: string,
51
+ ): Promise<DialogResult> {
52
+ const text = await ui.input(question, "");
53
+ if (!text?.trim()) return { response: null, cancelled: true };
54
+ return {
55
+ response: { kind: "freeform", text: text.trim() },
56
+ cancelled: false,
57
+ };
58
+ }
59
+
60
+ function editorThemeFor(theme: ThemeLike): EditorTheme {
61
+ return {
62
+ borderColor: (s) => theme.fg("accent", s),
63
+ selectList: {
64
+ selectedPrefix: (t) => theme.fg("accent", t),
65
+ selectedText: (t) => theme.fg("accent", t),
66
+ description: (t) => theme.fg("muted", t),
67
+ scrollInfo: (t) => theme.fg("dim", t),
68
+ noMatch: (t) => theme.fg("warning", t),
69
+ },
70
+ };
71
+ }
72
+
73
+ class AskDialogController {
74
+ private optionIndex = 0;
75
+ private editMode = false;
76
+ private readonly selected = new Set<number>();
77
+ private cachedLines: string[] | undefined;
78
+ private readonly editor: Editor;
79
+
80
+ constructor(
81
+ private readonly validated: ValidatedAskParams,
82
+ private readonly displayOptions: DisplayOption[],
83
+ private readonly tui: TuiLike,
84
+ private readonly theme: ThemeLike,
85
+ private readonly done: Done,
86
+ ) {
87
+ this.editor = new Editor(tui, editorThemeFor(theme));
88
+ this.editor.onSubmit = (value) => this.submitFreeform(value);
89
+ }
90
+
91
+ invalidate(): void {
92
+ this.cachedLines = undefined;
93
+ }
94
+
95
+ handleInput(data: string): void {
96
+ if (this.editMode) {
97
+ this.handleEditInput(data);
98
+ return;
99
+ }
100
+ if (this.handleNavigationInput(data)) return;
101
+ if (this.validated.allowMultiple && matchesKey(data, Key.space)) {
102
+ this.toggleMultiSelect();
103
+ return;
104
+ }
105
+ if (matchesKey(data, Key.enter)) this.submitSelection();
106
+ if (matchesKey(data, Key.escape)) this.done(null);
107
+ }
108
+
109
+ render(width: number): string[] {
110
+ if (this.cachedLines) return this.cachedLines;
111
+ const lines: string[] = [];
112
+ const add = (s: string) => lines.push(truncateToWidth(s, width));
113
+ const useOverlay = this.validated.displayMode !== "inline";
114
+ this.renderHeader(lines, add, width, useOverlay);
115
+ this.renderOptions(add);
116
+ this.renderEditor(lines, add, width);
117
+ this.renderFooter(add, width, useOverlay);
118
+ this.cachedLines = lines;
119
+ return lines;
120
+ }
121
+
122
+ private refresh(): void {
123
+ this.invalidate();
124
+ this.tui.requestRender();
125
+ }
126
+
127
+ private submitFreeform(value: string): void {
128
+ const trimmed = value.trim();
129
+ if (trimmed) {
130
+ this.done({ response: { kind: "freeform", text: trimmed } });
131
+ return;
132
+ }
133
+ this.editMode = false;
134
+ this.editor.setText("");
135
+ this.refresh();
136
+ }
137
+
138
+ private submitSelection(): void {
139
+ if (this.validated.allowMultiple) {
140
+ const titles = [...this.selected]
141
+ .sort((a, b) => a - b)
142
+ .map((i) => this.displayOptions[i].title)
143
+ .filter((t) => t !== "Type something…");
144
+ if (titles.length) {
145
+ this.done({ response: { kind: "selection", selections: titles } });
146
+ }
147
+ return;
148
+ }
149
+ const opt = this.displayOptions[this.optionIndex];
150
+ if (opt.isFreeform) {
151
+ this.editMode = true;
152
+ this.refresh();
153
+ return;
154
+ }
155
+ this.done({ response: { kind: "selection", selections: [opt.title] } });
156
+ }
157
+
158
+ private handleEditInput(data: string): void {
159
+ if (matchesKey(data, Key.escape)) {
160
+ this.editMode = false;
161
+ this.editor.setText("");
162
+ } else {
163
+ this.editor.handleInput(data);
164
+ }
165
+ this.refresh();
166
+ }
167
+
168
+ private handleNavigationInput(data: string): boolean {
169
+ if (matchesKey(data, Key.up)) {
170
+ this.optionIndex = Math.max(0, this.optionIndex - 1);
171
+ this.refresh();
172
+ return true;
173
+ }
174
+ if (matchesKey(data, Key.down)) {
175
+ this.optionIndex = Math.min(
176
+ this.displayOptions.length - 1,
177
+ this.optionIndex + 1,
178
+ );
179
+ this.refresh();
180
+ return true;
181
+ }
182
+ return false;
183
+ }
184
+
185
+ private toggleMultiSelect(): void {
186
+ const opt = this.displayOptions[this.optionIndex];
187
+ if (opt.isFreeform) return;
188
+ if (this.selected.has(this.optionIndex)) {
189
+ this.selected.delete(this.optionIndex);
190
+ } else {
191
+ this.selected.add(this.optionIndex);
192
+ }
193
+ this.refresh();
194
+ }
195
+
196
+ private renderHeader(
197
+ lines: string[],
198
+ add: (s: string) => void,
199
+ width: number,
200
+ useOverlay: boolean,
201
+ ): void {
202
+ if (useOverlay) add(this.theme.fg("accent", "─".repeat(width)));
203
+ if (this.validated.context) {
204
+ for (const line of this.validated.context.split("\n")) {
205
+ add(this.theme.fg("muted", ` ${line}`));
206
+ }
207
+ lines.push("");
208
+ }
209
+ add(this.theme.fg("text", ` ${this.validated.question}`));
210
+ lines.push("");
211
+ }
212
+
213
+ private renderOptions(add: (s: string) => void): void {
214
+ for (let i = 0; i < this.displayOptions.length; i++) {
215
+ const opt = this.displayOptions[i];
216
+ const prefix = this.optionPrefix(i, opt.isFreeform === true);
217
+ const label = this.optionLabel(i, opt);
218
+ add(`${prefix}${label}`);
219
+ if (opt.description)
220
+ add(` ${this.theme.fg("muted", opt.description)}`);
221
+ }
222
+ }
223
+
224
+ private optionPrefix(index: number, isFreeform: boolean): string {
225
+ if (!this.validated.allowMultiple) {
226
+ return index === this.optionIndex ? this.theme.fg("accent", "> ") : " ";
227
+ }
228
+ if (isFreeform) return " ";
229
+ return this.selected.has(index) ? this.theme.fg("accent", "[x] ") : "[ ] ";
230
+ }
231
+
232
+ private optionLabel(index: number, opt: DisplayOption): string {
233
+ const raw = `${index + 1}. ${opt.title}`;
234
+ const focused = index === this.optionIndex;
235
+ if (opt.isFreeform && this.editMode && focused) {
236
+ return this.theme.fg("accent", `${raw} ✎`);
237
+ }
238
+ if (focused && !this.validated.allowMultiple) {
239
+ return this.theme.fg("accent", raw);
240
+ }
241
+ return this.theme.fg("text", raw);
242
+ }
243
+
244
+ private renderEditor(
245
+ lines: string[],
246
+ add: (s: string) => void,
247
+ width: number,
248
+ ): void {
249
+ if (!this.editMode) return;
250
+ lines.push("");
251
+ add(this.theme.fg("muted", " Your answer:"));
252
+ for (const line of this.editor.render(width - 2)) add(` ${line}`);
253
+ }
254
+
255
+ private renderFooter(
256
+ add: (s: string) => void,
257
+ width: number,
258
+ useOverlay: boolean,
259
+ ): void {
260
+ add("");
261
+ if (this.editMode) {
262
+ add(this.theme.fg("dim", " Enter to submit • Esc to go back"));
263
+ } else if (this.validated.allowMultiple) {
264
+ add(
265
+ this.theme.fg(
266
+ "dim",
267
+ " ↑↓ navigate • Space toggle • Enter confirm • Esc cancel",
268
+ ),
269
+ );
270
+ } else {
271
+ add(
272
+ this.theme.fg("dim", " ↑↓ navigate • Enter to select • Esc to cancel"),
273
+ );
274
+ }
275
+ if (useOverlay) add(this.theme.fg("accent", "─".repeat(width)));
276
+ }
277
+ }
278
+
279
+ async function runOptionDialog(
280
+ ui: ExtensionUIContext,
281
+ validated: ValidatedAskParams,
282
+ displayOptions: DisplayOption[],
283
+ ): Promise<CustomAnswer | null> {
284
+ return withTimeout(
285
+ ui.custom<CustomAnswer | null>((tui, theme, _kb, done) => {
286
+ const controller = new AskDialogController(
287
+ validated,
288
+ displayOptions,
289
+ tui as TuiLike,
290
+ theme,
291
+ done,
292
+ );
293
+ return {
294
+ render: (width: number) => controller.render(width),
295
+ invalidate: () => controller.invalidate(),
296
+ handleInput: (data: string) => controller.handleInput(data),
297
+ };
298
+ }),
299
+ validated.timeout,
300
+ );
301
+ }
302
+
303
+ export async function runAskDialog(
304
+ ui: ExtensionUIContext,
305
+ validated: ValidatedAskParams,
306
+ ): Promise<DialogResult> {
307
+ const displayOptions = displayOptionsFor(validated);
308
+ if (displayOptions.length === 0) {
309
+ return runFreeformOnly(ui, validated.question);
310
+ }
311
+ const result = await runOptionDialog(ui, validated, displayOptions);
312
+ if (!result) return { response: null, cancelled: true };
313
+ return { response: result.response, cancelled: false };
314
+ }
@@ -4,25 +4,25 @@
4
4
 
5
5
  import { appendFile, mkdir, writeFile } from "node:fs/promises";
6
6
  import { join } from "node:path";
7
+ import {
8
+ type DebateState,
9
+ getDebateState,
10
+ getLastSeverity,
11
+ type SeverityScores,
12
+ setDebateState,
13
+ setLastSeverity,
14
+ } from "./debate-bus-state.js";
7
15
  import {
8
16
  type DebateParticipant,
9
17
  debatePhaseFromId,
10
18
  isPlanDebateId,
11
19
  PLAN_DEBATE_PARTICIPANTS,
12
20
  POST_EXECUTE_DEBATE_PARTICIPANTS,
13
- } from "../../lib/debate-orchestrator-types.js";
21
+ } from "./debate-orchestrator-types.js";
14
22
  import {
15
23
  isHarnessBudgetEnforceOn,
16
24
  shouldEmitBlockingBudgetExhausted,
17
- } from "../../lib/harness-budget-enforce.js";
18
- import {
19
- type DebateState,
20
- getDebateState,
21
- getLastSeverity,
22
- type SeverityScores,
23
- setDebateState,
24
- setLastSeverity,
25
- } from "./debate-bus-state.js";
25
+ } from "./harness-budget-enforce.js";
26
26
  import {
27
27
  type DebateProfile,
28
28
  PLAN_BUDGET_FAST,
@@ -2,7 +2,7 @@
2
2
  * Shared in-process debate bus state (one session per Pi process).
3
3
  */
4
4
 
5
- import type { DebateParticipant } from "../../lib/debate-orchestrator-types.js";
5
+ import type { DebateParticipant } from "./debate-orchestrator-types.js";
6
6
  import type { DebateProfile } from "./plan-debate-eligibility.js";
7
7
  import type { PlanDebateFocus } from "./plan-debate-focus.js";
8
8
 
@@ -1,6 +1,8 @@
1
1
  import { readFileSync } from "node:fs";
2
2
  import { join } from "node:path";
3
3
  import { fileURLToPath } from "node:url";
4
+ import { isAgtGovernanceActive } from "./agt-governance-active.js";
5
+ import { isHarnessProjectEnabled } from "./harness-project-config.js";
4
6
 
5
7
  const LOAD_GUARD_KEY = Symbol.for("ultimate-pi.extension-load-guard");
6
8
 
@@ -37,3 +39,22 @@ export function claimExtensionLoad(key: string, moduleUrl: string): boolean {
37
39
  registry.add(key);
38
40
  return true;
39
41
  }
42
+
43
+ /** AGT subprocess governance for all subagents when project/harness policy is active. */
44
+ export function claimSubagentGovernanceLoad(
45
+ key: string,
46
+ moduleUrl: string,
47
+ projectRoot?: string,
48
+ ): boolean {
49
+ if (!isAgtGovernanceActive(projectRoot)) return false;
50
+ return claimExtensionLoad(key, moduleUrl);
51
+ }
52
+
53
+ /** Skip duplicate loads and skip harness-only governance when harness is disabled. */
54
+ export function claimHarnessGovernanceLoad(
55
+ key: string,
56
+ moduleUrl: string,
57
+ ): boolean {
58
+ if (!isHarnessProjectEnabled()) return false;
59
+ return claimExtensionLoad(key, moduleUrl);
60
+ }
@@ -0,0 +1,5 @@
1
+ export {
2
+ evaluateAgtHarnessToolCall,
3
+ evaluateAgtToolCall,
4
+ type PolicyStateSlice,
5
+ } from "./agt-tool-guard.js";
@@ -6,7 +6,7 @@ import { constants } from "node:fs";
6
6
  import { access, readFile, stat } from "node:fs/promises";
7
7
  import { join } from "node:path";
8
8
  import { parse as parseYaml } from "yaml";
9
- import { validateAgainstHarnessSchema } from "../../lib/harness-schema-validate.js";
9
+ import { validateAgainstHarnessSchema } from "./harness-schema-validate.js";
10
10
 
11
11
  export interface ArtifactGateResult {
12
12
  ok: boolean;
@@ -20,9 +20,6 @@ const ARTIFACT_SCHEMA: Record<string, string> = {
20
20
  "plan-implementation-research-brief.schema.json",
21
21
  "artifacts/stack.yaml": "plan-stack-brief.schema.json",
22
22
  "artifacts/planning-context.yaml": "plan-planning-context.schema.json",
23
- "artifacts/scout-graphify.yaml": "plan-scout-findings.schema.json",
24
- "artifacts/scout-structure.yaml": "plan-scout-findings.schema.json",
25
- "artifacts/scout-semantic.yaml": "plan-scout-findings.schema.json",
26
23
  "artifacts/eval-verdict.yaml": "eval-verdict.schema.json",
27
24
  "artifacts/adversary-report.yaml": "adversary-report.schema.json",
28
25
  };
@@ -48,10 +45,10 @@ async function fileExists(path: string): Promise<boolean> {
48
45
  }
49
46
  }
50
47
 
51
- function scoutStatusBad(doc: Record<string, unknown>): string | null {
48
+ function artifactStatusBad(doc: Record<string, unknown>): string | null {
52
49
  const status = String(doc.status ?? "ok").toLowerCase();
53
50
  if (status === "partial" || status === "failed" || status === "error") {
54
- return `scout status is "${status}"`;
51
+ return `artifact status is "${status}"`;
55
52
  }
56
53
  return null;
57
54
  }
@@ -105,17 +102,10 @@ export async function validateHarnessArtifactFile(
105
102
  }
106
103
  }
107
104
 
108
- if (doc && normalized.startsWith("artifacts/scout-")) {
109
- const scoutErr = scoutStatusBad(doc);
110
- if (scoutErr) {
111
- errors.push(`${normalized}: ${scoutErr}`);
112
- }
113
- }
114
-
115
105
  if (doc && normalized === "artifacts/planning-context.yaml") {
116
- const scoutErr = scoutStatusBad(doc);
117
- if (scoutErr) {
118
- errors.push(`${normalized}: ${scoutErr}`);
106
+ const statusErr = artifactStatusBad(doc);
107
+ if (statusErr) {
108
+ errors.push(`${normalized}: ${statusErr}`);
119
109
  }
120
110
  const coverage = doc.coverage as Record<string, unknown> | undefined;
121
111
  if (coverage && typeof coverage === "object") {
@@ -0,0 +1,14 @@
1
+ export { Type } from "@sinclair/typebox";
2
+ export { parse as parseYaml } from "yaml";
3
+ export type { DebateParticipant } from "./debate-orchestrator-types.js";
4
+ export {
5
+ extractLastSubmitCall,
6
+ type MessageLike,
7
+ } from "./harness-agent-output.js";
8
+ export { captureHarnessEvent } from "./harness-posthog.js";
9
+ export {
10
+ getLatestRunContext,
11
+ getRunIdFromSession,
12
+ } from "./harness-run-context.js";
13
+ export { DEBATE_AGENT_SUBMIT_TOOL } from "./harness-subagent-submit-registry.js";
14
+ export { writeYamlFile } from "./harness-yaml.js";
@@ -0,0 +1,43 @@
1
+ export {
2
+ acceptDebateRound,
3
+ capsForDebate,
4
+ finalizeDebateConsensus,
5
+ openDebateBus,
6
+ } from "./debate-bus-core.js";
7
+ export { getDebateState } from "./debate-bus-state.js";
8
+ export {
9
+ type DebateEligibilityInput,
10
+ harnessPlanDebateEligibility,
11
+ } from "./plan-debate-eligibility.js";
12
+ export {
13
+ buildPlanReviewRoundEnvelope,
14
+ type PlanReviewRoundDraft,
15
+ } from "./plan-debate-envelope.js";
16
+ export {
17
+ getPlanFocusCoverage,
18
+ planDebateOutcomeComplete,
19
+ } from "./plan-debate-focus.js";
20
+ export { normalizePlanDebateId, planDebateIdForRun } from "./plan-debate-id.js";
21
+ export {
22
+ applyDebateLane,
23
+ applyDebateLaneFromDoc,
24
+ type DebateLaneKind,
25
+ debateLaneForAgent,
26
+ formatApplyLaneMessage,
27
+ } from "./plan-debate-lane.js";
28
+ export { getPlanDebateRoundStatus } from "./plan-debate-round-status.js";
29
+ export { withReviewRoundYamlWrite } from "./plan-debate-write-guard.js";
30
+ export {
31
+ formatTranscriptForSpawn,
32
+ getMessengerRoundState,
33
+ initPlanMessenger,
34
+ loadMessengerState,
35
+ messengerRoundDebateReady,
36
+ postMessengerMessage,
37
+ readRoundTranscript,
38
+ } from "./plan-messenger.js";
39
+ export {
40
+ loadValidationTurnYaml,
41
+ validateIntegratorDraft,
42
+ } from "./plan-review-integrator-rules.js";
43
+ export { assessPlanScopeDrift } from "./plan-scope-guard.js";
@@ -0,0 +1 @@
1
+ * text=auto eol=lf
@@ -0,0 +1,88 @@
1
+ import * as nodeFs from "node:fs";
2
+
3
+ export function tryCorrectIndentationMismatchFromContent(
4
+ oldText: string,
5
+ content: string,
6
+ ): string | undefined {
7
+ const normalized = oldText.replace(/\r\n/g, "\n");
8
+ if (content.includes(normalized)) return undefined;
9
+
10
+ const conversions = [
11
+ (s: string) =>
12
+ s
13
+ .split("\n")
14
+ .map((l) => l.replace(/^\t+/, (m) => " ".repeat(m.length)))
15
+ .join("\n"),
16
+ (s: string) =>
17
+ s
18
+ .split("\n")
19
+ .map((l) => l.replace(/^\t+/, (m) => " ".repeat(m.length)))
20
+ .join("\n"),
21
+ (s: string) =>
22
+ s
23
+ .split("\n")
24
+ .map((l) => l.replace(/^( {2})+/, (m) => "\t".repeat(m.length / 2)))
25
+ .join("\n"),
26
+ (s: string) =>
27
+ s
28
+ .split("\n")
29
+ .map((l) => l.replace(/^( {4})+/, (m) => "\t".repeat(m.length / 4)))
30
+ .join("\n"),
31
+ ];
32
+
33
+ for (const convert of conversions) {
34
+ const candidate = convert(normalized);
35
+ if (candidate !== normalized && content.includes(candidate))
36
+ return candidate;
37
+ }
38
+
39
+ return findIndentationInsensitiveCandidate(content, normalized);
40
+ }
41
+
42
+ export function tryCorrectIndentationMismatch(
43
+ oldText: string,
44
+ filePath: string,
45
+ ): string | undefined {
46
+ try {
47
+ return tryCorrectIndentationMismatchFromContent(
48
+ oldText,
49
+ nodeFs.readFileSync(filePath, "utf-8").replace(/\r\n/g, "\n"),
50
+ );
51
+ } catch {
52
+ return undefined;
53
+ }
54
+ }
55
+
56
+ function findIndentationInsensitiveCandidate(
57
+ content: string,
58
+ oldText: string,
59
+ ): string | undefined {
60
+ const contentLines = content.split("\n");
61
+ const oldLines = oldText.split("\n");
62
+ const stripIndent = (line: string) => line.replace(/^[\t ]+/, "").trimEnd();
63
+ const expected = oldLines.map(stripIndent);
64
+
65
+ for (
66
+ let start = 0;
67
+ start <= contentLines.length - oldLines.length;
68
+ start += 1
69
+ ) {
70
+ let matches = true;
71
+ for (let offset = 0; offset < oldLines.length; offset += 1) {
72
+ if (
73
+ stripIndent(contentLines[start + offset] ?? "") !== expected[offset]
74
+ ) {
75
+ matches = false;
76
+ break;
77
+ }
78
+ }
79
+ if (matches) {
80
+ const candidate = contentLines
81
+ .slice(start, start + oldLines.length)
82
+ .join("\n");
83
+ if (candidate !== oldText) return candidate;
84
+ }
85
+ }
86
+
87
+ return undefined;
88
+ }