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,148 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Generate .pi/harness/agents.policy.yaml from harness agent .md frontmatter + submit registry.
4
+ */
5
+
6
+ import { readFile, writeFile } from "node:fs/promises";
7
+ import { join, dirname } from "node:path";
8
+ import { fileURLToPath } from "node:url";
9
+ import { parse as parseYaml } from "yaml";
10
+ import { walkAgentsDir } from "../lib/harness-agent-discovery.mjs";
11
+
12
+ const ROOT = join(dirname(fileURLToPath(import.meta.url)), "..", "..");
13
+ const AGENTS_DIR = join(ROOT, ".pi", "agents");
14
+ const OUT = join(ROOT, ".pi", "harness", "agents.policy.yaml");
15
+
16
+ const SUBMIT_BY_AGENT = {
17
+ "harness/planning/planning-context": ["submit_planning_context"],
18
+ "harness/planning/decompose": ["submit_decomposition_brief", "submit_human_required"],
19
+ "harness/planning/hypothesis": ["submit_hypothesis_brief"],
20
+ "harness/planning/hypothesis-validator": ["submit_hypothesis_validation"],
21
+ "harness/planning/plan-evaluator": ["submit_validation_turn"],
22
+ "harness/planning/plan-adversary": ["submit_adversary_brief"],
23
+ "harness/planning/sprint-contract-auditor": ["submit_sprint_audit"],
24
+ "harness/planning/review-integrator": ["submit_review_round_draft"],
25
+ "harness/planning/implementation-researcher": ["submit_implementation_research"],
26
+ "harness/planning/stack-researcher": ["submit_stack_brief"],
27
+ "harness/planning/execution-plan-author": ["submit_execution_plan_brief"],
28
+ "harness/running/executor": ["submit_executor_handoff"],
29
+ "harness/reviewing/evaluator": ["submit_eval_verdict"],
30
+ "harness/reviewing/adversary": ["submit_adversary_report"],
31
+ "harness/reviewing/tie-breaker": ["submit_human_required"],
32
+ "harness/trace-librarian": ["submit_human_required"],
33
+ "harness/incident-recorder": ["submit_human_required"],
34
+ "harness/sentrux-steward": ["submit_sentrux_manifest_proposal"],
35
+ };
36
+
37
+ function parseFrontmatter(content) {
38
+ const m = content.match(/^---\r?\n([\s\S]*?)\r?\n---/);
39
+ if (!m) return {};
40
+ return parseYaml(m[1]) ?? {};
41
+ }
42
+
43
+ function kindFor(id) {
44
+ if (id.startsWith("harness/planning/")) return "planner";
45
+ if (id === "harness/running/executor") return "executor";
46
+ if (id === "harness/reviewing/evaluator") return "evaluator";
47
+ if (id === "harness/reviewing/adversary") return "adversary";
48
+ if (id === "harness/reviewing/tie-breaker") return "tie_breaker";
49
+ if (id === "harness/trace-librarian") return "trace";
50
+ if (id === "harness/incident-recorder") return "incident";
51
+ if (id === "harness/sentrux-steward" || id === "harness/sentrux-bootstrap")
52
+ return "planner";
53
+ return "other";
54
+ }
55
+
56
+ const KIND_BASE = {
57
+ planner: ["read", "grep", "find", "ls"],
58
+ executor: ["read", "write", "edit", "bash", "grep", "find", "ls"],
59
+ evaluator: ["read", "grep", "find", "ls"],
60
+ adversary: ["read", "grep", "find", "ls"],
61
+ tie_breaker: ["read", "grep", "find", "ls"],
62
+ trace: ["read", "grep", "find", "ls"],
63
+ incident: ["read", "grep", "find", "ls"],
64
+ other: ["read", "grep", "find", "ls"],
65
+ };
66
+
67
+ function csvTools(fm) {
68
+ const raw = fm.tools;
69
+ if (!raw) return [];
70
+ return String(raw)
71
+ .split(",")
72
+ .map((t) => t.trim())
73
+ .filter(Boolean);
74
+ }
75
+
76
+ async function main() {
77
+ const files = new Map();
78
+ walkAgentsDir(AGENTS_DIR, "package", files);
79
+
80
+ const kinds = {
81
+ planner: { tools: KIND_BASE.planner, extensions: false, read_only: true },
82
+ executor: { tools: KIND_BASE.executor, extensions: true, read_only: false },
83
+ evaluator: { tools: KIND_BASE.evaluator, extensions: false, read_only: true },
84
+ adversary: { tools: KIND_BASE.adversary, extensions: false, read_only: true },
85
+ tie_breaker: {
86
+ tools: KIND_BASE.tie_breaker,
87
+ extensions: false,
88
+ read_only: true,
89
+ },
90
+ trace: { tools: KIND_BASE.trace, extensions: false, read_only: true },
91
+ incident: { tools: KIND_BASE.incident, extensions: false, read_only: true },
92
+ other: { tools: KIND_BASE.other, extensions: false, read_only: true },
93
+ };
94
+
95
+ const agents = {};
96
+
97
+ for (const [id, file] of files) {
98
+ if (!id.startsWith("harness/")) continue;
99
+ const fm = parseFrontmatter(file.content);
100
+ const kind = kindFor(id);
101
+ const base = new Set(KIND_BASE[kind] ?? KIND_BASE.other);
102
+ const fromFm = csvTools(fm);
103
+ const submit = SUBMIT_BY_AGENT[id] ?? [];
104
+ const toolsAdd = [...new Set([...fromFm, ...submit])].filter(
105
+ (t) => !base.has(t),
106
+ );
107
+ const entry = { kind };
108
+ if (toolsAdd.length > 0) entry.tools_add = toolsAdd;
109
+ if (fm.extensions === false) entry.extensions = false;
110
+ if (fm.extensions === true) entry.extensions = true;
111
+ if (typeof fm.max_turns === "number") entry.max_turns = fm.max_turns;
112
+ if (typeof fm.thinking === "string") entry.thinking = fm.thinking;
113
+ if (submit.length === 1) entry.submit_tool = submit[0];
114
+ agents[id] = entry;
115
+ }
116
+
117
+ // plan-synthesizer: parent-only, minimal policy for spawn if ever used
118
+ agents["harness/planning/plan-synthesizer"] = {
119
+ kind: "planner",
120
+ tools_add: [
121
+ "submit_decomposition_brief",
122
+ "submit_hypothesis_brief",
123
+ "submit_execution_plan_brief",
124
+ ],
125
+ extensions: false,
126
+ };
127
+
128
+ const doc = {
129
+ apiVersion: "harness.toolkit/v1",
130
+ kinds,
131
+ agents,
132
+ };
133
+
134
+ const yaml = [
135
+ "# Generated/maintained SSOT for harness agent tools (see ADR 0049).",
136
+ "# Regenerate hints: node .pi/scripts/generate-agents-policy-yaml.mjs",
137
+ "",
138
+ ];
139
+ const { stringify } = await import("yaml");
140
+ yaml.push(stringify(doc));
141
+ await writeFile(OUT, yaml.join("\n"), "utf8");
142
+ console.log(`Wrote ${OUT} (${Object.keys(agents).length} harness agents)`);
143
+ }
144
+
145
+ main().catch((err) => {
146
+ console.error(err);
147
+ process.exit(1);
148
+ });
@@ -20,6 +20,7 @@ const DEFAULT_RAW_DIR = "raw/graphify-kb-updates";
20
20
  const DEFAULT_DATA_DIR = "data";
