zob-harness 0.1.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 (356) hide show
  1. package/.pi/adapters/registry.json +103 -0
  2. package/.pi/agents/architecture-cartographer.md +53 -0
  3. package/.pi/agents/chief-vision.md +39 -0
  4. package/.pi/agents/clarifier.md +58 -0
  5. package/.pi/agents/context-steward.md +52 -0
  6. package/.pi/agents/doc-steward.md +34 -0
  7. package/.pi/agents/explore.md +49 -0
  8. package/.pi/agents/factory.md +41 -0
  9. package/.pi/agents/implementer.md +44 -0
  10. package/.pi/agents/librarian.md +32 -0
  11. package/.pi/agents/oracle-merge.md +50 -0
  12. package/.pi/agents/oracle.md +55 -0
  13. package/.pi/agents/pattern-miner.md +53 -0
  14. package/.pi/agents/planner.md +39 -0
  15. package/.pi/agents/project-dna-golden-evaluator.md +32 -0
  16. package/.pi/agents/project-dna-ontology-steward.md +30 -0
  17. package/.pi/agents/project-dna-oracle.md +56 -0
  18. package/.pi/agents/project-dna-orchestrator.md +60 -0
  19. package/.pi/agents/project-dna-query-steward.md +38 -0
  20. package/.pi/agents/project-dna-safety-preflight.md +54 -0
  21. package/.pi/agents/project-dna-test-linker.md +27 -0
  22. package/.pi/agents/qa.md +38 -0
  23. package/.pi/agents/refactor-cartographer.md +28 -0
  24. package/.pi/agents/refactor-mover.md +31 -0
  25. package/.pi/agents/refactor-oracle.md +49 -0
  26. package/.pi/agents/repo-scout.md +60 -0
  27. package/.pi/agents/sample-architect.md +48 -0
  28. package/.pi/agents/specifier.md +57 -0
  29. package/.pi/agents/symbol-range-curator.md +41 -0
  30. package/.pi/agents/synthesis.md +52 -0
  31. package/.pi/agents/temp-agent-creator.md +35 -0
  32. package/.pi/autonomy-policy.json +67 -0
  33. package/.pi/budget-policy.json +54 -0
  34. package/.pi/capabilities/zob-public-runtime-capabilities.json +1700 -0
  35. package/.pi/chains/explore-plan-oracle.json +78 -0
  36. package/.pi/chains/explore-spec-clarify-plan-oracle.json +64 -0
  37. package/.pi/chains/explore-spec-plan-oracle.json +53 -0
  38. package/.pi/chains/spec-clarify-plan-oracle.json +53 -0
  39. package/.pi/chains/spec-factory-oracle.json +42 -0
  40. package/.pi/chains/spec-plan-oracle.json +42 -0
  41. package/.pi/compute-profiles/defaults.json +19 -0
  42. package/.pi/compute-profiles/overrides.json +13 -0
  43. package/.pi/compute-profiles/risk-rules.json +16 -0
  44. package/.pi/daemon-policy.json +80 -0
  45. package/.pi/damage-control-rules.json +45 -0
  46. package/.pi/extensions/zob-child-safety/index.ts +212 -0
  47. package/.pi/extensions/zob-harness/AGENTS.md +28 -0
  48. package/.pi/extensions/zob-harness/index.ts +391 -0
  49. package/.pi/extensions/zob-harness/src/AGENTS.md +25 -0
  50. package/.pi/extensions/zob-harness/src/agents.ts +82 -0
  51. package/.pi/extensions/zob-harness/src/autonomous-runtime.ts +2912 -0
  52. package/.pi/extensions/zob-harness/src/autonomy-readiness.ts +778 -0
  53. package/.pi/extensions/zob-harness/src/budget-policy.ts +308 -0
  54. package/.pi/extensions/zob-harness/src/capabilities.ts +249 -0
  55. package/.pi/extensions/zob-harness/src/child-runner.ts +249 -0
  56. package/.pi/extensions/zob-harness/src/chronicle.ts +262 -0
  57. package/.pi/extensions/zob-harness/src/compute-profile.ts +602 -0
  58. package/.pi/extensions/zob-harness/src/compute-workflow-shape.ts +168 -0
  59. package/.pi/extensions/zob-harness/src/coms-v2/AGENTS.md +16 -0
  60. package/.pi/extensions/zob-harness/src/coms-v2/envelope.ts +121 -0
  61. package/.pi/extensions/zob-harness/src/coms-v2/identity.ts +53 -0
  62. package/.pi/extensions/zob-harness/src/coms-v2/ledger-bridge.ts +67 -0
  63. package/.pi/extensions/zob-harness/src/coms-v2/local-transport.ts +147 -0
  64. package/.pi/extensions/zob-harness/src/coms-v2/pending-replies.ts +80 -0
  65. package/.pi/extensions/zob-harness/src/coms-v2/policy.ts +125 -0
  66. package/.pi/extensions/zob-harness/src/coms-v2/presence.ts +55 -0
  67. package/.pi/extensions/zob-harness/src/coms-v2/registry.ts +113 -0
  68. package/.pi/extensions/zob-harness/src/coms-v2/response-capture.ts +50 -0
  69. package/.pi/extensions/zob-harness/src/coms-v2/transcript-capture.ts +164 -0
  70. package/.pi/extensions/zob-harness/src/coms-v2/types.ts +149 -0
  71. package/.pi/extensions/zob-harness/src/coms-v2/zpeer-profile.ts +140 -0
  72. package/.pi/extensions/zob-harness/src/coms-v2/zpeer.ts +452 -0
  73. package/.pi/extensions/zob-harness/src/constants.ts +108 -0
  74. package/.pi/extensions/zob-harness/src/context-gbrain.ts +465 -0
  75. package/.pi/extensions/zob-harness/src/daemon-policy.ts +223 -0
  76. package/.pi/extensions/zob-harness/src/daemon-readiness.ts +134 -0
  77. package/.pi/extensions/zob-harness/src/daemon-runtime.ts +393 -0
  78. package/.pi/extensions/zob-harness/src/factory/AGENTS.md +24 -0
  79. package/.pi/extensions/zob-harness/src/factory/agentic-plan.ts +65 -0
  80. package/.pi/extensions/zob-harness/src/factory/quarantine.ts +319 -0
  81. package/.pi/extensions/zob-harness/src/factory/run.ts +520 -0
  82. package/.pi/extensions/zob-harness/src/factory/validation.ts +454 -0
  83. package/.pi/extensions/zob-harness/src/factory-selector.ts +318 -0
  84. package/.pi/extensions/zob-harness/src/full-autonomy-test.ts +226 -0
  85. package/.pi/extensions/zob-harness/src/git-ops.ts +868 -0
  86. package/.pi/extensions/zob-harness/src/goal-room.ts +178 -0
  87. package/.pi/extensions/zob-harness/src/goal-runtime.ts +1569 -0
  88. package/.pi/extensions/zob-harness/src/goal-todo-imports.ts +111 -0
  89. package/.pi/extensions/zob-harness/src/goal-todo-types.ts +231 -0
  90. package/.pi/extensions/zob-harness/src/goal-todos.ts +1410 -0
  91. package/.pi/extensions/zob-harness/src/goal.ts +152 -0
  92. package/.pi/extensions/zob-harness/src/governed-requests.ts +436 -0
  93. package/.pi/extensions/zob-harness/src/interactive-autonomy.ts +595 -0
  94. package/.pi/extensions/zob-harness/src/launch-apply.ts +313 -0
  95. package/.pi/extensions/zob-harness/src/merge-queue.ts +290 -0
  96. package/.pi/extensions/zob-harness/src/mission-control.ts +573 -0
  97. package/.pi/extensions/zob-harness/src/model-availability.ts +52 -0
  98. package/.pi/extensions/zob-harness/src/model-routing.ts +429 -0
  99. package/.pi/extensions/zob-harness/src/orchestration/AGENTS.md +23 -0
  100. package/.pi/extensions/zob-harness/src/orchestration/adaptive-delegation.ts +547 -0
  101. package/.pi/extensions/zob-harness/src/orchestration/adaptive-workflow.ts +585 -0
  102. package/.pi/extensions/zob-harness/src/orchestration/lead-plan.ts +192 -0
  103. package/.pi/extensions/zob-harness/src/orchestration/plan.ts +168 -0
  104. package/.pi/extensions/zob-harness/src/orchestration/room.ts +346 -0
  105. package/.pi/extensions/zob-harness/src/orchestration/run.ts +134 -0
  106. package/.pi/extensions/zob-harness/src/orchestration/supervised-readonly.ts +1147 -0
  107. package/.pi/extensions/zob-harness/src/orchestration/widget-readers.ts +132 -0
  108. package/.pi/extensions/zob-harness/src/output-contracts.ts +656 -0
  109. package/.pi/extensions/zob-harness/src/project-dna.ts +533 -0
  110. package/.pi/extensions/zob-harness/src/promotion/AGENTS.md +24 -0
  111. package/.pi/extensions/zob-harness/src/promotion/candidate.ts +336 -0
  112. package/.pi/extensions/zob-harness/src/promotion/coms.ts +127 -0
  113. package/.pi/extensions/zob-harness/src/promotion/documentation.ts +142 -0
  114. package/.pi/extensions/zob-harness/src/promotion/factory.ts +107 -0
  115. package/.pi/extensions/zob-harness/src/promotion/ledger.ts +2 -0
  116. package/.pi/extensions/zob-harness/src/promotion/temp-agent.ts +151 -0
  117. package/.pi/extensions/zob-harness/src/promotion/types.ts +149 -0
  118. package/.pi/extensions/zob-harness/src/promotion/validate.ts +6 -0
  119. package/.pi/extensions/zob-harness/src/promotion/write-lane.ts +162 -0
  120. package/.pi/extensions/zob-harness/src/prompt-packs.ts +239 -0
  121. package/.pi/extensions/zob-harness/src/queue.ts +386 -0
  122. package/.pi/extensions/zob-harness/src/rules.ts +225 -0
  123. package/.pi/extensions/zob-harness/src/runtime/AGENTS.md +26 -0
  124. package/.pi/extensions/zob-harness/src/runtime/adaptive-zmode.ts +116 -0
  125. package/.pi/extensions/zob-harness/src/runtime/auto-compaction.ts +715 -0
  126. package/.pi/extensions/zob-harness/src/runtime/commands.ts +1315 -0
  127. package/.pi/extensions/zob-harness/src/runtime/compaction-policy.ts +516 -0
  128. package/.pi/extensions/zob-harness/src/runtime/delegation-click-markers.ts +141 -0
  129. package/.pi/extensions/zob-harness/src/runtime/delegation-feed.ts +415 -0
  130. package/.pi/extensions/zob-harness/src/runtime/delegation-markdown.ts +97 -0
  131. package/.pi/extensions/zob-harness/src/runtime/delegation-monitor.ts +553 -0
  132. package/.pi/extensions/zob-harness/src/runtime/delegation-mouse.ts +205 -0
  133. package/.pi/extensions/zob-harness/src/runtime/delegation-overlay.ts +434 -0
  134. package/.pi/extensions/zob-harness/src/runtime/events.ts +736 -0
  135. package/.pi/extensions/zob-harness/src/runtime/goal-todo-overlay.ts +214 -0
  136. package/.pi/extensions/zob-harness/src/runtime/mode-intent.ts +144 -0
  137. package/.pi/extensions/zob-harness/src/runtime/plan-capture.ts +270 -0
  138. package/.pi/extensions/zob-harness/src/runtime/state.ts +403 -0
  139. package/.pi/extensions/zob-harness/src/runtime/tools-autonomous.ts +117 -0
  140. package/.pi/extensions/zob-harness/src/runtime/tools-compute.ts +136 -0
  141. package/.pi/extensions/zob-harness/src/runtime/tools-coms.ts +365 -0
  142. package/.pi/extensions/zob-harness/src/runtime/tools-context.ts +70 -0
  143. package/.pi/extensions/zob-harness/src/runtime/tools-delegation.ts +1854 -0
  144. package/.pi/extensions/zob-harness/src/runtime/tools-factory.ts +810 -0
  145. package/.pi/extensions/zob-harness/src/runtime/tools-goal-room.ts +46 -0
  146. package/.pi/extensions/zob-harness/src/runtime/tools-governed-requests.ts +38 -0
  147. package/.pi/extensions/zob-harness/src/runtime/tools-merge-queue.ts +61 -0
  148. package/.pi/extensions/zob-harness/src/runtime/tools-mission-control.ts +77 -0
  149. package/.pi/extensions/zob-harness/src/runtime/tools-orchestration.ts +106 -0
  150. package/.pi/extensions/zob-harness/src/runtime/tools-project-dna.ts +123 -0
  151. package/.pi/extensions/zob-harness/src/runtime/tools-worker-pool.ts +93 -0
  152. package/.pi/extensions/zob-harness/src/runtime/tools-workspace-claims.ts +62 -0
  153. package/.pi/extensions/zob-harness/src/runtime/tools-zcommit.ts +147 -0
  154. package/.pi/extensions/zob-harness/src/runtime/widget.ts +353 -0
  155. package/.pi/extensions/zob-harness/src/runtime/zobHarness.ts +60 -0
  156. package/.pi/extensions/zob-harness/src/safety.ts +338 -0
  157. package/.pi/extensions/zob-harness/src/sandbox.ts +1508 -0
  158. package/.pi/extensions/zob-harness/src/schemas-project-dna.ts +47 -0
  159. package/.pi/extensions/zob-harness/src/schemas.ts +695 -0
  160. package/.pi/extensions/zob-harness/src/telemetry.ts +373 -0
  161. package/.pi/extensions/zob-harness/src/topology/AGENTS.md +22 -0
  162. package/.pi/extensions/zob-harness/src/topology/chains.ts +236 -0
  163. package/.pi/extensions/zob-harness/src/topology/coms.ts +211 -0
  164. package/.pi/extensions/zob-harness/src/topology/orchestration-profiles.ts +204 -0
  165. package/.pi/extensions/zob-harness/src/topology/teams.ts +113 -0
  166. package/.pi/extensions/zob-harness/src/types/core.ts +47 -0
  167. package/.pi/extensions/zob-harness/src/types.ts +939 -0
  168. package/.pi/extensions/zob-harness/src/utils/AGENTS.md +22 -0
  169. package/.pi/extensions/zob-harness/src/utils/formatting.ts +34 -0
  170. package/.pi/extensions/zob-harness/src/utils/hashing.ts +11 -0
  171. package/.pi/extensions/zob-harness/src/utils/json.ts +28 -0
  172. package/.pi/extensions/zob-harness/src/utils/paths.ts +54 -0
  173. package/.pi/extensions/zob-harness/src/utils/records.ts +25 -0
  174. package/.pi/extensions/zob-harness/src/utils/resources.ts +38 -0
  175. package/.pi/extensions/zob-harness/src/worker-pool.ts +672 -0
  176. package/.pi/extensions/zob-harness/src/workspace-claims.ts +297 -0
  177. package/.pi/extensions/zob-switch/index.ts +180 -0
  178. package/.pi/factories/budget-preflight-dry-run/batch-manifest.json +59 -0
  179. package/.pi/factories/budget-preflight-dry-run/factory.json +94 -0
  180. package/.pi/factories/budget-preflight-dry-run/pilot-manifest.json +50 -0
  181. package/.pi/factories/budget-preflight-dry-run/smoke-manifest.json +43 -0
  182. package/.pi/factories/code-review-matrix/batch-manifest.json +61 -0
  183. package/.pi/factories/code-review-matrix/factory.json +163 -0
  184. package/.pi/factories/code-review-matrix/pilot-manifest.json +41 -0
  185. package/.pi/factories/code-review-matrix/smoke-manifest.json +35 -0
  186. package/.pi/factories/factory-forge/batch-manifest.json +56 -0
  187. package/.pi/factories/factory-forge/factory.json +84 -0
  188. package/.pi/factories/factory-forge/pilot-manifest.json +32 -0
  189. package/.pi/factories/factory-forge/smoke-manifest.json +19 -0
  190. package/.pi/factories/opencode-pattern-canonizer/batch-manifest.json +54 -0
  191. package/.pi/factories/opencode-pattern-canonizer/factory.json +86 -0
  192. package/.pi/factories/opencode-pattern-canonizer/pilot-manifest.json +39 -0
  193. package/.pi/factories/opencode-pattern-canonizer/smoke-manifest.json +26 -0
  194. package/.pi/factories/project-dna/README.md +182 -0
  195. package/.pi/factories/project-dna/batch-manifest.json +37 -0
  196. package/.pi/factories/project-dna/example-project-dna-manifest-v2.json +80 -0
  197. package/.pi/factories/project-dna/example-project-dna-manifest.json +58 -0
  198. package/.pi/factories/project-dna/factory.json +131 -0
  199. package/.pi/factories/project-dna/golden-cases-smoke.json +62 -0
  200. package/.pi/factories/project-dna/pi-agentic-ontology.json +88 -0
  201. package/.pi/factories/project-dna/pilot-manifest.json +32 -0
  202. package/.pi/factories/project-dna/schemas/benchmark-suite.schema.json +27 -0
  203. package/.pi/factories/project-dna/schemas/code-knowledge-graph.schema.json +97 -0
  204. package/.pi/factories/project-dna/schemas/context-pack.schema.json +43 -0
  205. package/.pi/factories/project-dna/schemas/golden-case.schema.json +36 -0
  206. package/.pi/factories/project-dna/schemas/manifest-v2.schema.json +128 -0
  207. package/.pi/factories/project-dna/schemas/manifest.schema.json +77 -0
  208. package/.pi/factories/project-dna/schemas/ontology.schema.json +45 -0
  209. package/.pi/factories/project-dna/schemas/project-fingerprint.schema.json +28 -0
  210. package/.pi/factories/project-dna/schemas/query-steward-report.schema.json +52 -0
  211. package/.pi/factories/project-dna/smoke-manifest.json +27 -0
  212. package/.pi/factories/roadmap-smoke-lots/batch-manifest.json +49 -0
  213. package/.pi/factories/roadmap-smoke-lots/factory.json +89 -0
  214. package/.pi/factories/roadmap-smoke-lots/pilot-manifest.json +50 -0
  215. package/.pi/factories/roadmap-smoke-lots/smoke-manifest.json +35 -0
  216. package/.pi/git-policy.json +120 -0
  217. package/.pi/mission-control/zob_coms_transport.json +64 -0
  218. package/.pi/model-catalog.example.json +345 -0
  219. package/.pi/model-economy.example.json +196 -0
  220. package/.pi/model-routing.json +86 -0
  221. package/.pi/orchestrations/adaptive-chief-vision.json +193 -0
  222. package/.pi/orchestrations/ceo-feature-build.json +182 -0
  223. package/.pi/orchestrations/readonly-dynamic-smoke.json +75 -0
  224. package/.pi/output-contracts/agent-event.v1.json +19 -0
  225. package/.pi/output-contracts/base.v1.json +24 -0
  226. package/.pi/output-contracts/brain-lookup.v1.json +21 -0
  227. package/.pi/output-contracts/clarification.v1.json +21 -0
  228. package/.pi/output-contracts/context-pack.v1.json +20 -0
  229. package/.pi/output-contracts/context-request.v1.json +21 -0
  230. package/.pi/output-contracts/context-steward.v1.json +19 -0
  231. package/.pi/output-contracts/context-writeback-proposal.v1.json +18 -0
  232. package/.pi/output-contracts/delegation-request.v1.json +21 -0
  233. package/.pi/output-contracts/explore.v1.json +52 -0
  234. package/.pi/output-contracts/factory.v1.json +48 -0
  235. package/.pi/output-contracts/guidance-steward.v1.json +18 -0
  236. package/.pi/output-contracts/implement.v1.json +40 -0
  237. package/.pi/output-contracts/launch-authorization.v1.json +21 -0
  238. package/.pi/output-contracts/lead-plan.v1.json +22 -0
  239. package/.pi/output-contracts/mission-readiness.v1.json +20 -0
  240. package/.pi/output-contracts/oracle-merge.v1.json +44 -0
  241. package/.pi/output-contracts/oracle-request.v1.json +20 -0
  242. package/.pi/output-contracts/oracle.v1.json +44 -0
  243. package/.pi/output-contracts/orchestration-profile.v1.json +22 -0
  244. package/.pi/output-contracts/plan.v1.json +48 -0
  245. package/.pi/output-contracts/prompt-pack.v1.json +20 -0
  246. package/.pi/output-contracts/qa.v1.json +40 -0
  247. package/.pi/output-contracts/research.v1.json +36 -0
  248. package/.pi/output-contracts/spec.v1.json +22 -0
  249. package/.pi/output-contracts/synthesis.v1.json +44 -0
  250. package/.pi/output-contracts/temp-agent-card.v1.json +23 -0
  251. package/.pi/output-contracts/todo-child-result.v1.json +20 -0
  252. package/.pi/output-contracts/todo-child-result.v2.json +22 -0
  253. package/.pi/output-contracts/todo-claim-validation.v1.json +22 -0
  254. package/.pi/output-contracts/todo-split-request.v1.json +20 -0
  255. package/.pi/prompts/adaptive-workflow.md +63 -0
  256. package/.pi/prompts/autonomous-runtime.md +15 -0
  257. package/.pi/prompts/benchmark-contender.md +15 -0
  258. package/.pi/prompts/benchmark-judge.md +19 -0
  259. package/.pi/prompts/clarify-spec.md +20 -0
  260. package/.pi/prompts/compute-plan.md +36 -0
  261. package/.pi/prompts/compute-preview.md +42 -0
  262. package/.pi/prompts/contract.md +29 -0
  263. package/.pi/prompts/explore.md +13 -0
  264. package/.pi/prompts/factory-run.md +36 -0
  265. package/.pi/prompts/factory.md +20 -0
  266. package/.pi/prompts/implement.md +27 -0
  267. package/.pi/prompts/model-catalog.md +68 -0
  268. package/.pi/prompts/model-economy.md +64 -0
  269. package/.pi/prompts/oracle-merge.md +18 -0
  270. package/.pi/prompts/oracle.md +13 -0
  271. package/.pi/prompts/orchestrator.md +48 -0
  272. package/.pi/prompts/parallel-review.md +21 -0
  273. package/.pi/prompts/plan.md +21 -0
  274. package/.pi/prompts/project-dna.md +90 -0
  275. package/.pi/prompts/refactor-oracle.md +23 -0
  276. package/.pi/prompts/refactor-slice.md +24 -0
  277. package/.pi/prompts/research.md +20 -0
  278. package/.pi/prompts/spec.md +19 -0
  279. package/.pi/prompts/synthesis.md +18 -0
  280. package/.pi/rules/always.md +38 -0
  281. package/.pi/rules/docs.md +32 -0
  282. package/.pi/rules/factory.md +44 -0
  283. package/.pi/rules/oracle.md +34 -0
  284. package/.pi/rules/orchestration.md +44 -0
  285. package/.pi/rules/project.md +34 -0
  286. package/.pi/rules/prompts.md +43 -0
  287. package/.pi/rules/runtime.md +43 -0
  288. package/.pi/rules/sandbox.md +43 -0
  289. package/.pi/settings.json +28 -0
  290. package/.pi/skills/zob-agentic-access/SKILL.md +20 -0
  291. package/.pi/skills/zob-autonomous-runtime/SKILL.md +41 -0
  292. package/.pi/skills/zob-commit/SKILL.md +79 -0
  293. package/.pi/skills/zob-compaction-policy/SKILL.md +92 -0
  294. package/.pi/skills/zob-compute-profile/SKILL.md +108 -0
  295. package/.pi/skills/zob-coms-safety/SKILL.md +54 -0
  296. package/.pi/skills/zob-coms-v2-live/SKILL.md +47 -0
  297. package/.pi/skills/zob-delegation-routing/SKILL.md +82 -0
  298. package/.pi/skills/zob-factory/SKILL.md +28 -0
  299. package/.pi/skills/zob-goal-todo-tree/SKILL.md +279 -0
  300. package/.pi/skills/zob-harness/SKILL.md +68 -0
  301. package/.pi/skills/zob-mission-control-coms/SKILL.md +39 -0
  302. package/.pi/skills/zob-oracle/SKILL.md +21 -0
  303. package/.pi/skills/zob-owner-pool-drill-writer/SKILL.md +244 -0
  304. package/.pi/skills/zob-owner-pool-launcher/SKILL.md +261 -0
  305. package/.pi/skills/zob-project-dna/SKILL.md +275 -0
  306. package/.pi/skills/zob-sandbox/SKILL.md +29 -0
  307. package/.pi/skills/zob-spec/SKILL.md +25 -0
  308. package/.pi/skills/zob-split-refactor/SKILL.md +39 -0
  309. package/.pi/skills/zob-tool-router/SKILL.md +104 -0
  310. package/.pi/teams/zob-core.json +122 -0
  311. package/AGENTS.md +89 -0
  312. package/CONTRIBUTING.md +56 -0
  313. package/LICENSE +21 -0
  314. package/README.md +360 -0
  315. package/SECURITY.md +35 -0
  316. package/SOURCE_INDEX.md +46 -0
  317. package/package.json +135 -0
  318. package/scripts/README.md +57 -0
  319. package/scripts/autonomy/mission-readiness-secret-smoke.mjs +90 -0
  320. package/scripts/compute-profile/plan-workflow.mjs +85 -0
  321. package/scripts/compute-profile/preview.mjs +242 -0
  322. package/scripts/compute-profile/regression-smoke.mjs +38 -0
  323. package/scripts/compute-profile/summarize.mjs +72 -0
  324. package/scripts/compute-profile/validate-policy.mjs +50 -0
  325. package/scripts/compute-profile/validate-preview.mjs +95 -0
  326. package/scripts/compute-profile/validate-workflow.mjs +58 -0
  327. package/scripts/git-ops/commit-policy-smoke.mjs +221 -0
  328. package/scripts/goal-todo/child-goal-ref-smoke.mjs +252 -0
  329. package/scripts/harness-switch/static-smoke.mjs +43 -0
  330. package/scripts/model-catalog/validate-economy.mjs +223 -0
  331. package/scripts/model-catalog/validate.mjs +199 -0
  332. package/scripts/package-surface/validate-script-refs.mjs +190 -0
  333. package/scripts/path-policy/validate-smoke.mjs +103 -0
  334. package/scripts/project-dna/bench-smoke.mjs +217 -0
  335. package/scripts/project-dna/build-capsules.mjs +207 -0
  336. package/scripts/project-dna/build-sample-spec.mjs +140 -0
  337. package/scripts/project-dna/emit-golden-cases.mjs +75 -0
  338. package/scripts/project-dna/emit-ontology.mjs +75 -0
  339. package/scripts/project-dna/generate-sample.mjs +302 -0
  340. package/scripts/project-dna/oracle-review-smoke.mjs +157 -0
  341. package/scripts/project-dna/plan-workflow.mjs +289 -0
  342. package/scripts/project-dna/query-context.mjs +276 -0
  343. package/scripts/project-dna/query-steward.mjs +149 -0
  344. package/scripts/project-dna/scan.mjs +553 -0
  345. package/scripts/project-dna/validate-5of5.mjs +159 -0
  346. package/scripts/project-dna/validate-golden-cases.mjs +78 -0
  347. package/scripts/project-dna/validate-ontology.mjs +97 -0
  348. package/scripts/project-dna/validate-sample-project.mjs +105 -0
  349. package/scripts/project-dna/validate-scaffold.mjs +383 -0
  350. package/scripts/project-dna/validate-scan-artifacts.mjs +187 -0
  351. package/scripts/project-dna/validate-workflow.mjs +166 -0
  352. package/scripts/start-pi.sh +4 -0
  353. package/scripts/worker-pool/static-smoke.mjs +54 -0
  354. package/scripts/zpeer-local-e2e-smoke.mjs +395 -0
  355. package/scripts/zpeer-static-smoke.mjs +129 -0
  356. package/tsconfig.json +12 -0