21
21
  const DEFAULT_GRAPH_DIR = "graphify-out";
22
22
  const REQUIRED_RIGHTS = ["license", "access", "approved_by", "approved_at"];
23
+ const REQUIRED_PROVENANCE = ["origin", "locator"];
23
24
  const RISKY_KINDS = new Set(["book", "transcript", "youtube"]);
24
25
 
25
26
  function parseArgs(argv) {
@@ -92,6 +93,8 @@ function loadConfig(args) {
92
93
  allowlist: (cfg.allowlist ?? []).map((entry) => typeof entry === "string" ? { domain: entry, approved: true } : entry),
93
94
  reviewQueue: cfg.review_queue ?? [],
94
95
  articleQueries: cfg.article_queries ?? [],
96
+ repoSources: cfg.repo_sources ?? [],
97
+ releaseFeeds: cfg.release_feeds ?? [],
95
98
  paperFeeds: cfg.paper_feeds ?? [],
96
99
  localBooks: cfg.local_books ?? [{ path: "data/books" }],
97
100
  localTranscripts: cfg.local_transcripts ?? [{ path: "data/youtube-transcripts" }],
@@ -130,6 +133,11 @@ function hasRightsApproval(candidate) {
130
133
  return Boolean(r && REQUIRED_RIGHTS.every((k) => typeof r[k] === "string" && r[k].trim()));
131
134
  }
132
135
 
136
+ function hasCompleteProvenance(candidate) {
137
+ const p = candidate.provenance;
138
+ return Boolean(p && REQUIRED_PROVENANCE.every((k) => typeof p[k] === "string" && p[k].trim()));
139
+ }
140
+
133
141
  function urlDomain(url) {
134
142
  try { return new URL(url).hostname.replace(/^www\./, ""); } catch { return ""; }
135
143
  }
@@ -138,6 +146,14 @@ function allowlistEntry(cfg, domain) {
138
146
  return cfg.allowlist.find((entry) => entry.domain === domain || domain.endsWith(`.${entry.domain}`));
139
147
  }
140
148
 
149
+ function allowlistEntryAllows(entry, kind) {
150
+ return Boolean(entry?.approved === true && (!Array.isArray(entry.allowed_source_classes) || entry.allowed_source_classes.includes(kind)));
151
+ }
152
+
153
+ function allowlistAllows(candidate) {
154
+ return Boolean(candidate.allowlist_state.allowed && candidate.allowlist_state.approved && (!Array.isArray(candidate.allowlist_state.allowed_source_classes) || candidate.allowlist_state.allowed_source_classes.includes(candidate.kind)));
155
+ }
156
+
141
157
  function competitorLabels(cfg, candidate) {
142
158
  const haystack = `${candidate.title ?? ""} ${candidate.url ?? ""} ${candidate.path ?? ""}`.toLowerCase();
143
159
  const labels = [];
@@ -163,7 +179,7 @@ function normalizeCandidate(cfg, raw) {
163
179
  risk_class: raw.risk_class ?? taxonomy.risk_class ?? (RISKY_KINDS.has(raw.kind) ? "high" : "medium"),
164
180
  provenance: raw.provenance ?? { origin: raw.source_type, discovered_by: "graphify-kb-updater", locator: raw.url ?? raw.path ?? raw.query ?? null },
165
181
  rights_access: raw.rights_access ?? null,
166
- allowlist_state: allow ? { allowed: true, domain: allow.domain, approved: allow.approved === true, approved_by: allow.approved_by ?? null, approved_at: allow.approved_at ?? null } : { allowed: false },
182
+ allowlist_state: allow ? { allowed: true, domain: allow.domain, approved: allow.approved === true, approved_by: allow.approved_by ?? null, approved_at: allow.approved_at ?? null, allowed_source_classes: allow.allowed_source_classes ?? null } : { allowed: false },
167
183
  approval_state: raw.approved === true ? "approved" : "not_approved",
168
184
  };
169
185
  candidate.competitor_labels = raw.competitor_labels ?? competitorLabels(cfg, candidate);
@@ -175,12 +191,27 @@ function normalizeCandidate(cfg, raw) {
175
191
  function discoverCandidates(cfg, args) {
176
192
  const candidates = [];
177
193
  for (const query of cfg.articleQueries) candidates.push({ kind: "article", source_type: "web_search_query", title: query, query, review_required: true, promotion_policy: "stage_only" });
194
+ for (const repo of cfg.repoSources) {
195
+ const kind = "repo";
196
+ const domain = urlDomain(repo.url);
197
+ const allow = allowlistEntry(cfg, domain);
198
+ const explicit = cfg.autoPromoteAllowlist && allowlistEntryAllows(allow, kind) && repo.approved === true;
199
+ candidates.push({ ...repo, kind, source_type: "repo_metadata", domain, review_required: !explicit, promotion_policy: explicit ? "allowlist_auto_promote" : "manual_review", rights_access: repo.rights_access ?? null, provenance: repo.provenance });
200
+ }
201
+ for (const release of cfg.releaseFeeds) {
202
+ const kind = "release";
203
+ const domain = urlDomain(release.url);
204
+ const allow = allowlistEntry(cfg, domain);
205
+ const explicit = cfg.autoPromoteAllowlist && allowlistEntryAllows(allow, kind) && release.approved === true;
206
+ candidates.push({ ...release, kind, source_type: "release_metadata", domain, review_required: !explicit, promotion_policy: explicit ? "allowlist_auto_promote" : "manual_review", rights_access: release.rights_access ?? null, provenance: release.provenance });
207
+ }
178
208
  for (const feed of cfg.paperFeeds) candidates.push({ kind: "paper", source_type: "feed", title: feed.title ?? feed.url, url: feed.url, rights_access: feed.rights_access ?? null, review_required: true, promotion_policy: "stage_only", provenance: feed.provenance });
179
209
  for (const entry of cfg.reviewQueue) {
210
+ const kind = entry.kind ?? "article";
180
211
  const domain = urlDomain(entry.url);
181
212
  const allow = allowlistEntry(cfg, domain);
182
- const explicit = cfg.autoPromoteAllowlist && allow?.approved === true && entry.approved === true;
183
- candidates.push({ ...entry, kind: entry.kind ?? "article", source_type: "review_queue", domain, review_required: !explicit, promotion_policy: explicit ? "allowlist_auto_promote" : "manual_review", rights_access: entry.rights_access ?? null });
213
+ const explicit = cfg.autoPromoteAllowlist && allowlistEntryAllows(allow, kind) && entry.approved === true;
214
+ candidates.push({ ...entry, kind, source_type: "review_queue", domain, review_required: !explicit, promotion_policy: explicit ? "allowlist_auto_promote" : "manual_review", rights_access: entry.rights_access ?? null });
184
215
  }
185
216
  for (const spec of cfg.localBooks) for (const file of walkFiles(resolve(ROOT, spec.path), [".md", ".txt", ".pdf"], spec.max_files ?? 50)) candidates.push({ kind: "book", source_type: "local_file", title: basename(file), path: rel(file), rights_access: rightsFromSidecar(file), review_required: true, promotion_policy: "manual_review" });
186
217
  for (const spec of cfg.localTranscripts) for (const file of walkFiles(resolve(ROOT, spec.path), [".md", ".txt", ".vtt"], spec.max_files ?? 80)) candidates.push({ kind: "transcript", source_type: "local_file", title: basename(file), path: rel(file), rights_access: rightsFromSidecar(file), review_required: true, promotion_policy: "manual_review" });
@@ -208,10 +239,11 @@ function sourceBody(candidate) {
208
239
  }
209
240
 
210
241
  function promotionAllowed(candidate) {
242
+ if (!hasCompleteProvenance(candidate)) return { ok: false, reason: "missing_complete_provenance" };
211
243
  if (!hasRightsApproval(candidate)) return { ok: false, reason: "missing_rights_access_approval" };
212
244
  if (RISKY_KINDS.has(candidate.kind) && candidate.approved !== true) return { ok: false, reason: "manual_approval_required" };
213
- if (candidate.source_type === "review_queue" && candidate.promotion_policy === "allowlist_auto_promote" && candidate.allowlist_state.allowed && candidate.allowlist_state.approved) return { ok: true };
214
- return candidate.approved === true ? { ok: true } : { ok: false, reason: "manual_approval_required" };
245
+ if (candidate.promotion_policy === "allowlist_auto_promote" && candidate.approved === true && allowlistAllows(candidate)) return { ok: true, reason: "allowlist_auto_promote" };
246
+ return candidate.approved === true ? { ok: true, reason: "manual_approved" } : { ok: false, reason: "manual_approval_required" };
215
247
  }
216
248
 
217
249
  function promote(candidate, args) {
@@ -267,15 +299,19 @@ function pilotMetrics(summary) {
267
299
  function schedulerSmoke() {
268
300
  const service = readFileSync(resolve(ROOT, ".pi/harness/corpus/systemd/graphify-kb-updater.service"), "utf8");
269
301
  const timer = readFileSync(resolve(ROOT, ".pi/harness/corpus/systemd/graphify-kb-updater.timer"), "utf8");
302
+ const envTemplate = readFileSync(resolve(ROOT, ".pi/harness/corpus/systemd/graphify-kb-updater.env.template"), "utf8");
270
303
  const cron = readFileSync(resolve(ROOT, ".pi/harness/corpus/cron.example"), "utf8");
304
+ const graphifyArgs = envTemplate.match(/^GRAPHIFY_KB_ARGS=(.+)$/m)?.[1] ?? "";
271
305
  const checks = {
272
306
  systemd_daily: /OnCalendar=\*-\*-\*\s+08:30:00|OnCalendar=daily/i.test(timer),
273
307
  cron_daily: /^30\s+8\s+\*\s+\*\s+\*/m.test(cron),
274
308
  bounded_timeout: /timeout 45m/.test(service) && /timeout 45m/.test(cron),
275
309
  locked_no_overlap: /flock -n/.test(service) && /flock -n/.test(cron),
276
310
  explicit_env: /EnvironmentFile/.test(service) && /UP_ROOT/.test(cron),
311
+ working_directory: /WorkingDirectory=\$\{UP_ROOT\}/.test(service),
277
312
  logged: /StandardOutput=append/.test(service) && /HARNESS_GRAPHIFY_KB_LOG/.test(cron),
278
- refresh_intent: /--refresh-graph/.test(cron),
313
+ refresh_intent: /--refresh-graph/.test(cron) && /--refresh-graph/.test(graphifyArgs),
314
+ graphify_kb_args_template: /--apply/.test(graphifyArgs) && /--pilot-report/.test(graphifyArgs) && !/[;&|`$<>]/.test(graphifyArgs),
279
315
  };
280
316
  const ok = Object.values(checks).every(Boolean);
281
317
  console.log(JSON.stringify({ ok, checks }, null, 2));
@@ -305,8 +341,8 @@ function main() {
305
341
  }
306
342
  if (contentChanged) changedExisting++;
307
343
  const gate = promotionAllowed(c);
308
- registry.candidates[c.id] = { ...(prior ?? {}), ...c, first_seen_at: prior?.first_seen_at ?? runAt, last_seen_at: runAt, status: gate.ok ? "promotable" : "review_required", block_reason: gate.reason ?? null, content_state: contentChanged ? "changed" : "new" };
309
- if (!gate.ok) { blocked.push({ id: c.id, title: c.title, reason: gate.reason, allowlist_state: c.allowlist_state, category: c.category, competitor_labels: c.competitor_labels }); continue; }
344
+ registry.candidates[c.id] = { ...(prior ?? {}), ...c, first_seen_at: prior?.first_seen_at ?? runAt, last_seen_at: runAt, status: gate.ok ? "promotable" : "review_required", decision: gate.ok ? gate.reason : "stage_for_review", block_reason: gate.ok ? null : gate.reason, next_action: gate.ok ? "promote_on_apply" : "manual_review_required", content_state: contentChanged ? "changed" : "new" };
345
+ if (!gate.ok) { blocked.push({ id: c.id, title: c.title, kind: c.kind, source_type: c.source_type, reason: gate.reason, next_action: "manual_review_required", allowlist_state: c.allowlist_state, category: c.category, competitor_labels: c.competitor_labels }); continue; }
310
346
  planned.push(c);
311
347
  }
312
348
 
@@ -326,6 +362,7 @@ function main() {
326
362
 
327
363
  const graph = refreshGraph(args, promoted);
328
364
  const stale = registry.runs.at?.(-1)?.run_id ? [] : ["no_prior_apply_run_recorded"];
365
+ const reviewQueue = blocked.map((b) => ({ id: b.id, title: b.title, kind: b.kind, reason: b.reason, next_action: b.next_action })).slice(0, 50);
329
366
  const summary = {
330
367
  run_id: `kb-${Date.now()}`,
331
368
  last_run_at: runAt,
@@ -335,6 +372,7 @@ function main() {
335
372
  promoted_count: promoted,
336
373
  duplicate_skips: duplicates,
337
374
  blocked_count: blocked.length,
375
+ staged_count: blocked.length,
338
376
  skipped_count: skipped.length,
339
377
  failure_count: failed,
340
378
  changed_existing_count: changedExisting,
@@ -344,6 +382,8 @@ function main() {
344
382
  graph,
345
383
  exit_status: failed || graph.ok === false ? 1 : 0,
346
384
  promoted: promotedRefs,
385
+ review_queue_count: reviewQueue.length,
386
+ review_queue: reviewQueue,
347
387
  blocked: blocked.slice(0, 50),
348
388
  skipped: skipped.slice(0, 50),
349
389
  config: rel(cfg.path),
@@ -7,6 +7,7 @@
7
7
  * node .pi/scripts/harness-agents-manifest.mjs --check
8
8
  */
9
9
 
10
+ import { existsSync } from "node:fs";
10
11
  import { readFile, writeFile } from "node:fs/promises";
11
12
  import { join, dirname } from "node:path";
12
13
  import { fileURLToPath } from "node:url";
@@ -15,6 +16,10 @@ import {
15
16
  sha256Content,
16
17
  walkAgentsDir,
17
18
  } from "../lib/harness-agent-discovery.mjs";
19
+ import {
20
+ loadAgentsPolicyMerged,
21
+ packageAgentsPolicyPath,
22
+ } from "../lib/agents-policy.mjs";
18
23
 
19
24
  const ROOT = join(dirname(fileURLToPath(import.meta.url)), "..", "..");
20
25
  const MANIFEST_PATH = join(ROOT, ".pi", "harness", "agents.manifest.json");
@@ -27,7 +32,7 @@ async function readPackageMeta() {
27
32
  return { name: pkg.name ?? "ultimate-pi", version: pkg.version ?? "0.0.0" };
28
33
  }
29
34
 
30
- function buildManifest(packageFiles, packageName, packageVersion) {
35
+ async function buildManifest(packageFiles, packageName, packageVersion) {
31
36
  const agents = {};
32
37
  for (const f of packageFiles.values()) {
33
38
  agents[f.id] = {
@@ -35,11 +40,17 @@ function buildManifest(packageFiles, packageName, packageVersion) {
35
40
  sha256: sha256Content(f.content),
36
41
  };
37
42
  }
43
+ const policyPath = packageAgentsPolicyPath(ROOT);
44
+ let policy_sha256;
45
+ if (existsSync(policyPath)) {
46
+ policy_sha256 = sha256Content(await readFile(policyPath, "utf-8"));
47
+ }
38
48
  return {
39
49
  schema_version: "1.0.0",
40
50
  package: packageName,
41
51
  package_version: packageVersion,
42
52
  generated_at: new Date().toISOString(),
53
+ ...(policy_sha256 ? { policy_sha256 } : {}),
43
54
  agents,
44
55
  };
45
56
  }
@@ -68,6 +79,37 @@ function getDriftReport(manifest, packageFiles) {
68
79
  return { ok: items.length === 0, items };
69
80
  }
70
81
 
82
+ function frontmatterHasToolLists(content) {
83
+ const m = content.match(/^---\r?\n([\s\S]*?)\r?\n---/);
84
+ if (!m) return false;
85
+ return /^tools:/m.test(m[1]) || /^disallowed_tools:/m.test(m[1]);
86
+ }
87
+
88
+ function verifyAgentsPolicy(packageFiles) {
89
+ const items = [];
90
+ const policyPath = packageAgentsPolicyPath(ROOT);
91
+ if (!existsSync(policyPath)) {
92
+ return { ok: false, items: [{ id: "*", kind: "missing_agents_policy_yaml" }] };
93
+ }
94
+ const merged = loadAgentsPolicyMerged(ROOT, ROOT);
95
+ for (const [id, file] of packageFiles) {
96
+ if (!id.startsWith("harness/")) continue;
97
+ if (frontmatterHasToolLists(file.content)) {
98
+ items.push({ id, kind: "frontmatter_tools" });
99
+ }
100
+ if (!merged.agents.has(id)) {
101
+ items.push({ id, kind: "missing_policy_entry" });
102
+ }
103
+ }
104
+ for (const id of merged.agents.keys()) {
105
+ if (!id.startsWith("harness/")) continue;
106
+ if (!packageFiles.has(id)) {
107
+ items.push({ id, kind: "orphan_policy_entry" });
108
+ }
109
+ }
110
+ return { ok: items.length === 0, items };
111
+ }
112
+
71
113
  async function loadPackageFiles() {
72
114
  const files = new Map();
73
115
  walkAgentsDir(PACKAGE_AGENTS, "package", files);
@@ -81,10 +123,10 @@ async function main() {
81
123
  const mode = process.argv.includes("--check") ? "check" : "write";
82
124
  const { name, version } = await readPackageMeta();
83
125
  const packageFiles = await loadPackageFiles();
84
- const built = buildManifest(packageFiles, name, version);
126
+ const built = await buildManifest(packageFiles, name, version);
85
127
 
86
128
  if (mode === "write") {
87
- await writeFile(MANIFEST_PATH, `${JSON.stringify(built, null, 2)}\n`, "utf-8");
129
+ await writeFile(MANIFEST_PATH, `${JSON.stringify(built, null, "\t")}\n`, "utf-8");
88
130
  console.log(
89
131
  `Wrote ${MANIFEST_PATH} (${Object.keys(built.agents).length} agents)`,
90
132
  );
@@ -107,6 +149,19 @@ async function main() {
107
149
  process.exit(1);
108
150
  }
109
151
 
152
+ const policyCheck = verifyAgentsPolicy(packageFiles);
153
+ if (!policyCheck.ok) {
154
+ for (const item of policyCheck.items) {
155
+ console.error(`policy: ${item.id} (${item.kind})`);
156
+ }
157
+ process.exit(1);
158
+ }
159
+
160
+ if (built.policy_sha256 && onDisk.policy_sha256 !== built.policy_sha256) {
161
+ console.error("policy_sha256 mismatch — regenerate manifest with --write");
162
+ process.exit(1);
163
+ }
164
+
110
165
  if (onDisk.package_version !== version) {
111
166
  console.error(
112
167
  `package_version mismatch: manifest=${onDisk.package_version} package=${version}`,
@@ -114,7 +169,9 @@ async function main() {
114
169
  process.exit(1);
115
170
  }
116
171
 
117
- console.log(`agents.manifest.json OK (${Object.keys(built.agents).length} agents)`);
172
+ console.log(
173
+ `agents.manifest.json OK (${Object.keys(built.agents).length} agents, policy aligned)`,
174
+ );
118
175
  }
119
176
 
120
177
  main().catch((err) => {
@@ -0,0 +1,36 @@
1
+ #!/usr/bin/env npx tsx
2
+ import { readFileSync } from "node:fs";
3
+ import { dirname, join } from "node:path";
4
+ import { fileURLToPath } from "node:url";
5
+ import { doctorHarnessPolicies } from "../lib/agt/policy-engine.js";
6
+
7
+ const ROOT = join(dirname(fileURLToPath(import.meta.url)), "..", "..");
8
+ const doc = doctorHarnessPolicies(ROOT);
9
+ if (!doc.ok) {
10
+ console.error("AGT policy doctor failed:");
11
+ for (const e of doc.errors) console.error(` - ${e}`);
12
+ process.exit(1);
13
+ }
14
+ const pkg = JSON.parse(readFileSync(join(ROOT, "package.json"), "utf-8")) as {
15
+ files?: string[];
16
+ };
17
+ const files = pkg.files ?? [];
18
+ if (
19
+ !files.some(
20
+ (f: string) =>
21
+ f === ".pi/harness/policies" || f.startsWith(".pi/harness/policies/"),
22
+ )
23
+ ) {
24
+ console.error("package.json files[] missing .pi/harness/policies");
25
+ process.exit(1);
26
+ }
27
+ if (
28
+ !files.includes(".pi/lib") &&
29
+ !files.some((f) => f.startsWith(".pi/lib/"))
30
+ ) {
31
+ console.error("package.json files[] missing .pi/lib (ships .pi/lib/agt)");
32
+ process.exit(1);
33
+ }
34
+ console.log(
35
+ `AGT doctor OK (${doc.loaded.length} policies at ${doc.policyDir})`,
36
+ );
@@ -262,11 +262,18 @@ verify_cocoindex() {
262
262
 
263
263
  verify_biome() {
264
264
  log "[biome]"
265
- npm_global_install "@biomejs/biome" "biome" || { fail "biome npm install"; return; }
265
+ if [ ! -f "${ROOT}/package.json" ] && [ ! -f "${ROOT}/biome.json" ]; then
266
+ warn "biome skipped (non-JS/TS repo detected; optional tool)"
267
+ return
268
+ fi
269
+ npm_global_install "@biomejs/biome" "biome" || {
270
+ warn "biome npm install failed (optional — use your stack's formatter/linter)"
271
+ return
272
+ }
266
273
  if biome --version &>/dev/null; then
267
274
  pass "biome $(biome --version 2>/dev/null | head -1)"
268
275
  else
269
- fail "biome --version failed"
276
+ warn "biome --version failed (optional — use your stack's formatter/linter)"
270
277
  fi
271
278
  }
272
279
 
@@ -0,0 +1,129 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Toggle per-project harness governance — writes `.pi/harness/project.json`.
4
+ *
5
+ * Usage:
6
+ * node harness-project-toggle.mjs status [--project-root DIR]
7
+ * node harness-project-toggle.mjs enable [--project-root DIR]
8
+ * node harness-project-toggle.mjs disable [--project-root DIR]
9
+ */
10
+
11
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
12
+ import { dirname, join } from "node:path";
13
+ import { fileURLToPath } from "node:url";
14
+
15
+ const CONFIG_BASENAME = "project.json";
16
+
17
+ function parseArgs(argv) {
18
+ const args = [...argv];
19
+ let projectRoot = process.cwd();
20
+ const positional = [];
21
+ for (let i = 0; i < args.length; i++) {
22
+ const arg = args[i];
23
+ if (arg === "--project-root" && args[i + 1]) {
24
+ projectRoot = args[++i];
25
+ continue;
26
+ }
27
+ positional.push(arg);
28
+ }
29
+ return { projectRoot, action: positional[0] ?? "status" };
30
+ }
31
+
32
+ function configPath(projectRoot) {
33
+ return join(projectRoot, ".pi", "harness", CONFIG_BASENAME);
34
+ }
35
+
36
+ function envOverrideEnabled() {
37
+ const raw = process.env.HARNESS_ENABLED?.trim().toLowerCase();
38
+ if (!raw) return null;
39
+ if (raw === "0" || raw === "false" || raw === "no") return false;
40
+ if (raw === "1" || raw === "true" || raw === "yes") return true;
41
+ return null;
42
+ }
43
+
44
+ function readConfig(projectRoot) {
45
+ const fromEnv = envOverrideEnabled();
46
+ if (fromEnv !== null) {
47
+ return {
48
+ schema_version: "1.0.0",
49
+ enabled: fromEnv,
50
+ source: "env:HARNESS_ENABLED",
51
+ };
52
+ }
53
+
54
+ const path = configPath(projectRoot);
55
+ if (!existsSync(path)) {
56
+ return {
57
+ schema_version: "1.0.0",
58
+ enabled: true,
59
+ source: "default",
60
+ };
61
+ }
62
+
63
+ try {
64
+ const raw = JSON.parse(readFileSync(path, "utf8"));
65
+ if (typeof raw.enabled === "boolean") {
66
+ return {
67
+ schema_version: "1.0.0",
68
+ enabled: raw.enabled,
69
+ updated_at: raw.updated_at,
70
+ source: path,
71
+ };
72
+ }
73
+ } catch {
74
+ // fall through
75
+ }
76
+
77
+ return {
78
+ schema_version: "1.0.0",
79
+ enabled: true,
80
+ source: "default-corrupt-file",
81
+ };
82
+ }
83
+
84
+ function writeConfig(projectRoot, enabled) {
85
+ const path = configPath(projectRoot);
86
+ mkdirSync(dirname(path), { recursive: true });
87
+ const payload = {
88
+ schema_version: "1.0.0",
89
+ enabled,
90
+ updated_at: new Date().toISOString(),
91
+ };
92
+ writeFileSync(path, `${JSON.stringify(payload, null, "\t")}\n`, "utf8");
93
+ return { ...payload, path };
94
+ }
95
+
96
+ function main() {
97
+ const { projectRoot, action } = parseArgs(process.argv.slice(2));
98
+ if (!["status", "enable", "disable"].includes(action)) {
99
+ console.error(
100
+ "Usage: harness-project-toggle.mjs <status|enable|disable> [--project-root DIR]",
101
+ );
102
+ process.exit(1);
103
+ }
104
+
105
+ if (action === "status") {
106
+ const config = readConfig(projectRoot);
107
+ console.log(JSON.stringify({ ok: true, projectRoot, ...config }, null, 2));
108
+ return;
109
+ }
110
+
111
+ const enabled = action === "enable";
112
+ const written = writeConfig(projectRoot, enabled);
113
+ console.log(
114
+ JSON.stringify(
115
+ {
116
+ ok: true,
117
+ projectRoot,
118
+ enabled: written.enabled,
119
+ path: written.path,
120
+ updated_at: written.updated_at,
121
+ reload_required: true,
122
+ },
123
+ null,
124
+ 2,
125
+ ),
126
+ );
127
+ }
128
+
129
+ main();