@@ -0,0 +1,868 @@
1
+ import { existsSync, readFileSync, statSync } from "node:fs";
2
+ import { relative, resolve } from "node:path";
3
+ import { createHash } from "node:crypto";
4
+ import { execFileSync } from "node:child_process";
5
+
6
+ import { pathMatches } from "./utils/paths.js";
7
+
8
+ export type ZcommitToggleState = "on" | "off";
9
+
10
+ export type ZcommitValidationMode = "off" | "advisory" | "blocking";
11
+ export type ZcommitFileSelectionStrategy = "agent_owned" | "workspace_filtered";
12
+
13
+ export interface ZcommitPolicy {
14
+ schema?: string;
15
+ defaults?: { autocommit?: ZcommitToggleState; autopush?: ZcommitToggleState };
16
+ commandSurface?: { allowedCommands?: string[]; aliasesAllowed?: boolean; explicitOnly?: boolean };
17
+ authorization?: { baseline?: string; autocommitRequires?: string[]; autopushRequires?: string[] };
18
+ commitMessage?: { allowedTypes?: string[]; format?: string; required?: boolean; requireImperativeSummary?: boolean; includeValidationEvidenceWhenUseful?: boolean };
19
+ validation?: { mode?: ZcommitValidationMode; runBeforeCommit?: boolean; requiredBeforeCommit?: boolean; requiredBeforePush?: boolean; recordCommandsAndResults?: boolean; allowExplicitException?: boolean };
20
+ fileSelection?: { strategy?: ZcommitFileSelectionStrategy; forbiddenPaths?: string[]; onlyAgentOwnedFiles?: boolean; excludeUnrelatedDirtyFiles?: boolean; bulkStageAllAllowed?: boolean };
21
+ remotes?: { allowed?: string[]; default?: string };
22
+ branches?: { allowedPatterns?: string[]; protectedDirectPushRequiresExplicitUserApproval?: string[] };
23
+ push?: { forcePushAllowed?: boolean; tagsAllowed?: boolean; pushAllAllowed?: boolean; setUpstreamAllowedOnlyWhenExplicit?: boolean };
24
+ }
25
+
26
+ export type ZcommitOwnershipSource = "local_tool_call" | "parent_accepted_child_claim" | "compaction_continuity" | "explicit_zcommit_adopt";
27
+
28
+ export interface ZcommitChildChangedPathRef {
29
+ path: string;
30
+ pathHash: string;
31
+ status: string;
32
+ contentHash?: string;
33
+ }
34
+
35
+ export interface ZcommitChildDirtySnapshot {
36
+ paths: Record<string, ZcommitChildChangedPathRef>;
37
+ errors: string[];
38
+ }
39
+
40
+ export interface ZcommitTouchedFileRecord {
41
+ path: string;
42
+ firstTouchedAt: string;
43
+ lastTouchedAt: string;
44
+ tools: Array<"edit" | "write">;
45
+ sources: ZcommitOwnershipSource[];
46
+ count: number;
47
+ }
48
+
49
+ export interface ZcommitOwnedPathRef {
50
+ path: string;
51
+ source: ZcommitOwnershipSource;
52
+ pathHash: string;
53
+ firstOwnedAt: string;
54
+ lastOwnedAt: string;
55
+ }
56
+
57
+ export interface ZcommitValidationRecord {
58
+ command: string[];
59
+ ok: boolean;
60
+ output: string;
61
+ ranAt: string;
62
+ }
63
+
64
+ export interface ZcommitLastCommitRecord {
65
+ hash: string;
66
+ shortHash: string;
67
+ subject: string;
68
+ files: string[];
69
+ remote?: string;
70
+ branch?: string;
71
+ createdAt: string;
72
+ pushedAt?: string;
73
+ }
74
+
75
+ export interface ZcommitRuntimeState {
76
+ autocommit: ZcommitToggleState;
77
+ autopush: ZcommitToggleState;
78
+ touchedFiles: Record<string, ZcommitTouchedFileRecord>;
79
+ ownedPathRefs: Record<string, ZcommitOwnedPathRef>;
80
+ lastValidation?: ZcommitValidationRecord;
81
+ lastCommit?: ZcommitLastCommitRecord;
82
+ sessionStartedAt?: string;
83
+ updatedAt?: string;
84
+ }
85
+
86
+ export interface GitDirtyFile {
87
+ path: string;
88
+ status: string;
89
+ indexStatus: string;
90
+ worktreeStatus: string;
91
+ originalPath?: string;
92
+ }
93
+
94
+ export interface ZcommitPlanFile extends GitDirtyFile {
95
+ owned: boolean;
96
+ forbidden: boolean;
97
+ staged: boolean;
98
+ reason?: string;
99
+ }
100
+
101
+ export interface ZcommitPlanOptions {
102
+ pathspecs?: string[];
103
+ message?: string;
104
+ body?: string[];
105
+ }
106
+
107
+ export interface ZcommitPlan {
108
+ schema: "zob.zcommit-plan.v1";
109
+ policyLoaded: boolean;
110
+ policyErrors: string[];
111
+ gitErrors: string[];
112
+ toggles: { autocommit: ZcommitToggleState; autopush: ZcommitToggleState };
113
+ selectionMode: ZcommitFileSelectionStrategy;
114
+ validationMode: ZcommitValidationMode;
115
+ selectionPathspecs: string[];
116
+ dirtyFiles: GitDirtyFile[];
117
+ touchedFiles: string[];
118
+ eligible: ZcommitPlanFile[];
119
+ excluded: ZcommitPlanFile[];
120
+ forbidden: ZcommitPlanFile[];
121
+ unexpectedStaged: ZcommitPlanFile[];
122
+ noShip: boolean;
123
+ noShipNotes: string[];
124
+ conventionalCommit: { type: string; scope: string; subject: string; body: string[] };
125
+ requiredValidation: string[][];
126
+ commitEnabled: boolean;
127
+ pushEnabled: boolean;
128
+ }
129
+
130
+ export interface ZcommitCommandResult {
131
+ ok: boolean;
132
+ action: "commit" | "push";
133
+ message: string;
134
+ plan: ZcommitPlan;
135
+ errors: string[];
136
+ validation?: ZcommitValidationRecord;
137
+ commit?: ZcommitLastCommitRecord;
138
+ actualGitCommitRun: boolean;
139
+ actualGitPushRun: boolean;
140
+ }
141
+
142
+ export interface ZcommitAdoptResult {
143
+ ok: boolean;
144
+ action: "adopt";
145
+ message: string;
146
+ requestedPaths: string[];
147
+ adopted: string[];
148
+ excluded: Array<{ path: string; reason: string; status?: string }>;
149
+ errors: string[];
150
+ plan: ZcommitPlan;
151
+ actualGitCommitRun: false;
152
+ actualGitPushRun: false;
153
+ }
154
+
155
+ export function createZcommitRuntimeState(policy?: ZcommitPolicy): ZcommitRuntimeState {
156
+ return {
157
+ autocommit: policy?.defaults?.autocommit === "on" ? "on" : "off",
158
+ autopush: policy?.defaults?.autopush === "on" ? "on" : "off",
159
+ touchedFiles: {},
160
+ ownedPathRefs: {},
161
+ sessionStartedAt: new Date().toISOString(),
162
+ };
163
+ }
164
+
165
+ export function normalizeRepoRelativePath(repoRoot: string, inputPath: string): string | undefined {
166
+ const root = resolve(repoRoot);
167
+ const absolutePath = resolve(root, inputPath);
168
+ if (absolutePath !== root && !absolutePath.startsWith(`${root}/`)) return undefined;
169
+ const rel = relative(root, absolutePath).replace(/\\/g, "/");
170
+ return rel.length > 0 ? rel : ".";
171
+ }
172
+
173
+ function hardForbiddenZcommitPatterns(policy: ZcommitPolicy): string[] {
174
+ return [
175
+ ...(policy.fileSelection?.forbiddenPaths ?? []),
176
+ ".git/",
177
+ ".env",
178
+ ".env.*",
179
+ "*.pem",
180
+ "*.key",
181
+ "node_modules/",
182
+ "dist/",
183
+ "build/",
184
+ "reports/",
185
+ ".pi/sessions/",
186
+ ".pi/agent-sessions/",
187
+ ".pi/tmp/",
188
+ ".pi/logs/",
189
+ ".pi/coms/",
190
+ ".pi/context/",
191
+ ".pi/goal-rooms/",
192
+ ".pi/workspace-claims/",
193
+ ".pi/worker-pools/",
194
+ ".pi/merge-queue/",
195
+ ];
196
+ }
197
+
198
+ function zcommitPathAllowed(repoRoot: string, policy: ZcommitPolicy, normalizedPath: string): boolean {
199
+ if (normalizedPath === "." || normalizedPath.trim().length === 0 || normalizedPath.includes("\0")) return false;
200
+ return !hardForbiddenZcommitPatterns(policy).some((pattern) => pathMatches(normalizedPath, pattern, repoRoot));
201
+ }
202
+
203
+ function zcommitPathHash(value: string): string {
204
+ return createHash("sha256").update(value).digest("hex");
205
+ }
206
+
207
+ function zcommitFileContentHash(repoRoot: string, path: string): string | undefined {
208
+ const absolutePath = resolve(repoRoot, path);
209
+ try {
210
+ const stat = statSync(absolutePath);
211
+ if (!stat.isFile()) return undefined;
212
+ return createHash("sha256").update(readFileSync(absolutePath)).digest("hex");
213
+ } catch {
214
+ return undefined;
215
+ }
216
+ }
217
+
218
+ function zcommitPathWithinAllowed(repoRoot: string, normalizedPath: string, allowedPaths: string[] | undefined): boolean {
219
+ if (!allowedPaths || allowedPaths.length === 0) return false;
220
+ return allowedPaths.some((allowedPath) => pathMatches(normalizedPath, allowedPath, repoRoot));
221
+ }
222
+
223
+ function hasZcommitAdoptWildcard(inputPath: string): boolean {
224
+ return /[*?[\]{}]/.test(inputPath);
225
+ }
226
+
227
+ function isExplicitZcommitAdoptArg(repoRoot: string, inputPath: string, normalizedPath: string): boolean {
228
+ if (inputPath.trim().length === 0) return false;
229
+ if (inputPath.startsWith("/")) return false;
230
+ if (inputPath.includes("\0")) return false;
231
+ if (hasZcommitAdoptWildcard(inputPath)) return false;
232
+ if (normalizedPath === "." || normalizedPath.trim().length === 0) return false;
233
+ try {
234
+ if (statSync(resolve(repoRoot, normalizedPath)).isDirectory()) return false;
235
+ } catch {
236
+ // Deleted dirty files may no longer exist on disk; exact deleted file paths remain adoptable.
237
+ }
238
+ return true;
239
+ }
240
+
241
+ function zcommitDirtyFileMatchesAdoptPath(file: GitDirtyFile, requestedPath: string): boolean {
242
+ return file.path === requestedPath || file.originalPath === requestedPath;
243
+ }
244
+
245
+ function hasZcommitPathspecWildcard(inputPath: string): boolean {
246
+ return /[*?[\]{}]/.test(inputPath);
247
+ }
248
+
249
+ function normalizeZcommitPathspec(repoRoot: string, inputPath: string): string | undefined {
250
+ const trimmed = inputPath.trim();
251
+ if (trimmed.length === 0 || trimmed.includes("\0")) return undefined;
252
+ if (trimmed === "." || trimmed === "./" || trimmed.startsWith("/") || trimmed.startsWith("~")) return undefined;
253
+ if (/(^|\/)\.\.(\/|$)/.test(trimmed)) return undefined;
254
+ const withoutDotSlash = trimmed.startsWith("./") ? trimmed.slice(2) : trimmed;
255
+ if (hasZcommitPathspecWildcard(withoutDotSlash)) return withoutDotSlash;
256
+ const normalized = normalizeRepoRelativePath(repoRoot, withoutDotSlash);
257
+ if (!normalized || normalized === ".") return undefined;
258
+ return withoutDotSlash.endsWith("/") ? `${normalized}/` : normalized;
259
+ }
260
+
261
+ function normalizeZcommitPathspecs(repoRoot: string, inputPathspecs: string[] = []): { pathspecs: string[]; errors: string[] } {
262
+ const pathspecs: string[] = [];
263
+ const errors: string[] = [];
264
+ for (const inputPathspec of inputPathspecs) {
265
+ const normalized = normalizeZcommitPathspec(repoRoot, inputPathspec);
266
+ if (!normalized) {
267
+ errors.push(`invalid zcommit pathspec (must be repo-relative file, directory, or glob): ${inputPathspec || "<empty>"}`);
268
+ continue;
269
+ }
270
+ pathspecs.push(normalized);
271
+ }
272
+ return { pathspecs: uniqueSorted(pathspecs), errors };
273
+ }
274
+
275
+ function zcommitPathspecMatchesDirtyFile(file: GitDirtyFile, pathspec: string, repoRoot: string): boolean {
276
+ return pathMatches(file.path, pathspec, repoRoot) || Boolean(file.originalPath && pathMatches(file.originalPath, pathspec, repoRoot));
277
+ }
278
+
279
+ export function captureZcommitChildDirtySnapshot(repoRoot: string, policyInput: { allowedPaths?: string[]; forbiddenPaths?: string[] } = {}): ZcommitChildDirtySnapshot {
280
+ const { policy } = readZcommitPolicy(repoRoot);
281
+ const mergedPolicy: ZcommitPolicy = {
282
+ ...policy,
283
+ fileSelection: {
284
+ ...(policy.fileSelection ?? {}),
285
+ forbiddenPaths: [...(policy.fileSelection?.forbiddenPaths ?? []), ...(policyInput.forbiddenPaths ?? [])],
286
+ },
287
+ };
288
+ const { files, errors } = readGitDirtyFiles(repoRoot);
289
+ const paths: Record<string, ZcommitChildChangedPathRef> = {};
290
+ for (const file of files) {
291
+ const normalizedPath = normalizeRepoRelativePath(repoRoot, file.path);
292
+ if (!normalizedPath) continue;
293
+ if (!zcommitPathAllowed(repoRoot, mergedPolicy, normalizedPath)) continue;
294
+ if (!zcommitPathWithinAllowed(repoRoot, normalizedPath, policyInput.allowedPaths)) continue;
295
+ paths[normalizedPath] = {
296
+ path: normalizedPath,
297
+ pathHash: zcommitPathHash(normalizedPath),
298
+ status: file.status,
299
+ contentHash: zcommitFileContentHash(repoRoot, normalizedPath),
300
+ };
301
+ }
302
+ return { paths, errors };
303
+ }
304
+
305
+ export function diffZcommitChildDirtySnapshots(before: ZcommitChildDirtySnapshot, after: ZcommitChildDirtySnapshot): ZcommitChildChangedPathRef[] {
306
+ return Object.values(after.paths)
307
+ .filter((afterRef) => {
308
+ const beforeRef = before.paths[afterRef.path];
309
+ if (!beforeRef) return true;
310
+ return beforeRef.status !== afterRef.status || beforeRef.contentHash !== afterRef.contentHash;
311
+ })
312
+ .sort((left, right) => left.path.localeCompare(right.path));
313
+ }
314
+
315
+ export function recordZcommitOwnedPaths(state: ZcommitRuntimeState, repoRoot: string, paths: ZcommitChildChangedPathRef[], source: ZcommitOwnershipSource, at = new Date().toISOString()): string[] {
316
+ const recorded: string[] = [];
317
+ for (const ref of paths) {
318
+ if (recordZcommitOwnedPath(state, repoRoot, ref.path, source, at)) recorded.push(ref.path);
319
+ }
320
+ return recorded.sort();
321
+ }
322
+
323
+ export function recordZcommitOwnedPath(state: ZcommitRuntimeState, repoRoot: string, inputPath: string, source: ZcommitOwnershipSource, at = new Date().toISOString()): boolean {
324
+ const normalizedPath = normalizeRepoRelativePath(repoRoot, inputPath);
325
+ if (!normalizedPath) return false;
326
+ const { policy } = readZcommitPolicy(repoRoot);
327
+ if (!zcommitPathAllowed(repoRoot, policy, normalizedPath)) return false;
328
+ const existing = state.ownedPathRefs[normalizedPath];
329
+ state.ownedPathRefs[normalizedPath] = existing
330
+ ? { ...existing, source: existing.source === source ? existing.source : source, lastOwnedAt: at }
331
+ : { path: normalizedPath, source, pathHash: zcommitPathHash(normalizedPath), firstOwnedAt: at, lastOwnedAt: at };
332
+ state.updatedAt = at;
333
+ return true;
334
+ }
335
+
336
+ export function recordZcommitTouchedFile(state: ZcommitRuntimeState, repoRoot: string, inputPath: string, tool: "edit" | "write", at = new Date().toISOString()): void {
337
+ if (!recordZcommitOwnedPath(state, repoRoot, inputPath, "local_tool_call", at)) return;
338
+ const normalizedPath = normalizeRepoRelativePath(repoRoot, inputPath);
339
+ if (!normalizedPath) return;
340
+ const existing = state.touchedFiles[normalizedPath];
341
+ const nextSources: ZcommitOwnershipSource[] = existing?.sources?.includes("local_tool_call") ? existing.sources : [...(existing?.sources ?? []), "local_tool_call"];
342
+ state.touchedFiles[normalizedPath] = existing
343
+ ? { ...existing, lastTouchedAt: at, tools: existing.tools.includes(tool) ? existing.tools : [...existing.tools, tool], sources: nextSources, count: existing.count + 1 }
344
+ : { path: normalizedPath, firstTouchedAt: at, lastTouchedAt: at, tools: [tool], sources: ["local_tool_call"], count: 1 };
345
+ state.updatedAt = at;
346
+ }
347
+
348
+ export function runGovernedZcommitAdopt(repoRoot: string, runtime: ZcommitRuntimeState, inputPaths: string[]): ZcommitAdoptResult {
349
+ const { policy, loaded, errors: policyErrors } = readZcommitPolicy(repoRoot);
350
+ const { files: dirtyFiles, errors: gitErrors } = readGitDirtyFiles(repoRoot);
351
+ const requestedPaths: string[] = [];
352
+ const requestedSet = new Set<string>();
353
+ const adopted: string[] = [];
354
+ const excluded: ZcommitAdoptResult["excluded"] = [];
355
+ const errors = [...policyErrors, ...gitErrors];
356
+ const at = new Date().toISOString();
357
+
358
+ if (!loaded) errors.push(".pi/git-policy.json must load before /zcommit adopt");
359
+ if (inputPaths.length === 0) errors.push("usage: /zcommit adopt <repo-relative dirty paths...>");
360
+
361
+ for (const inputPath of inputPaths) {
362
+ const normalizedPath = normalizeRepoRelativePath(repoRoot, inputPath);
363
+ if (!normalizedPath || !isExplicitZcommitAdoptArg(repoRoot, inputPath, normalizedPath)) {
364
+ errors.push(`adopt path rejected (must be exact repo-relative dirty file path, non-root, non-directory, no wildcards): ${inputPath || "<empty>"}`);
365
+ continue;
366
+ }
367
+ if (!zcommitPathAllowed(repoRoot, policy, normalizedPath)) {
368
+ excluded.push({ path: normalizedPath, reason: "forbidden_by_git_policy" });
369
+ continue;
370
+ }
371
+ if (!requestedSet.has(normalizedPath)) {
372
+ requestedSet.add(normalizedPath);
373
+ requestedPaths.push(normalizedPath);
374
+ }
375
+ }
376
+
377
+ for (const requestedPath of requestedPaths) {
378
+ const matches = dirtyFiles.filter((file) => zcommitDirtyFileMatchesAdoptPath(file, requestedPath));
379
+ if (matches.length === 0) {
380
+ excluded.push({ path: requestedPath, reason: "no_matching_dirty_file" });
381
+ continue;
382
+ }
383
+ for (const file of matches) {
384
+ const originalForbidden = file.originalPath ? !zcommitPathAllowed(repoRoot, policy, file.originalPath) : false;
385
+ if (!zcommitPathAllowed(repoRoot, policy, file.path) || originalForbidden) {
386
+ excluded.push({ path: file.path, reason: "forbidden_by_git_policy", status: file.status });
387
+ continue;
388
+ }
389
+ if (isStaged(file)) {
390
+ excluded.push({ path: file.path, reason: "staged_file_not_adopted", status: file.status });
391
+ errors.push(`adopt blocked for staged dirty path: ${file.path}`);
392
+ continue;
393
+ }
394
+ if (recordZcommitOwnedPath(runtime, repoRoot, file.path, "explicit_zcommit_adopt", at)) adopted.push(file.path);
395
+ }
396
+ }
397
+
398
+ const uniqueAdopted = uniqueSorted(adopted);
399
+ const plan = buildZcommitPlan(repoRoot, runtime);
400
+ const ok = errors.length === 0 && uniqueAdopted.length > 0;
401
+ const message = `zcommit adopt ${ok ? "completed" : "blocked"}: adopted=${uniqueAdopted.length} excluded=${excluded.length} errors=${errors.length}; plan eligible=${plan.eligible.length} commit=${plan.commitEnabled ? "gated" : "blocked"}`;
402
+ return { ok, action: "adopt", message, requestedPaths, adopted: uniqueAdopted, excluded, errors, plan, actualGitCommitRun: false, actualGitPushRun: false };
403
+ }
404
+
405
+ export function readZcommitPolicy(repoRoot: string): { policy: ZcommitPolicy; loaded: boolean; errors: string[] } {
406
+ const policyPath = resolve(repoRoot, ".pi/git-policy.json");
407
+ if (!existsSync(policyPath)) return { policy: {}, loaded: false, errors: [".pi/git-policy.json not found"] };
408
+ try {
409
+ const parsed = JSON.parse(readFileSync(policyPath, "utf8")) as ZcommitPolicy;
410
+ return { policy: parsed, loaded: true, errors: [] };
411
+ } catch (error) {
412
+ const message = error instanceof Error ? error.message : String(error);
413
+ return { policy: {}, loaded: false, errors: [`failed to parse .pi/git-policy.json: ${message}`] };
414
+ }
415
+ }
416
+
417
+ function runGit(repoRoot: string, args: string[]): string {
418
+ return execFileSync("git", args, { cwd: repoRoot, encoding: "utf8", stdio: ["ignore", "pipe", "pipe"] });
419
+ }
420
+
421
+ function parsePorcelainZ(stdout: string): GitDirtyFile[] {
422
+ const entries = stdout.split("\0").filter((entry) => entry.length > 0);
423
+ const files: GitDirtyFile[] = [];
424
+ for (let index = 0; index < entries.length; index += 1) {
425
+ const entry = entries[index];
426
+ const status = entry.slice(0, 2);
427
+ const path = entry.slice(3);
428
+ if (!path) continue;
429
+ const file: GitDirtyFile = { path, status, indexStatus: status[0] ?? " ", worktreeStatus: status[1] ?? " " };
430
+ if (status.includes("R") || status.includes("C")) {
431
+ const originalPath = entries[index + 1];
432
+ if (originalPath) {
433
+ file.originalPath = originalPath;
434
+ index += 1;
435
+ }
436
+ }
437
+ files.push(file);
438
+ }
439
+ return files;
440
+ }
441
+
442
+ export function readGitDirtyFiles(repoRoot: string): { files: GitDirtyFile[]; errors: string[] } {
443
+ try {
444
+ return { files: parsePorcelainZ(runGit(repoRoot, ["status", "--porcelain=v1", "-z", "--untracked-files=all"])), errors: [] };
445
+ } catch (error) {
446
+ const message = error instanceof Error ? error.message : String(error);
447
+ return { files: [], errors: [`git status failed: ${message}`] };
448
+ }
449
+ }
450
+
451
+ function isStaged(file: GitDirtyFile): boolean {
452
+ return file.indexStatus !== " " && file.indexStatus !== "?";
453
+ }
454
+
455
+ function uniqueSorted(values: string[]): string[] {
456
+ return [...new Set(values)].sort();
457
+ }
458
+
459
+ function sameStringSet(left: string[], right: string[]): boolean {
460
+ const a = uniqueSorted(left);
461
+ const b = uniqueSorted(right);
462
+ return a.length === b.length && a.every((value, index) => value === b[index]);
463
+ }
464
+
465
+ function zcommitValidationMode(policy: ZcommitPolicy): ZcommitValidationMode {
466
+ const mode = policy.validation?.mode;
467
+ if (mode === "off" || mode === "advisory" || mode === "blocking") return mode;
468
+ if (policy.validation?.requiredBeforeCommit === false) return "off";
469
+ return "blocking";
470
+ }
471
+
472
+ function zcommitFileSelectionStrategy(policy: ZcommitPolicy): ZcommitFileSelectionStrategy {
473
+ const strategy = policy.fileSelection?.strategy;
474
+ if (strategy === "agent_owned" || strategy === "workspace_filtered") return strategy;
475
+ if (policy.fileSelection?.onlyAgentOwnedFiles === false || policy.fileSelection?.bulkStageAllAllowed === true || policy.fileSelection?.excludeUnrelatedDirtyFiles === false) return "workspace_filtered";
476
+ return "agent_owned";
477
+ }
478
+
479
+ function defaultValidationCommands(policy: ZcommitPolicy): string[][] {
480
+ const mode = zcommitValidationMode(policy);
481
+ if (mode === "off") return [];
482
+ if (policy.validation?.runBeforeCommit === false) return [];
483
+ return [["npm", "run", "check", "--", "--pretty", "false"]];
484
+ }
485
+
486
+ function zcommitEligiblePaths(eligible: ZcommitPlanFile[]): string[] {
487
+ return eligible.map((file) => file.path);
488
+ }
489
+
490
+ function isWorkerPoolCommit(paths: string[]): boolean {
491
+ return paths.some((path) => path.includes("worker-pool") || path.includes("owner-pool") || path.includes("ZOB_PARALLEL_OWNER_POOLS"));
492
+ }
493
+
494
+ function inferConventionalCommitType(policy: ZcommitPolicy, eligible: ZcommitPlanFile[]): string {
495
+ const allowed = policy.commitMessage?.allowedTypes ?? ["feat", "fix", "docs", "test", "refactor", "chore", "ci", "build", "perf", "revert"];
496
+ const paths = zcommitEligiblePaths(eligible);
497
+ const preferred = isWorkerPoolCommit(paths)
498
+ ? "feat"
499
+ : paths.length > 0 && paths.every((path) => /(^docs\/|\.md$)/.test(path))
500
+ ? "docs"
501
+ : paths.some((path) => /(^test\/|\.test\.|\.spec\.|smoke)/.test(path))
502
+ ? "test"
503
+ : paths.some((path) => /(^package(-lock)?\.json$|^\.github\/|^scripts\/)/.test(path)) && !paths.some((path) => path.includes("src/"))
504
+ ? "chore"
505
+ : "chore";
506
+ return allowed.includes(preferred) ? preferred : (allowed.includes("chore") ? "chore" : (allowed[0] ?? "chore"));
507
+ }
508
+
509
+ function inferConventionalCommitScope(eligible: ZcommitPlanFile[]): string {
510
+ const paths = zcommitEligiblePaths(eligible);
511
+ if (isWorkerPoolCommit(paths)) return "worker-pool";
512
+ if (paths.some((path) => path.includes("zcommit") || path === ".pi/git-policy.json" || path.includes("git-ops"))) return "zcommit";
513
+ if (paths.some((path) => path.includes("runtime/"))) return "runtime";
514
+ if (paths.some((path) => path.startsWith("scripts/"))) return "tooling";
515
+ if (paths.length > 0 && paths.every((path) => /(^docs\/|\.md$)/.test(path))) return "docs";
516
+ return "workspace";
517
+ }
518
+
519
+ function inferredConventionalCommitPlan(policy: ZcommitPolicy, eligible: ZcommitPlanFile[], selectionMode: ZcommitFileSelectionStrategy, validationMode: ZcommitValidationMode): ZcommitPlan["conventionalCommit"] {
520
+ const type = inferConventionalCommitType(policy, eligible);
521
+ const scope = inferConventionalCommitScope(eligible);
522
+ if (scope === "worker-pool") {
523
+ return {
524
+ type,
525
+ scope,
526
+ subject: "add supervised owner micro-worker pools",
527
+ body: [
528
+ "Add metadata-only parallel owner micro-worker pool coordination with read-across/write-by-owner semantics, Goal Room owner requests/decisions, governed child owner-change extraction, workspace/sandbox/merge safety gates, public registry entries, prompts, skills, docs, and smoke coverage.",
529
+ `Validation mode: ${validationMode}.`,
530
+ `Eligible files: ${eligible.length}`,
531
+ ],
532
+ };
533
+ }
534
+ const subject = scope === "zcommit" ? "simplify governed commit workflow" : `update ${scope} changes`;
535
+ return {
536
+ type,
537
+ scope,
538
+ subject,
539
+ body: [
540
+ `Select safe workspace changes using ${selectionMode} mode.`,
541
+ `Keep Conventional Commit formatting as the primary commit policy.`,
542
+ `Validation mode: ${validationMode}.`,
543
+ `Eligible files: ${eligible.length}`,
544
+ ],
545
+ };
546
+ }
547
+
548
+ function parseConventionalCommitOverride(policy: ZcommitPolicy, message: string | undefined, body: string[] | undefined, fallback: ZcommitPlan["conventionalCommit"]): { commit: ZcommitPlan["conventionalCommit"]; errors: string[] } {
549
+ const trimmed = message?.trim();
550
+ if (!trimmed) return { commit: fallback, errors: [] };
551
+ const match = /^(\w+)(?:\(([A-Za-z0-9._-]+)\))?:\s+(.+)$/.exec(trimmed);
552
+ if (!match) return { commit: fallback, errors: [`invalid Conventional Commit message: ${trimmed}`] };
553
+ const [, type, rawScope, subject] = match;
554
+ const allowed = policy.commitMessage?.allowedTypes ?? ["feat", "fix", "docs", "test", "refactor", "chore", "ci", "build", "perf", "revert"];
555
+ const errors: string[] = [];
556
+ if (!allowed.includes(type)) errors.push(`commit type '${type}' is not allowed by .pi/git-policy.json`);
557
+ if (!subject || subject.trim().length === 0) errors.push("Conventional Commit subject must be non-empty");
558
+ if (trimmed.length > 180) errors.push("Conventional Commit subject line is too long for /zcommit override");
559
+ const safeBody = (body ?? []).map((line) => line.trim()).filter((line) => line.length > 0).slice(0, 20);
560
+ return {
561
+ commit: errors.length > 0 ? fallback : { type, scope: rawScope ?? fallback.scope, subject: subject.trim(), body: safeBody.length > 0 ? safeBody : fallback.body },
562
+ errors,
563
+ };
564
+ }
565
+
566
+ export function buildZcommitPlan(repoRoot: string, runtime: ZcommitRuntimeState, options: ZcommitPlanOptions = {}): ZcommitPlan {
567
+ const { policy, loaded, errors: policyErrors } = readZcommitPolicy(repoRoot);
568
+ const { files, errors: gitErrors } = readGitDirtyFiles(repoRoot);
569
+ const { pathspecs: selectionPathspecs, errors: pathspecErrors } = normalizeZcommitPathspecs(repoRoot, options.pathspecs ?? []);
570
+ const forbiddenPatterns = hardForbiddenZcommitPatterns(policy);
571
+ const touched = new Set([...Object.keys(runtime.touchedFiles), ...Object.keys(runtime.ownedPathRefs ?? {})]);
572
+ const selectionMode = zcommitFileSelectionStrategy(policy);
573
+ const validationMode = zcommitValidationMode(policy);
574
+ const workspaceFiltered = selectionMode === "workspace_filtered";
575
+ const eligible: ZcommitPlanFile[] = [];
576
+ const excluded: ZcommitPlanFile[] = [];
577
+ const forbidden: ZcommitPlanFile[] = [];
578
+ const unexpectedStaged: ZcommitPlanFile[] = [];
579
+
580
+ for (const file of files) {
581
+ const fileForbidden = forbiddenPatterns.some((pattern) => pathMatches(file.path, pattern, repoRoot));
582
+ const originalForbidden = file.originalPath ? forbiddenPatterns.some((pattern) => pathMatches(file.originalPath ?? "", pattern, repoRoot)) : false;
583
+ const isForbidden = fileForbidden || originalForbidden;
584
+ const owned = touched.has(file.path) || (file.originalPath ? touched.has(file.originalPath) : false);
585
+ const staged = isStaged(file);
586
+ const matchesPathspecs = selectionPathspecs.length === 0 || selectionPathspecs.some((pathspec) => zcommitPathspecMatchesDirtyFile(file, pathspec, repoRoot));
587
+ const planned: ZcommitPlanFile = { ...file, owned, forbidden: isForbidden, staged };
588
+ if (!matchesPathspecs) {
589
+ planned.reason = "outside_zcommit_pathspec_filter";
590
+ excluded.push(planned);
591
+ if (staged) unexpectedStaged.push(planned);
592
+ } else if (isForbidden) {
593
+ planned.reason = staged ? "forbidden_staged_path" : "forbidden_by_git_policy_excluded";
594
+ forbidden.push(planned);
595
+ excluded.push(planned);
596
+ if (staged) unexpectedStaged.push(planned);
597
+ } else if (workspaceFiltered || owned) {
598
+ eligible.push(planned);
599
+ } else {
600
+ planned.reason = staged ? "unexpected_staged_file_not_touched_by_current_session" : "dirty_file_not_touched_by_current_session";
601
+ excluded.push(planned);
602
+ if (staged) unexpectedStaged.push(planned);
603
+ }
604
+ }
605
+
606
+ const requiredValidation = defaultValidationCommands(policy);
607
+ const inferredCommitMessage = inferredConventionalCommitPlan(policy, eligible, selectionMode, validationMode);
608
+ const { commit: commitMessage, errors: commitMessageErrors } = parseConventionalCommitOverride(policy, options.message, options.body, inferredCommitMessage);
609
+ const forbiddenStaged = forbidden.filter((file) => file.staged);
610
+ const blockingNotes = [
611
+ ...policyErrors,
612
+ ...gitErrors,
613
+ ...pathspecErrors,
614
+ ...commitMessageErrors,
615
+ ...(!loaded ? ["abort: .pi/git-policy.json must load before governed commit/push"] : []),
616
+ ...(eligible.length === 0 ? [selectionPathspecs.length > 0 ? "abort: no safe dirty files match zcommit pathspecs" : workspaceFiltered ? "abort: no safe dirty workspace files to commit" : "abort: no eligible explicitly tracked agent-owned dirty files"] : []),
617
+ ...(forbiddenStaged.length > 0 ? [`abort: forbidden paths are already staged and must be unstaged first: ${forbiddenStaged.map((file) => file.path).join(", ")}`] : []),
618
+ ...(unexpectedStaged.some((file) => !file.forbidden) ? [selectionPathspecs.length > 0 ? "abort: pre-existing staged files outside zcommit pathspec selection" : workspaceFiltered ? "abort: pre-existing staged files outside safe workspace selection" : "abort: pre-existing staged files outside eligible touched set"] : []),
619
+ ];
620
+ const warningNotes = [
621
+ ...(forbidden.some((file) => !file.staged) ? ["warning: forbidden dirty paths are excluded from the easy commit"] : []),
622
+ ...(!workspaceFiltered && excluded.some((file) => file.reason === "dirty_file_not_touched_by_current_session") ? ["warning: unrelated unstaged dirty files are excluded and will not be staged"] : []),
623
+ ...(requiredValidation.length > 0 ? [`validation ${validationMode} before commit: ${requiredValidation.map((cmd) => cmd.join(" ")).join(" && ")}`] : ["validation off before commit"]),
624
+ ];
625
+ const commitEnabled = loaded && policyErrors.length === 0 && gitErrors.length === 0 && pathspecErrors.length === 0 && commitMessageErrors.length === 0 && eligible.length > 0 && forbiddenStaged.length === 0 && !unexpectedStaged.some((file) => !file.forbidden);
626
+ const pushEnabled = Boolean(runtime.lastCommit?.hash) && loaded && policyErrors.length === 0 && gitErrors.length === 0;
627
+
628
+ return {
629
+ schema: "zob.zcommit-plan.v1",
630
+ policyLoaded: loaded,
631
+ policyErrors,
632
+ gitErrors,
633
+ toggles: { autocommit: runtime.autocommit, autopush: runtime.autopush },
634
+ selectionMode,
635
+ validationMode,
636
+ selectionPathspecs,
637
+ dirtyFiles: files,
638
+ touchedFiles: [...touched].sort(),
639
+ eligible,
640
+ excluded,
641
+ forbidden,
642
+ unexpectedStaged,
643
+ noShip: blockingNotes.length > 0,
644
+ noShipNotes: [...blockingNotes, ...warningNotes],
645
+ conventionalCommit: commitMessage,
646
+ requiredValidation,
647
+ commitEnabled,
648
+ pushEnabled,
649
+ };
650
+ }
651
+
652
+ export function formatZcommitStatus(plan: ZcommitPlan): string {
653
+ const pathspecs = plan.selectionPathspecs.join(", ") || "all-safe-dirty";
654
+ return `zcommit status: mode=${plan.selectionMode} pathspecs=[${pathspecs}] validation=${plan.validationMode} autocommit=${plan.toggles.autocommit} autopush=${plan.toggles.autopush} policy=${plan.policyLoaded ? "loaded" : "missing/error"} dirty=${plan.dirtyFiles.length} touched=${plan.touchedFiles.length} eligible=${plan.eligible.length} excluded=${plan.excluded.length} forbidden=${plan.forbidden.length} unexpected_staged=${plan.unexpectedStaged.length} commit=${plan.commitEnabled ? "easy-ready" : "blocked"} push=${plan.pushEnabled ? "gated-ready" : "blocked"}`;
655
+ }
656
+
657
+ export function formatZcommitPlan(plan: ZcommitPlan): string {
658
+ const eligible = plan.eligible.map((file) => file.path).join(", ") || "none";
659
+ const excluded = plan.excluded.map((file) => `${file.path}${file.reason ? `(${file.reason})` : ""}`).join(", ") || "none";
660
+ const notes = plan.noShipNotes.join(" | ") || "none";
661
+ const commit = `${plan.conventionalCommit.type}(${plan.conventionalCommit.scope}): ${plan.conventionalCommit.subject}`;
662
+ const validation = plan.requiredValidation.map((cmd) => cmd.join(" ")).join(" && ") || "none";
663
+ const pathspecs = plan.selectionPathspecs.join(", ") || "all-safe-dirty";
664
+ return `zcommit plan: mode=${plan.selectionMode} pathspecs=[${pathspecs}] eligible=[${eligible}] excluded=[${excluded}] no_ship=${String(plan.noShip)} notes=${notes} validation_mode=${plan.validationMode} validation="${validation}" conventional_commit="${commit}" commit=${plan.commitEnabled ? "easy" : "blocked"} push=${plan.pushEnabled ? "gated" : "blocked"}`;
665
+ }
666
+
667
+ function cachedNames(repoRoot: string): string[] {
668
+ const stdout = runGit(repoRoot, ["diff", "--cached", "--name-only", "-z"]);
669
+ return stdout.split("\0").filter(Boolean).sort();
670
+ }
671
+
672
+ function cachedPatch(repoRoot: string, paths: string[]): string {
673
+ if (paths.length === 0) return "";
674
+ return runGit(repoRoot, ["diff", "--cached", "--binary", "--", ...paths]);
675
+ }
676
+
677
+ function restoreCachedPaths(repoRoot: string, paths: string[], preAddPatch: string): void {
678
+ if (paths.length === 0) return;
679
+ execFileSync("git", ["restore", "--staged", "--", ...paths], { cwd: repoRoot, encoding: "utf8", stdio: ["ignore", "pipe", "pipe"] });
680
+ if (preAddPatch.trim().length > 0) {
681
+ execFileSync("git", ["apply", "--cached", "--binary", "--whitespace=nowarn"], { cwd: repoRoot, input: preAddPatch, encoding: "utf8", stdio: ["pipe", "pipe", "pipe"] });
682
+ }
683
+ }
684
+
685
+ function currentHead(repoRoot: string): string {
686
+ return runGit(repoRoot, ["rev-parse", "HEAD"]).trim();
687
+ }
688
+
689
+ function currentBranch(repoRoot: string): string {
690
+ return runGit(repoRoot, ["branch", "--show-current"]).trim();
691
+ }
692
+
693
+ function runRequiredValidation(repoRoot: string, commands: string[][]): ZcommitValidationRecord {
694
+ const ranAt = new Date().toISOString();
695
+ const outputs: string[] = [];
696
+ try {
697
+ for (const command of commands) {
698
+ if (command.length === 0) continue;
699
+ const [bin, ...args] = command;
700
+ const output = execFileSync(bin, args, { cwd: repoRoot, encoding: "utf8", stdio: ["ignore", "pipe", "pipe"] });
701
+ outputs.push(output.trim());
702
+ }
703
+ return { command: commands.flatMap((cmd, index) => (index === 0 ? cmd : ["&&", ...cmd])), ok: true, output: outputs.join("\n").slice(-4000), ranAt };
704
+ } catch (error) {
705
+ const message = error instanceof Error ? error.message : String(error);
706
+ return { command: commands.flatMap((cmd, index) => (index === 0 ? cmd : ["&&", ...cmd])), ok: false, output: message.slice(-4000), ranAt };
707
+ }
708
+ }
709
+
710
+ function commitMessageArgs(plan: ZcommitPlan, validation: ZcommitValidationRecord): string[] {
711
+ const subject = `${plan.conventionalCommit.type}(${plan.conventionalCommit.scope}): ${plan.conventionalCommit.subject}`;
712
+ const validationLine = validation.command.length > 0 ? `Validation (${plan.validationMode}): ${validation.command.join(" ")} => ${validation.ok ? "passed" : "failed"}` : "Validation: not run";
713
+ const body = [...plan.conventionalCommit.body, validationLine].join("\n");
714
+ return ["commit", "-m", subject, "-m", body];
715
+ }
716
+
717
+ export function runGovernedZcommitCommit(repoRoot: string, runtime: ZcommitRuntimeState, options: ZcommitPlanOptions = {}): ZcommitCommandResult {
718
+ let plan = buildZcommitPlan(repoRoot, runtime, options);
719
+ if (!plan.commitEnabled) {
720
+ return { ok: false, action: "commit", message: `zcommit commit blocked: ${plan.noShipNotes.join(" | ") || "commit gates failed"}`, plan, errors: plan.noShipNotes, actualGitCommitRun: false, actualGitPushRun: false };
721
+ }
722
+
723
+ const eligiblePaths = uniqueSorted(plan.eligible.map((file) => file.path));
724
+ const preStaged = cachedNames(repoRoot);
725
+ const preAddPatch = cachedPatch(repoRoot, eligiblePaths);
726
+ let stagedByZcommit = false;
727
+ const cleanupStagedByZcommit = (): void => {
728
+ if (!stagedByZcommit) return;
729
+ restoreCachedPaths(repoRoot, eligiblePaths, preAddPatch);
730
+ stagedByZcommit = false;
731
+ };
732
+ const unexpectedPreStaged = preStaged.filter((file) => !eligiblePaths.includes(file));
733
+ if (unexpectedPreStaged.length > 0) {
734
+ const errors = ["pre-existing staged files outside safe zcommit selection", ...unexpectedPreStaged.map((file) => `unexpected staged: ${file}`)];
735
+ return { ok: false, action: "commit", message: `zcommit commit blocked: ${errors.join(" | ")}`, plan, errors, actualGitCommitRun: false, actualGitPushRun: false };
736
+ }
737
+
738
+ const validation = runRequiredValidation(repoRoot, plan.requiredValidation);
739
+ runtime.lastValidation = validation;
740
+ runtime.updatedAt = validation.ranAt;
741
+ if (!validation.ok && plan.validationMode === "blocking") {
742
+ return { ok: false, action: "commit", message: `zcommit commit blocked: validation failed for ${validation.command.join(" ")}`, plan, errors: [validation.output], validation, actualGitCommitRun: false, actualGitPushRun: false };
743
+ }
744
+
745
+ try {
746
+ execFileSync("git", ["add", "--", ...eligiblePaths], { cwd: repoRoot, encoding: "utf8", stdio: ["ignore", "pipe", "pipe"] });
747
+ stagedByZcommit = true;
748
+ const stagedAfterAdd = cachedNames(repoRoot);
749
+ if (!sameStringSet(stagedAfterAdd, eligiblePaths)) {
750
+ cleanupStagedByZcommit();
751
+ const errors = [`cached diff path set mismatch: cached=[${stagedAfterAdd.join(", ")}] eligible=[${eligiblePaths.join(", ")}]`];
752
+ return { ok: false, action: "commit", message: `zcommit commit blocked: ${errors[0]}`, plan, errors, validation, actualGitCommitRun: false, actualGitPushRun: false };
753
+ }
754
+ try {
755
+ runGit(repoRoot, ["diff", "--cached", "--check"]);
756
+ } catch (error) {
757
+ cleanupStagedByZcommit();
758
+ throw error;
759
+ }
760
+ try {
761
+ execFileSync("git", commitMessageArgs(plan, validation), { cwd: repoRoot, encoding: "utf8", stdio: ["ignore", "pipe", "pipe"] });
762
+ } catch (error) {
763
+ cleanupStagedByZcommit();
764
+ throw error;
765
+ }
766
+ stagedByZcommit = false;
767
+ const hash = currentHead(repoRoot);
768
+ const shortHash = runGit(repoRoot, ["rev-parse", "--short", "HEAD"]).trim();
769
+ const subject = `${plan.conventionalCommit.type}(${plan.conventionalCommit.scope}): ${plan.conventionalCommit.subject}`;
770
+ const createdAt = new Date().toISOString();
771
+ const commit: ZcommitLastCommitRecord = { hash, shortHash, subject, files: eligiblePaths, createdAt };
772
+ runtime.lastCommit = commit;
773
+ runtime.updatedAt = createdAt;
774
+ plan = buildZcommitPlan(repoRoot, runtime, options);
775
+ return { ok: true, action: "commit", message: `zcommit commit created ${shortHash}: ${subject}`, plan, errors: [], validation, commit, actualGitCommitRun: true, actualGitPushRun: false };
776
+ } catch (error) {
777
+ try {
778
+ cleanupStagedByZcommit();
779
+ } catch (cleanupError) {
780
+ const message = error instanceof Error ? error.message : String(error);
781
+ const cleanupMessage = cleanupError instanceof Error ? cleanupError.message : String(cleanupError);
782
+ return { ok: false, action: "commit", message: `zcommit commit failed: ${message}; cleanup failed: ${cleanupMessage}`, plan, errors: [message, `cleanup failed: ${cleanupMessage}`], validation, actualGitCommitRun: false, actualGitPushRun: false };
783
+ }
784
+ const message = error instanceof Error ? error.message : String(error);
785
+ return { ok: false, action: "commit", message: `zcommit commit failed: ${message}`, plan, errors: [message], validation, actualGitCommitRun: false, actualGitPushRun: false };
786
+ }
787
+ }
788
+
789
+ function patternToRegex(pattern: string): RegExp {
790
+ const escaped = pattern.replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*");
791
+ return new RegExp(`^${escaped}$`);
792
+ }
793
+
794
+ function branchAllowed(branch: string, patterns: string[]): boolean {
795
+ return patterns.some((pattern) => patternToRegex(pattern).test(branch));
796
+ }
797
+
798
+ export function runGovernedZcommitPush(repoRoot: string, runtime: ZcommitRuntimeState, options: { explicitPush: boolean } = { explicitPush: true }): ZcommitCommandResult {
799
+ const plan = buildZcommitPlan(repoRoot, runtime);
800
+ const errors: string[] = [];
801
+ const { policy, loaded, errors: policyErrors } = readZcommitPolicy(repoRoot);
802
+ if (!loaded) errors.push(".pi/git-policy.json must load before push");
803
+ errors.push(...policyErrors);
804
+ errors.push(...plan.gitErrors);
805
+ if (plan.unexpectedStaged.length > 0) errors.push(`unexpected staged files present before push: ${plan.unexpectedStaged.map((file) => file.path).join(", ")}`);
806
+ if (!runtime.lastCommit?.hash) errors.push("last commit must be created by /zcommit commit before push");
807
+ if (!options.explicitPush && runtime.autocommit !== "on") errors.push("autocommit is off and no explicit /zcommit push command was used");
808
+ if (!options.explicitPush && runtime.autopush !== "on") errors.push("autopush is off and no explicit /zcommit push command was used");
809
+
810
+ let head = "";
811
+ let branch = "";
812
+ try {
813
+ head = currentHead(repoRoot);
814
+ branch = currentBranch(repoRoot);
815
+ } catch (error) {
816
+ errors.push(error instanceof Error ? error.message : String(error));
817
+ }
818
+ if (runtime.lastCommit?.hash && head && runtime.lastCommit.hash !== head) errors.push("HEAD is not the last commit created by /zcommit commit");
819
+ if (!branch) errors.push("current branch is detached or unavailable");
820
+
821
+ const forbiddenPatterns = policy.fileSelection?.forbiddenPaths ?? [];
822
+ const forbiddenLastCommitFiles = (runtime.lastCommit?.files ?? []).filter((file) => forbiddenPatterns.some((pattern) => pathMatches(file, pattern, repoRoot)));
823
+ if (forbiddenLastCommitFiles.length > 0) errors.push(`last /zcommit commit contains forbidden files: ${forbiddenLastCommitFiles.join(", ")}`);
824
+
825
+ if (policy.validation?.requiredBeforePush !== false) {
826
+ const validation = runtime.lastValidation;
827
+ const commitCreatedAt = runtime.lastCommit?.createdAt;
828
+ if (!validation) {
829
+ errors.push("validation.requiredBeforePush requires successful validation before /zcommit push");
830
+ } else if (!validation.ok) {
831
+ errors.push("validation.requiredBeforePush requires the last validation to pass before /zcommit push");
832
+ } else if (!commitCreatedAt) {
833
+ errors.push("validation.requiredBeforePush requires last /zcommit commit creation time before /zcommit push");
834
+ } else {
835
+ const validationRanAt = Date.parse(validation.ranAt);
836
+ const lastCommitCreatedAt = Date.parse(commitCreatedAt);
837
+ if (Number.isNaN(validationRanAt) || Number.isNaN(lastCommitCreatedAt)) {
838
+ errors.push("validation.requiredBeforePush requires valid validation and commit timestamps before /zcommit push");
839
+ } else if (validationRanAt > lastCommitCreatedAt) {
840
+ errors.push("validation.requiredBeforePush requires validation to run before or at the last /zcommit commit creation");
841
+ }
842
+ }
843
+ }
844
+
845
+ const remote = policy.remotes?.default ?? "origin";
846
+ const allowedRemotes = policy.remotes?.allowed ?? [remote];
847
+ if (!allowedRemotes.includes(remote)) errors.push(`remote '${remote}' is not allowed by .pi/git-policy.json`);
848
+ const allowedBranches = policy.branches?.allowedPatterns ?? [];
849
+ if (branch && !branchAllowed(branch, allowedBranches)) errors.push(`branch '${branch}' is not allowed by .pi/git-policy.json`);
850
+ const protectedBranches = policy.branches?.protectedDirectPushRequiresExplicitUserApproval ?? [];
851
+ if (branch && protectedBranches.includes(branch) && !options.explicitPush) errors.push(`branch '${branch}' requires explicit /zcommit push approval`);
852
+ if (policy.push?.forcePushAllowed) errors.push("policy misconfiguration: forcePushAllowed must be false for governed zcommit push");
853
+ if (policy.push?.tagsAllowed) errors.push("policy misconfiguration: tagsAllowed must be false for governed zcommit push");
854
+ if (policy.push?.pushAllAllowed) errors.push("policy misconfiguration: pushAllAllowed must be false for governed zcommit push");
855
+
856
+ if (errors.length > 0) return { ok: false, action: "push", message: `zcommit push blocked: ${errors.join(" | ")}`, plan, errors, actualGitCommitRun: false, actualGitPushRun: false };
857
+
858
+ try {
859
+ execFileSync("git", ["push", remote, `HEAD:${branch}`], { cwd: repoRoot, encoding: "utf8", stdio: ["ignore", "pipe", "pipe"] });
860
+ const pushedAt = new Date().toISOString();
861
+ runtime.lastCommit = { ...runtime.lastCommit!, remote, branch, pushedAt };
862
+ runtime.updatedAt = pushedAt;
863
+ return { ok: true, action: "push", message: `zcommit push completed: ${remote} HEAD:${branch}`, plan: buildZcommitPlan(repoRoot, runtime), errors: [], commit: runtime.lastCommit, actualGitCommitRun: false, actualGitPushRun: true };
864
+ } catch (error) {
865
+ const message = error instanceof Error ? error.message : String(error);
866
+ return { ok: false, action: "push", message: `zcommit push failed: ${message}`, plan, errors: [message], actualGitCommitRun: false, actualGitPushRun: false };
867
+ }
868
+ }