zob-harness 0.2.0 → 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (253) hide show
  1. package/.pi/capabilities/zob-public-runtime-capabilities.json +4 -4
  2. package/.pi/extensions/zob-child-safety/AGENTS.md +12 -0
  3. package/.pi/extensions/zob-child-safety/index.ts +4 -105
  4. package/.pi/extensions/zob-child-safety/src/AGENTS.md +10 -0
  5. package/.pi/extensions/zob-child-safety/src/policy.ts +106 -0
  6. package/.pi/extensions/zob-harness/AGENTS.md +2 -0
  7. package/.pi/extensions/zob-harness/index.ts +94 -94
  8. package/.pi/extensions/zob-harness/src/AGENTS.md +4 -0
  9. package/.pi/extensions/zob-harness/src/core/AGENTS.md +24 -0
  10. package/.pi/extensions/zob-harness/src/{constants.ts → core/constants.ts} +10 -1
  11. package/.pi/extensions/zob-harness/src/core/utils/AGENTS.md +23 -0
  12. package/.pi/extensions/zob-harness/src/{utils → core/utils}/formatting.ts +1 -1
  13. package/.pi/extensions/zob-harness/src/{utils → core/utils}/records.ts +1 -1
  14. package/.pi/extensions/zob-harness/src/{utils → core/utils}/resources.ts +1 -1
  15. package/.pi/extensions/zob-harness/src/domains/AGENTS.md +23 -0
  16. package/.pi/extensions/zob-harness/src/domains/autonomy/AGENTS.md +21 -0
  17. package/.pi/extensions/zob-harness/src/{autonomous-runtime.ts → domains/autonomy/autonomous-runtime.ts} +22 -22
  18. package/.pi/extensions/zob-harness/src/{autonomy-readiness.ts → domains/autonomy/autonomy-readiness.ts} +18 -18
  19. package/.pi/extensions/zob-harness/src/{daemon-policy.ts → domains/autonomy/daemon-policy.ts} +6 -6
  20. package/.pi/extensions/zob-harness/src/{daemon-readiness.ts → domains/autonomy/daemon-readiness.ts} +7 -7
  21. package/.pi/extensions/zob-harness/src/{daemon-runtime.ts → domains/autonomy/daemon-runtime.ts} +2 -2
  22. package/.pi/extensions/zob-harness/src/{full-autonomy-test.ts → domains/autonomy/full-autonomy-test.ts} +4 -4
  23. package/.pi/extensions/zob-harness/src/{interactive-autonomy.ts → domains/autonomy/interactive-autonomy.ts} +2 -2
  24. package/.pi/extensions/zob-harness/src/{compute-profile.ts → domains/compute/compute-profile.ts} +3 -3
  25. package/.pi/extensions/zob-harness/src/{compute-workflow-shape.ts → domains/compute/compute-workflow-shape.ts} +3 -3
  26. package/.pi/extensions/zob-harness/src/domains/coms/AGENTS.md +21 -0
  27. package/.pi/extensions/zob-harness/src/{coms-v2 → domains/coms/coms-v2}/envelope.ts +2 -2
  28. package/.pi/extensions/zob-harness/src/{coms-v2 → domains/coms/coms-v2}/identity.ts +3 -3
  29. package/.pi/extensions/zob-harness/src/{coms-v2 → domains/coms/coms-v2}/ledger-bridge.ts +2 -2
  30. package/.pi/extensions/zob-harness/src/{coms-v2 → domains/coms/coms-v2}/local-transport.ts +1 -1
  31. package/.pi/extensions/zob-harness/src/{coms-v2 → domains/coms/coms-v2}/policy.ts +3 -3
  32. package/.pi/extensions/zob-harness/src/{coms-v2 → domains/coms/coms-v2}/presence.ts +1 -1
  33. package/.pi/extensions/zob-harness/src/{coms-v2 → domains/coms/coms-v2}/registry.ts +47 -4
  34. package/.pi/extensions/zob-harness/src/{coms-v2 → domains/coms/coms-v2}/response-capture.ts +1 -1
  35. package/.pi/extensions/zob-harness/src/{coms-v2 → domains/coms/coms-v2}/transcript-capture.ts +2 -2
  36. package/.pi/extensions/zob-harness/src/{coms-v2 → domains/coms/coms-v2}/zpeer-profile.ts +3 -3
  37. package/.pi/extensions/zob-harness/src/{coms-v2 → domains/coms/coms-v2}/zpeer.ts +35 -7
  38. package/.pi/extensions/zob-harness/src/{mission-control.ts → domains/coms/mission-control.ts} +12 -9
  39. package/.pi/extensions/zob-harness/src/{zagents.ts → domains/coms/zagents.ts} +86 -4
  40. package/.pi/extensions/zob-harness/src/domains/context/AGENTS.md +20 -0
  41. package/.pi/extensions/zob-harness/src/{context-gbrain.ts → domains/context/context-gbrain.ts} +4 -4
  42. package/.pi/extensions/zob-harness/src/domains/delegation/AGENTS.md +21 -0
  43. package/.pi/extensions/zob-harness/src/{agents.ts → domains/delegation/agents.ts} +2 -2
  44. package/.pi/extensions/zob-harness/src/{capabilities.ts → domains/delegation/capabilities.ts} +6 -6
  45. package/.pi/extensions/zob-harness/src/{child-runner.ts → domains/delegation/child-runner.ts} +8 -8
  46. package/.pi/extensions/zob-harness/src/{output-contracts.ts → domains/delegation/output-contracts.ts} +1 -1
  47. package/.pi/extensions/zob-harness/src/{prompt-packs.ts → domains/delegation/prompt-packs.ts} +4 -4
  48. package/.pi/extensions/zob-harness/src/domains/factory/AGENTS.md +21 -0
  49. package/.pi/extensions/zob-harness/src/{factory → domains/factory}/agentic-plan.ts +1 -1
  50. package/.pi/extensions/zob-harness/src/{factory-selector.ts → domains/factory/factory-selector.ts} +4 -4
  51. package/.pi/extensions/zob-harness/src/{factory → domains/factory}/quarantine.ts +5 -5
  52. package/.pi/extensions/zob-harness/src/{factory → domains/factory}/run.ts +5 -5
  53. package/.pi/extensions/zob-harness/src/{factory → domains/factory}/validation.ts +10 -10
  54. package/.pi/extensions/zob-harness/src/domains/git/AGENTS.md +20 -0
  55. package/.pi/extensions/zob-harness/src/{git-ops.ts → domains/git/git-ops.ts} +3 -3
  56. package/.pi/extensions/zob-harness/src/domains/goal/AGENTS.md +22 -0
  57. package/.pi/extensions/zob-harness/src/{goal-room.ts → domains/goal/goal-room.ts} +7 -7
  58. package/.pi/extensions/zob-harness/src/{goal-todo-imports.ts → domains/goal/goal-todo-imports.ts} +1 -1
  59. package/.pi/extensions/zob-harness/src/{goal-todo-types.ts → domains/goal/goal-todo-types.ts} +1 -1
  60. package/.pi/extensions/zob-harness/src/{goal-todos.ts → domains/goal/goal-todos.ts} +4 -4
  61. package/.pi/extensions/zob-harness/src/{goal.ts → domains/goal/goal.ts} +1 -1
  62. package/.pi/extensions/zob-harness/src/domains/governance/AGENTS.md +21 -0
  63. package/.pi/extensions/zob-harness/src/{budget-policy.ts → domains/governance/budget-policy.ts} +6 -6
  64. package/.pi/extensions/zob-harness/src/{governed-requests.ts → domains/governance/governed-requests.ts} +7 -7
  65. package/.pi/extensions/zob-harness/src/{launch-apply.ts → domains/governance/launch-apply.ts} +4 -4
  66. package/.pi/extensions/zob-harness/src/{merge-queue.ts → domains/governance/merge-queue.ts} +6 -6
  67. package/.pi/extensions/zob-harness/src/{rules.ts → domains/governance/rules.ts} +7 -7
  68. package/.pi/extensions/zob-harness/src/{safety.ts → domains/governance/safety.ts} +3 -3
  69. package/.pi/extensions/zob-harness/src/{sandbox.ts → domains/governance/sandbox.ts} +5 -5
  70. package/.pi/extensions/zob-harness/src/{worker-pool.ts → domains/governance/worker-pool.ts} +7 -7
  71. package/.pi/extensions/zob-harness/src/{workspace-claims.ts → domains/governance/workspace-claims.ts} +6 -6
  72. package/.pi/extensions/zob-harness/src/domains/models/AGENTS.md +20 -0
  73. package/.pi/extensions/zob-harness/src/{model-availability.ts → domains/models/model-availability.ts} +1 -1
  74. package/.pi/extensions/zob-harness/src/{model-routing.ts → domains/models/model-routing.ts} +6 -6
  75. package/.pi/extensions/zob-harness/src/domains/orchestration/AGENTS.md +21 -0
  76. package/.pi/extensions/zob-harness/src/{orchestration → domains/orchestration}/adaptive-delegation.ts +8 -8
  77. package/.pi/extensions/zob-harness/src/{orchestration → domains/orchestration}/adaptive-workflow.ts +4 -4
  78. package/.pi/extensions/zob-harness/src/{orchestration → domains/orchestration}/lead-plan.ts +5 -5
  79. package/.pi/extensions/zob-harness/src/{orchestration → domains/orchestration}/plan.ts +5 -5
  80. package/.pi/extensions/zob-harness/src/{orchestration → domains/orchestration}/room.ts +4 -4
  81. package/.pi/extensions/zob-harness/src/{orchestration → domains/orchestration}/run.ts +6 -6
  82. package/.pi/extensions/zob-harness/src/{orchestration → domains/orchestration}/supervised-readonly.ts +6 -6
  83. package/.pi/extensions/zob-harness/src/{orchestration → domains/orchestration}/widget-readers.ts +2 -2
  84. package/.pi/extensions/zob-harness/src/domains/project-dna/AGENTS.md +19 -0
  85. package/.pi/extensions/zob-harness/src/{project-dna.ts → domains/project-dna/project-dna.ts} +4 -4
  86. package/.pi/extensions/zob-harness/src/{promotion → domains/promotion}/candidate.ts +5 -5
  87. package/.pi/extensions/zob-harness/src/{promotion → domains/promotion}/coms.ts +3 -3
  88. package/.pi/extensions/zob-harness/src/{promotion → domains/promotion}/documentation.ts +4 -4
  89. package/.pi/extensions/zob-harness/src/{promotion → domains/promotion}/factory.ts +2 -2
  90. package/.pi/extensions/zob-harness/src/{promotion → domains/promotion}/temp-agent.ts +4 -4
  91. package/.pi/extensions/zob-harness/src/{promotion → domains/promotion}/write-lane.ts +3 -3
  92. package/.pi/extensions/zob-harness/src/domains/telemetry/AGENTS.md +19 -0
  93. package/.pi/extensions/zob-harness/src/{chronicle.ts → domains/telemetry/chronicle.ts} +3 -3
  94. package/.pi/extensions/zob-harness/src/{queue.ts → domains/telemetry/queue.ts} +7 -7
  95. package/.pi/extensions/zob-harness/src/{telemetry.ts → domains/telemetry/telemetry.ts} +4 -4
  96. package/.pi/extensions/zob-harness/src/{topology → domains/topology}/chains.ts +10 -10
  97. package/.pi/extensions/zob-harness/src/{topology → domains/topology}/coms.ts +3 -3
  98. package/.pi/extensions/zob-harness/src/{topology → domains/topology}/orchestration-profiles.ts +8 -8
  99. package/.pi/extensions/zob-harness/src/{topology → domains/topology}/teams.ts +8 -8
  100. package/.pi/extensions/zob-harness/src/runtime/adaptive-zmode.ts +2 -2
  101. package/.pi/extensions/zob-harness/src/runtime/auto-compaction.ts +2 -2
  102. package/.pi/extensions/zob-harness/src/runtime/commands.ts +51 -29
  103. package/.pi/extensions/zob-harness/src/runtime/compaction-policy.ts +2 -2
  104. package/.pi/extensions/zob-harness/src/runtime/delegation-feed.ts +1 -1
  105. package/.pi/extensions/zob-harness/src/runtime/delegation-monitor.ts +1 -1
  106. package/.pi/extensions/zob-harness/src/runtime/events.ts +53 -37
  107. package/.pi/extensions/zob-harness/src/{goal-runtime.ts → runtime/goal-runtime.ts} +9 -9
  108. package/.pi/extensions/zob-harness/src/runtime/goal-todo-overlay.ts +1 -1
  109. package/.pi/extensions/zob-harness/src/runtime/plan-capture.ts +2 -2
  110. package/.pi/extensions/zob-harness/src/runtime/state.ts +15 -13
  111. package/.pi/extensions/zob-harness/src/runtime/tools-autonomous.ts +2 -2
  112. package/.pi/extensions/zob-harness/src/runtime/tools-compute.ts +3 -3
  113. package/.pi/extensions/zob-harness/src/runtime/tools-coms.ts +15 -12
  114. package/.pi/extensions/zob-harness/src/runtime/tools-context.ts +2 -2
  115. package/.pi/extensions/zob-harness/src/runtime/tools-delegation.ts +13 -13
  116. package/.pi/extensions/zob-harness/src/runtime/tools-factory.ts +19 -19
  117. package/.pi/extensions/zob-harness/src/runtime/tools-goal-room.ts +3 -3
  118. package/.pi/extensions/zob-harness/src/runtime/tools-governed-requests.ts +3 -3
  119. package/.pi/extensions/zob-harness/src/runtime/tools-merge-queue.ts +3 -3
  120. package/.pi/extensions/zob-harness/src/runtime/tools-mission-control.ts +3 -3
  121. package/.pi/extensions/zob-harness/src/runtime/tools-orchestration.ts +9 -9
  122. package/.pi/extensions/zob-harness/src/runtime/tools-project-dna.ts +2 -2
  123. package/.pi/extensions/zob-harness/src/runtime/tools-worker-pool.ts +3 -3
  124. package/.pi/extensions/zob-harness/src/runtime/tools-workspace-claims.ts +3 -3
  125. package/.pi/extensions/zob-harness/src/runtime/tools-zcommit.ts +3 -3
  126. package/.pi/extensions/zob-harness/src/runtime/widget.ts +27 -10
  127. package/.pi/extensions/zob-harness/src/runtime/zobHarness.ts +1 -1
  128. package/.pi/extensions/zob-harness/src/types.ts +2 -2
  129. package/.pi/extensions/zob-switch/AGENTS.md +9 -0
  130. package/.pi/extensions/zob-switch/index.ts +9 -121
  131. package/.pi/extensions/zob-switch/src/AGENTS.md +8 -0
  132. package/.pi/extensions/zob-switch/src/autocomplete.ts +12 -0
  133. package/.pi/extensions/zob-switch/src/paths.ts +8 -0
  134. package/.pi/extensions/zob-switch/src/settings.ts +25 -0
  135. package/.pi/extensions/zob-switch/src/snapshot.ts +34 -0
  136. package/.pi/extensions/zob-switch/src/state.ts +57 -0
  137. package/.pi/factories/agentic-spec-team/README.md +35 -0
  138. package/.pi/factories/agentic-spec-team/batch-manifest.json +11 -0
  139. package/.pi/factories/agentic-spec-team/example-agentic-spec-manifest.json +19 -0
  140. package/.pi/factories/agentic-spec-team/factory.json +98 -0
  141. package/.pi/factories/agentic-spec-team/pilot-manifest.json +11 -0
  142. package/.pi/factories/agentic-spec-team/schemas/final-report.schema.json +15 -0
  143. package/.pi/factories/agentic-spec-team/schemas/manifest.schema.json +14 -0
  144. package/.pi/factories/agentic-spec-team/schemas/question.schema.json +18 -0
  145. package/.pi/factories/agentic-spec-team/schemas/source-register.schema.json +11 -0
  146. package/.pi/factories/agentic-spec-team/schemas/traceability.schema.json +11 -0
  147. package/.pi/factories/agentic-spec-team/smoke-manifest.json +11 -0
  148. package/.pi/skills/zob-agentic-spec-team/SKILL.md +148 -0
  149. package/.pi/skills/zob-coms-safety/SKILL.md +13 -0
  150. package/.pi/skills/zob-coms-v2-live/SKILL.md +11 -0
  151. package/.pi/skills/zob-factory/SKILL.md +21 -0
  152. package/.pi/skills/zob-harness/SKILL.md +14 -0
  153. package/.pi/skills/zob-split-refactor/SKILL.md +1 -1
  154. package/.pi/skills/zob-zagent-creator/SKILL.md +266 -12
  155. package/.pi/zagents/agent-factory-pacman-chief.json +22 -0
  156. package/.pi/zagents/agent-factory-pacman-engine-builder.json +21 -0
  157. package/.pi/zagents/agent-factory-pacman-frontend-builder.json +21 -0
  158. package/.pi/zagents/agent-factory-pacman-game-architect.json +21 -0
  159. package/.pi/zagents/agent-factory-pacman-game-designer.json +21 -0
  160. package/.pi/zagents/agent-factory-pacman-qa-oracle.json +21 -0
  161. package/.pi/zagents/bdd-writer.json +20 -0
  162. package/.pi/zagents/data-profile-analyst.json +20 -0
  163. package/.pi/zagents/domain-modeler.json +20 -0
  164. package/.pi/zagents/planner-handoff-writer.json +20 -0
  165. package/.pi/zagents/prompts/agent-factory-pacman-chief.md +53 -0
  166. package/.pi/zagents/prompts/agent-factory-pacman-engine-builder.md +41 -0
  167. package/.pi/zagents/prompts/agent-factory-pacman-frontend-builder.md +40 -0
  168. package/.pi/zagents/prompts/agent-factory-pacman-game-architect.md +41 -0
  169. package/.pi/zagents/prompts/agent-factory-pacman-game-designer.md +43 -0
  170. package/.pi/zagents/prompts/agent-factory-pacman-qa-oracle.md +51 -0
  171. package/.pi/zagents/prompts/agentic-spec-run-role.md +30 -0
  172. package/.pi/zagents/source-intake-steward.json +20 -0
  173. package/.pi/zagents/spec-chief.json +21 -0
  174. package/.pi/zagents/spec-oracle.json +20 -0
  175. package/.pi/zagents/spec-writer.json +20 -0
  176. package/.pi/zagents/ux-flow-analyst.json +20 -0
  177. package/.pi/zteams/agent-factory-pacman-multiplayer-runtime.mjs +384 -0
  178. package/.pi/zteams/agent-factory-pacman-multiplayer.json +42 -0
  179. package/.pi/zteams/agent-factory-pacman-multiplayer.tmux.sh +256 -0
  180. package/.pi/zteams/agentic-spec-run.json +42 -0
  181. package/.pi/zteams/agentic-spec-run.tmux.sh +134 -0
  182. package/.pi/zteams/templates/agent-factory-pacman-chief-kickoff.template.md +71 -0
  183. package/.pi/zteams/templates/agent-factory-pacman-worker-kickoff.template.md +59 -0
  184. package/README.md +183 -110
  185. package/SOURCE_INDEX.md +5 -1
  186. package/examples/agent-factory-mission-control/AGENTS.md +10 -0
  187. package/examples/agent-factory-mission-control/README.md +17 -0
  188. package/examples/agent-factory-mission-control/apps/api/AGENTS.md +3 -0
  189. package/examples/agent-factory-mission-control/apps/dashboard/AGENTS.md +3 -0
  190. package/examples/agent-factory-mission-control/mission.md +3 -0
  191. package/examples/agent-factory-mission-control/output-contract.md +3 -0
  192. package/examples/agent-factory-mission-control/packages/domain/AGENTS.md +3 -0
  193. package/examples/agent-factory-mission-control/packages/snapshot-reader/AGENTS.md +3 -0
  194. package/examples/agent-factory-pacman-multiplayer/AGENTS.md +27 -0
  195. package/examples/agent-factory-pacman-multiplayer/README.md +84 -0
  196. package/examples/agent-factory-pacman-multiplayer/mission.md +43 -0
  197. package/examples/agent-factory-pacman-multiplayer/output-contract.md +58 -0
  198. package/examples/agent-factory-tmux-comms/README.md +146 -0
  199. package/examples/agent-factory-tmux-comms/chief-kickoff.template.md +54 -0
  200. package/examples/agent-factory-tmux-comms/simple-agent-factory.team.json +92 -0
  201. package/examples/agent-factory-tmux-comms/simple-agent-factory.tmux.sh +248 -0
  202. package/examples/agent-factory-tmux-comms/worker-kickoff.template.md +43 -0
  203. package/package.json +17 -3
  204. package/scripts/README.md +1 -1
  205. package/scripts/agentic-spec-team/validate-bdd.mjs +13 -0
  206. package/scripts/agentic-spec-team/validate-final-report.mjs +14 -0
  207. package/scripts/agentic-spec-team/validate-manifest.mjs +14 -0
  208. package/scripts/agentic-spec-team/validate-oracle-ready.mjs +13 -0
  209. package/scripts/agentic-spec-team/validate-question-loop.mjs +15 -0
  210. package/scripts/agentic-spec-team/validate-run.mjs +11 -0
  211. package/scripts/agentic-spec-team/validate-source-register.mjs +17 -0
  212. package/scripts/agentic-spec-team/validate-traceability.mjs +20 -0
  213. package/scripts/agentic-spec-team/validate-workgraph.mjs +13 -0
  214. package/scripts/autonomy/mission-readiness-secret-smoke.mjs +5 -5
  215. package/scripts/git-ops/commit-policy-smoke.mjs +5 -4
  216. package/scripts/goal-todo/child-goal-ref-smoke.mjs +2 -2
  217. package/scripts/path-policy/validate-smoke.mjs +3 -3
  218. package/scripts/project-dna/AGENTS.md +39 -0
  219. package/scripts/project-dna/{validate-scaffold.mjs → validation/validate-scaffold.mjs} +7 -7
  220. package/scripts/spec-run.mjs +365 -0
  221. package/scripts/worker-pool/static-smoke.mjs +5 -5
  222. package/scripts/zagent-static-smoke.mjs +35 -6
  223. package/scripts/zpeer-local-e2e-smoke.mjs +12 -5
  224. package/scripts/zpeer-static-smoke.mjs +17 -17
  225. /package/.pi/extensions/zob-harness/src/{types → core/types}/core.ts +0 -0
  226. /package/.pi/extensions/zob-harness/src/{utils → core/utils}/hashing.ts +0 -0
  227. /package/.pi/extensions/zob-harness/src/{utils → core/utils}/json.ts +0 -0
  228. /package/.pi/extensions/zob-harness/src/{utils → core/utils}/paths.ts +0 -0
  229. /package/.pi/extensions/zob-harness/src/{coms-v2 → domains/coms/coms-v2}/AGENTS.md +0 -0
  230. /package/.pi/extensions/zob-harness/src/{coms-v2 → domains/coms/coms-v2}/pending-replies.ts +0 -0
  231. /package/.pi/extensions/zob-harness/src/{coms-v2 → domains/coms/coms-v2}/types.ts +0 -0
  232. /package/.pi/extensions/zob-harness/src/{promotion → domains/promotion}/ledger.ts +0 -0
  233. /package/.pi/extensions/zob-harness/src/{promotion → domains/promotion}/types.ts +0 -0
  234. /package/.pi/extensions/zob-harness/src/{promotion → domains/promotion}/validate.ts +0 -0
  235. /package/.pi/extensions/zob-harness/src/{schemas-project-dna.ts → runtime/schemas-project-dna.ts} +0 -0
  236. /package/.pi/extensions/zob-harness/src/{schemas.ts → runtime/schemas.ts} +0 -0
  237. /package/scripts/project-dna/{bench-smoke.mjs → benchmark/bench-smoke.mjs} +0 -0
  238. /package/scripts/project-dna/{build-capsules.mjs → capsules/build-capsules.mjs} +0 -0
  239. /package/scripts/project-dna/{emit-golden-cases.mjs → emit/emit-golden-cases.mjs} +0 -0
  240. /package/scripts/project-dna/{emit-ontology.mjs → emit/emit-ontology.mjs} +0 -0
  241. /package/scripts/project-dna/{oracle-review-smoke.mjs → oracle/oracle-review-smoke.mjs} +0 -0
  242. /package/scripts/project-dna/{query-context.mjs → query/query-context.mjs} +0 -0
  243. /package/scripts/project-dna/{query-steward.mjs → query/query-steward.mjs} +0 -0
  244. /package/scripts/project-dna/{build-sample-spec.mjs → sample/build-sample-spec.mjs} +0 -0
  245. /package/scripts/project-dna/{generate-sample.mjs → sample/generate-sample.mjs} +0 -0
  246. /package/scripts/project-dna/{validate-sample-project.mjs → sample/validate-sample-project.mjs} +0 -0
  247. /package/scripts/project-dna/{scan.mjs → scan/scan.mjs} +0 -0
  248. /package/scripts/project-dna/{validate-scan-artifacts.mjs → scan/validate-scan-artifacts.mjs} +0 -0
  249. /package/scripts/project-dna/{validate-5of5.mjs → validation/validate-5of5.mjs} +0 -0
  250. /package/scripts/project-dna/{validate-golden-cases.mjs → validation/validate-golden-cases.mjs} +0 -0
  251. /package/scripts/project-dna/{validate-ontology.mjs → validation/validate-ontology.mjs} +0 -0
  252. /package/scripts/project-dna/{plan-workflow.mjs → workflow/plan-workflow.mjs} +0 -0
  253. /package/scripts/project-dna/{validate-workflow.mjs → workflow/validate-workflow.mjs} +0 -0
@@ -0,0 +1,365 @@
1
+ #!/usr/bin/env node
2
+ import fs from "node:fs/promises";
3
+ import { existsSync, statSync } from "node:fs";
4
+ import path from "node:path";
5
+ import { spawnSync } from "node:child_process";
6
+
7
+ const RUNS_ROOT = "reports/agentic-spec-runs";
8
+ const TEAM_SCRIPT = ".pi/zteams/agentic-spec-run.tmux.sh";
9
+ const DEFAULT_AGENT = "spec-chief";
10
+ const args = process.argv.slice(2);
11
+ const command = args[0] && !args[0].startsWith("--") ? args[0] : "help";
12
+
13
+ try {
14
+ const result = await dispatch(command, command === "help" ? args : args.slice(1));
15
+ if (result) print(result);
16
+ if (result?.no_ship) process.exitCode = 1;
17
+ } catch (error) {
18
+ print({ schema: "agentic-spec-run.error.v1", status: "error", no_ship: true, message: error?.message || String(error) });
19
+ process.exitCode = 1;
20
+ }
21
+
22
+ async function dispatch(cmd, values) {
23
+ if (cmd === "help" || values.includes("--help") || args.includes("--help")) return help();
24
+ if (cmd === "init") return initRun(parseOptions(values));
25
+ if (cmd === "auto-pilot") return autoPilot(parseOptions(values));
26
+ if (cmd === "send-kickoff") return sendKickoff(values[0], parseOptions(values.slice(1)));
27
+ if (cmd === "team-start") return teamStart(values[0]);
28
+ if (cmd === "team-status") return teamStatus(parseRunOrSession(values));
29
+ if (cmd === "team-attach") return teamAttach(values[0], values[1] || DEFAULT_AGENT);
30
+ if (cmd === "team-close") return teamClose(parseRunOrSession(values));
31
+ if (cmd === "wait") return waitForCompletion(values[0], parseOptions(values.slice(1)));
32
+ if (cmd === "validate") return validateRun(values[0]);
33
+ if (cmd === "questions") return listQuestions(values[0], parseOptions(values.slice(1)));
34
+ if (cmd === "answer") return answerQuestion(values[0], values[1], parseOptions(values.slice(2)));
35
+ if (cmd === "resume") return resumeRun(values[0]);
36
+ if (cmd === "status") return readRunStatus(values[0]);
37
+ return { ...help(), status: "blocked", no_ship: true, message: `unknown command: ${cmd}` };
38
+ }
39
+
40
+ function help() {
41
+ return {
42
+ schema: "agentic-spec-run.help.v1",
43
+ usage: [
44
+ "node scripts/spec-run.mjs init --mission <text>|--mission-file <path> --source <path> [--source <path>] [--name name] [--run-id id] [--owner owner] [--prepare-only]",
45
+ "node scripts/spec-run.mjs auto-pilot --mission <text>|--mission-file <path> --source <path> [--timeout-ms 14400000] [--poll-ms 30000] [--no-submit] [--no-wait] [--no-close]",
46
+ "node scripts/spec-run.mjs send-kickoff reports/agentic-spec-runs/<run_id> --session agentic-spec-run-<run_id> --agent spec-chief --submit",
47
+ "node scripts/spec-run.mjs answer <run_id> Q-001 --text '<answer>' --answered-by owner",
48
+ "node scripts/spec-run.mjs validate <run_id>",
49
+ ],
50
+ output: "json",
51
+ v1_posture: "skill + factory + ZTeam + CLI; no runtime extension",
52
+ no_ship: false,
53
+ };
54
+ }
55
+
56
+ async function initRun(opts) {
57
+ const sources = opts.source ?? [];
58
+ const mission = await resolveMission(opts);
59
+ if (!mission) return blocked("init", "missing mission; use --mission or --mission-file");
60
+ if (!sources.length) return blocked("init", "missing at least one --source path");
61
+
62
+ const runId = safeRunId(opts.runId || `${opts.name || "spec"}-${timestamp()}`);
63
+ const runDir = path.join(RUNS_ROOT, runId);
64
+ const createdAt = new Date().toISOString();
65
+ await mkdirs(runDir, ["analysis", "spec", "validation", "handoff", "oracle", "checkpoints"]);
66
+
67
+ const sourceRows = sources.map((source, index) => inspectSource(source, index));
68
+ const manifest = {
69
+ schema: "agentic-spec-run.manifest.v1",
70
+ run_id: runId,
71
+ name: opts.name || runId,
72
+ owner: opts.owner || "owner",
73
+ created_at: createdAt,
74
+ mission,
75
+ mission_file: opts.missionFile || null,
76
+ source_count: sourceRows.length,
77
+ source_refs: sourceRows.map((row) => row.source_id),
78
+ run_dir: runDir,
79
+ team_id: "agentic-spec-run",
80
+ entry_agent: DEFAULT_AGENT,
81
+ expected_session: sessionName(runId),
82
+ policies: {
83
+ source_policy: "citation_only_by_default",
84
+ raw_secret_storage: false,
85
+ human_questions_block_completion: true,
86
+ final_oracle_required: true,
87
+ traceability_required: true,
88
+ runtime_extension_v1: false,
89
+ },
90
+ };
91
+ const teamPlan = {
92
+ schema: "agentic-spec.team-plan.v1",
93
+ run_id: runId,
94
+ session: sessionName(runId),
95
+ entry_agent: DEFAULT_AGENT,
96
+ agents: ["spec-chief", "source-intake-steward", "data-profile-analyst", "domain-modeler", "ux-flow-analyst", "spec-writer", "bdd-writer", "planner-handoff-writer", "spec-oracle"],
97
+ rooms: ["spec-control", "intake", "data", "domain", "ux", "writing", "validation", "handoff", "oracle"],
98
+ };
99
+ const status = {
100
+ schema: "agentic-spec-run.status.v1",
101
+ run_id: runId,
102
+ status: "ready",
103
+ phase: "init",
104
+ no_ship: false,
105
+ run_dir: runDir,
106
+ open_blocking_questions: 0,
107
+ final_oracle: "pending",
108
+ updated_at: createdAt,
109
+ };
110
+
111
+ await writeJson(path.join(runDir, "run-manifest.json"), manifest);
112
+ await writeJson(path.join(runDir, "source-register.json"), { schema: "agentic-spec.source-register.v1", run_id: runId, sources: sourceRows });
113
+ await writeJson(path.join(runDir, "team-plan.json"), teamPlan);
114
+ await writeJson(path.join(runDir, "status.json"), status);
115
+ await fs.writeFile(path.join(runDir, "source-register.md"), renderSourceRegister(sourceRows), "utf8");
116
+ await fs.writeFile(path.join(runDir, "chief-kickoff.md"), renderKickoff(manifest, sourceRows), "utf8");
117
+ await fs.writeFile(path.join(runDir, "questions.md"), "# Open Questions\n\nNo questions recorded yet.\n", "utf8");
118
+ await fs.writeFile(path.join(runDir, "answers.md"), "# Answers\n\nNo answers recorded yet.\n", "utf8");
119
+ await fs.writeFile(path.join(runDir, "questions.jsonl"), "", "utf8");
120
+ await fs.writeFile(path.join(runDir, "answers.jsonl"), "", "utf8");
121
+ await fs.writeFile(path.join(runDir, "assumptions.md"), "# Assumptions\n\n", "utf8");
122
+ await fs.writeFile(path.join(runDir, "validation/traceability-matrix.md"), "# Traceability Matrix\n\n| Source | Fact / answer / assumption | Requirement | Acceptance criteria | Task | Oracle check |\n| --- | --- | --- | --- | --- | --- |\n", "utf8");
123
+ await writeJson(path.join(runDir, "validation/traceability-matrix.json"), { schema: "agentic-spec.traceability.v1", run_id: runId, rows: [] });
124
+
125
+ return { schema: "agentic-spec-run.init-result.v1", status: "ready", no_ship: false, run_id: runId, run_dir: runDir, prepare_only: opts.prepareOnly !== false, full_team_launched: false, expected_session: sessionName(runId) };
126
+ }
127
+
128
+ async function autoPilot(opts) {
129
+ const timeoutMs = Number(opts.timeoutMs || 4 * 60 * 60 * 1000);
130
+ const pollMs = Number(opts.pollMs || 30 * 1000);
131
+ const submit = opts.submit !== false;
132
+ const wait = opts.wait !== false;
133
+ const close = opts.close !== false;
134
+ const init = await initRun({ ...opts, prepareOnly: true });
135
+ if (init.no_ship) return init;
136
+ const sessionGuard = validateSessionOverride(opts.session, sessionName(init.run_id));
137
+ if (sessionGuard.no_ship) return { schema: "agentic-spec-run.auto-pilot-result.v1", status: "blocked", no_ship: true, phase: "session-guard", init, session_guard: sessionGuard };
138
+ const team_start = teamStart(init.run_id);
139
+ if (team_start.no_ship) return { schema: "agentic-spec-run.auto-pilot-result.v1", status: "blocked", no_ship: true, phase: "team-start", init, team_start };
140
+ const kickoff = await sendKickoff(init.run_dir, { session: sessionGuard.session, agent: opts.agent || DEFAULT_AGENT, submit });
141
+ if (kickoff.no_ship) return { schema: "agentic-spec-run.auto-pilot-result.v1", status: "blocked", no_ship: true, phase: "send-kickoff", init, team_start, kickoff };
142
+ if (!wait) return { schema: "agentic-spec-run.auto-pilot-result.v1", status: "launched", no_ship: false, run_id: init.run_id, run_dir: init.run_dir, run_session: sessionGuard.session, waited: false, closed: false, next_command: `npm run spec-run -- team-status --session ${sessionGuard.session}` };
143
+ const completion = await waitForCompletion(init.run_id, { timeoutMs, pollMs });
144
+ if (completion.status !== "pass") return { schema: "agentic-spec-run.auto-pilot-result.v1", status: completion.status, no_ship: true, run_id: init.run_id, run_dir: init.run_dir, run_session: sessionGuard.session, completion, closed: false, close_skipped_reason: "completion_not_pass_no_ship_false" };
145
+ let closeResult = { status: "skipped", closed: false, reason: "--no-close" };
146
+ if (close) closeResult = teamClose({ runId: init.run_id, confirm: true, session: sessionGuard.session });
147
+ return { schema: "agentic-spec-run.auto-pilot-result.v1", status: "pass", no_ship: false, run_id: init.run_id, run_dir: init.run_dir, run_session: sessionGuard.session, completion, close: closeResult, closed: closeResult.closed === true };
148
+ }
149
+
150
+ async function sendKickoff(runDirOrId, opts) {
151
+ const runDir = resolveRunDir(runDirOrId);
152
+ const manifest = await readJson(path.join(runDir, "run-manifest.json"));
153
+ const kickoffPath = path.join(runDir, "chief-kickoff.md");
154
+ const session = opts.session || sessionName(manifest.run_id);
155
+ const guard = validateSessionOverride(session, sessionName(manifest.run_id));
156
+ if (guard.no_ship) return { schema: "agentic-spec-run.kickoff-result.v1", status: "blocked", no_ship: true, session_guard: guard };
157
+ const agent = opts.agent || DEFAULT_AGENT;
158
+ if (!opts.submit) return { schema: "agentic-spec-run.kickoff-result.v1", status: "pass", no_ship: false, submitted: false, run_id: manifest.run_id, run_dir: runDir, kickoff_ref: kickoffPath, session, agent };
159
+ const target = `${session}:${agent}`;
160
+ const body = await fs.readFile(kickoffPath, "utf8");
161
+ const payload = `Please execute this Agentic Spec kickoff. Read ${kickoffPath} and coordinate the run.\n\n${body}`;
162
+ const sent = spawnSync("tmux", ["send-keys", "-t", target, payload, "C-m"], { encoding: "utf8" });
163
+ if (sent.status !== 0) return { schema: "agentic-spec-run.kickoff-result.v1", status: "blocked", no_ship: true, submitted: false, reason: "tmux_send_failed", stderr: sent.stderr?.trim(), target };
164
+ return { schema: "agentic-spec-run.kickoff-result.v1", status: "pass", no_ship: false, submitted: true, run_id: manifest.run_id, run_dir: runDir, kickoff_ref: kickoffPath, session, agent };
165
+ }
166
+
167
+ function teamStart(runId) {
168
+ if (!runId) return blocked("team-start", "missing run_id");
169
+ return runTeamScript(["start-detached", runId], "agentic-spec-run.team-start-result.v1");
170
+ }
171
+
172
+ function teamStatus(spec) {
173
+ const session = spec.session || (spec.runId ? sessionName(spec.runId) : null);
174
+ if (!session) return blocked("team-status", "missing --session or run_id");
175
+ const runId = spec.runId || session.replace(/^agentic-spec-run-/u, "");
176
+ return runTeamScript(["status", runId], "agentic-spec-run.team-status-result.v1", { session });
177
+ }
178
+
179
+ function teamAttach(runId, agent) {
180
+ if (!runId) return blocked("team-attach", "missing run_id");
181
+ return runTeamScript(["attach", runId, agent || DEFAULT_AGENT], "agentic-spec-run.team-attach-result.v1");
182
+ }
183
+
184
+ function teamClose(spec) {
185
+ const session = spec.session || (spec.runId ? sessionName(spec.runId) : null);
186
+ const runId = spec.runId || session?.replace(/^agentic-spec-run-/u, "");
187
+ if (!runId || !session) return blocked("team-close", "missing --session or run_id");
188
+ const guard = validateSessionOverride(session, sessionName(runId));
189
+ if (guard.no_ship) return { schema: "agentic-spec-run.team-close-result.v1", status: "blocked", no_ship: true, closed: false, session_guard: guard };
190
+ if (!spec.confirm) return { schema: "agentic-spec-run.team-close-result.v1", status: "blocked", no_ship: true, closed: false, reason: "team-close requires --confirm" };
191
+ return runTeamScript(["close", runId], "agentic-spec-run.team-close-result.v1", { session, closed: true });
192
+ }
193
+
194
+ async function waitForCompletion(runId, opts) {
195
+ const runDir = resolveRunDir(runId);
196
+ const timeoutMs = Number(opts.timeoutMs || 4 * 60 * 60 * 1000);
197
+ const pollMs = Number(opts.pollMs || 30 * 1000);
198
+ const deadline = Date.now() + timeoutMs;
199
+ const checked = [];
200
+ while (Date.now() <= deadline) {
201
+ for (const file of [path.join(runDir, "final-report.json"), path.join(runDir, "status.json")]) {
202
+ const parsed = await readJsonIfExists(file);
203
+ if (!parsed) continue;
204
+ checked.push(file);
205
+ if (isPassingCompletion(parsed)) return { schema: "agentic-spec-run.wait-result.v1", status: "pass", no_ship: false, completion_ref: file, report: parsed };
206
+ if (isBlockingCompletion(parsed)) return { schema: "agentic-spec-run.wait-result.v1", status: "blocked", no_ship: true, completion_ref: file, report: parsed };
207
+ }
208
+ await sleep(pollMs);
209
+ }
210
+ return { schema: "agentic-spec-run.wait-result.v1", status: "timeout", no_ship: true, timeout_ms: timeoutMs, checked_refs: [...new Set(checked)] };
211
+ }
212
+
213
+ async function validateRun(runIdOrDir) {
214
+ const runDir = resolveRunDir(runIdOrDir);
215
+ const required = ["run-manifest.json", "source-register.json", "source-register.md", "team-plan.json", "chief-kickoff.md", "status.json", "questions.md", "answers.md", "validation/traceability-matrix.json", "validation/traceability-matrix.md"];
216
+ const missing = required.filter((file) => !existsSync(path.join(runDir, file)));
217
+ const errors = [...missing.map((file) => `missing ${file}`)];
218
+ const manifest = await readJsonIfExists(path.join(runDir, "run-manifest.json"));
219
+ const sources = await readJsonIfExists(path.join(runDir, "source-register.json"));
220
+ const traceability = await readJsonIfExists(path.join(runDir, "validation/traceability-matrix.json"));
221
+ if (manifest?.schema !== "agentic-spec-run.manifest.v1") errors.push("manifest schema mismatch");
222
+ if (!Array.isArray(sources?.sources) || sources.sources.length < 1) errors.push("source-register must include at least one source");
223
+ if (!Array.isArray(traceability?.rows)) errors.push("traceability rows must be an array");
224
+ const questions = await readJsonlIfExists(path.join(runDir, "questions.jsonl"));
225
+ const openBlocking = questions.filter((q) => q?.blocking === true && q?.status !== "answered" && q?.status !== "closed");
226
+ if (openBlocking.length > 0) errors.push(`open blocking questions: ${openBlocking.map((q) => q.question_id).join(", ")}`);
227
+ return { schema: "agentic-spec-run.validation-result.v1", status: errors.length ? "fail" : "pass", no_ship: errors.length > 0, run_dir: runDir, errors, checked_files: required };
228
+ }
229
+
230
+ async function listQuestions(runId, opts) {
231
+ const runDir = resolveRunDir(runId);
232
+ const questions = await readJsonlIfExists(path.join(runDir, "questions.jsonl"));
233
+ const rows = opts.openOnly ? questions.filter((q) => q.status === "open") : questions;
234
+ return { schema: "agentic-spec-run.questions-result.v1", status: "pass", no_ship: false, run_id: path.basename(runDir), questions: rows };
235
+ }
236
+
237
+ async function answerQuestion(runId, questionId, opts) {
238
+ if (!runId || !questionId) return blocked("answer", "usage: answer <run_id> <question_id> --text '<answer>'");
239
+ if (!opts.text) return blocked("answer", "missing --text answer");
240
+ const runDir = resolveRunDir(runId);
241
+ const answer = {
242
+ schema: "agentic-spec.answer.v1",
243
+ question_id: questionId,
244
+ answered_by: opts.answeredBy || "owner",
245
+ answer_summary: opts.text,
246
+ selected_option: opts.option || null,
247
+ raw_answer_stored: false,
248
+ answered_at: new Date().toISOString(),
249
+ };
250
+ await fs.appendFile(path.join(runDir, "answers.jsonl"), `${JSON.stringify(answer)}\n`, "utf8");
251
+ await fs.appendFile(path.join(runDir, "answers.md"), `\n## ${questionId}\n\nAnswered by: ${answer.answered_by}\n\n${answer.answer_summary}\n`, "utf8");
252
+ const statusPath = path.join(runDir, "status.json");
253
+ const status = await readJsonIfExists(statusPath) || {};
254
+ status.status = "answer_recorded";
255
+ status.phase = "human-clarification";
256
+ status.no_ship = true;
257
+ status.updated_at = new Date().toISOString();
258
+ status.next_command = `npm run spec-run -- resume ${path.basename(runDir)}`;
259
+ await writeJson(statusPath, status);
260
+ return { schema: "agentic-spec-run.answer-result.v1", status: "pass", no_ship: false, run_id: path.basename(runDir), answer_ref: path.join(runDir, "answers.md"), next_command: status.next_command };
261
+ }
262
+
263
+ async function resumeRun(runId) {
264
+ const runDir = resolveRunDir(runId);
265
+ const statusPath = path.join(runDir, "status.json");
266
+ const status = await readJsonIfExists(statusPath) || {};
267
+ status.status = "ready";
268
+ status.phase = "resume_after_human_answer";
269
+ status.no_ship = false;
270
+ status.updated_at = new Date().toISOString();
271
+ await writeJson(statusPath, status);
272
+ return { schema: "agentic-spec-run.resume-result.v1", status: "pass", no_ship: false, run_id: path.basename(runDir), status_ref: statusPath, next_step: `send kickoff or attach spec-chief in ${sessionName(path.basename(runDir))}` };
273
+ }
274
+
275
+ async function readRunStatus(runId) {
276
+ const runDir = resolveRunDir(runId);
277
+ return await readJson(path.join(runDir, "status.json"));
278
+ }
279
+
280
+ function runTeamScript(teamArgs, schema, extra = {}) {
281
+ if (!existsSync(TEAM_SCRIPT)) return { schema, status: "blocked", no_ship: true, reason: `missing ${TEAM_SCRIPT}` };
282
+ const result = spawnSync("bash", [TEAM_SCRIPT, ...teamArgs], { encoding: "utf8", cwd: process.cwd(), env: process.env });
283
+ if (result.status !== 0) return { schema, status: "blocked", no_ship: true, stdout: result.stdout?.trim(), stderr: result.stderr?.trim(), ...extra };
284
+ return { schema, status: "pass", no_ship: false, stdout: result.stdout?.trim(), ...extra };
285
+ }
286
+
287
+ function parseOptions(values) {
288
+ const opts = { source: [], submit: true, wait: true, close: true, prepareOnly: true };
289
+ for (let i = 0; i < values.length; i++) {
290
+ const arg = values[i];
291
+ if (arg === "--source") opts.source.push(values[++i]);
292
+ else if (arg === "--mission") opts.mission = values[++i];
293
+ else if (arg === "--mission-file") opts.missionFile = values[++i];
294
+ else if (arg === "--name") opts.name = values[++i];
295
+ else if (arg === "--run-id") opts.runId = values[++i];
296
+ else if (arg === "--owner") opts.owner = values[++i];
297
+ else if (arg === "--session") opts.session = values[++i];
298
+ else if (arg === "--agent") opts.agent = values[++i];
299
+ else if (arg === "--timeout-ms") opts.timeoutMs = values[++i];
300
+ else if (arg === "--poll-ms") opts.pollMs = values[++i];
301
+ else if (arg === "--answered-by") opts.answeredBy = values[++i];
302
+ else if (arg === "--text") opts.text = values[++i];
303
+ else if (arg === "--option") opts.option = values[++i];
304
+ else if (arg === "--prepare-only") opts.prepareOnly = true;
305
+ else if (arg === "--no-submit") opts.submit = false;
306
+ else if (arg === "--no-wait") opts.wait = false;
307
+ else if (arg === "--no-close") opts.close = false;
308
+ else if (arg === "--open-only") opts.openOnly = true;
309
+ else if (arg === "--confirm") opts.confirm = true;
310
+ }
311
+ return opts;
312
+ }
313
+
314
+ function parseRunOrSession(values) {
315
+ const opts = parseOptions(values);
316
+ const runId = values.find((value) => !value.startsWith("--") && ![opts.session].includes(value));
317
+ return { runId, session: opts.session, confirm: opts.confirm };
318
+ }
319
+
320
+ async function resolveMission(opts) {
321
+ if (opts.missionFile) return fs.readFile(opts.missionFile, "utf8");
322
+ return opts.mission || "";
323
+ }
324
+
325
+ function inspectSource(source, index) {
326
+ const exists = existsSync(source);
327
+ const stat = exists ? statSync(source) : null;
328
+ return {
329
+ source_id: `SRC-${String(index + 1).padStart(3, "0")}`,
330
+ path: source,
331
+ exists,
332
+ kind: stat?.isDirectory() ? "directory" : stat?.isFile() ? "file" : "missing",
333
+ size_bytes: stat?.isFile() ? stat.size : null,
334
+ policy: "citation_only",
335
+ sensitivity: "unknown_until_intake",
336
+ };
337
+ }
338
+
339
+ function renderSourceRegister(rows) {
340
+ return `# Source Register\n\n| Source | Path | Kind | Exists | Policy | Sensitivity |\n| --- | --- | --- | --- | --- | --- |\n${rows.map((row) => `| ${row.source_id} | ${row.path} | ${row.kind} | ${row.exists} | ${row.policy} | ${row.sensitivity} |`).join("\n")}\n`;
341
+ }
342
+
343
+ function renderKickoff(manifest, sources) {
344
+ return `# Agentic Spec Run Kickoff\n\nRun: ${manifest.run_id}\nSession: ${manifest.expected_session}\nEntry agent: ${manifest.entry_agent}\n\n## Mission\n\n${manifest.mission}\n\n## Sources\n\n${sources.map((row) => `- ${row.source_id}: ${row.path} (${row.kind}, policy=${row.policy})`).join("\n")}\n\n## Required outputs\n\n- source register and source quality report\n- data profile / domain model / UX flows as applicable\n- mission spec, requirements, BDD acceptance criteria\n- validation/traceability-matrix.md and .json\n- handoff/workgraph.md and implementation tasks\n- oracle/final-oracle-review.json and final-report.json\n\n## Rules\n\nSpec Chief is the only human interlocutor. Do not invent answers to blocking business questions. Completion requires oracle PASS and no_ship=false.\n`;
345
+ }
346
+
347
+ async function mkdirs(root, children) {
348
+ await fs.mkdir(root, { recursive: true });
349
+ for (const child of children) await fs.mkdir(path.join(root, child), { recursive: true });
350
+ }
351
+
352
+ function sessionName(runId) { return `agentic-spec-run-${safeRunId(runId)}`; }
353
+ function safeRunId(value) { return String(value || "").trim().replace(/[^A-Za-z0-9_-]+/gu, "-").replace(/^-+|-+$/gu, "").slice(0, 120) || "spec-run"; }
354
+ function timestamp() { return new Date().toISOString().replace(/[-:TZ.]/gu, "").slice(0, 14); }
355
+ function resolveRunDir(runIdOrDir) { return String(runIdOrDir || "").startsWith(RUNS_ROOT) ? runIdOrDir : path.join(RUNS_ROOT, safeRunId(runIdOrDir)); }
356
+ function validateSessionOverride(requestedSession, expectedSession) { const session = requestedSession || expectedSession; return session === expectedSession ? { status: "pass", no_ship: false, session, expected_session: expectedSession } : { status: "blocked", no_ship: true, session, expected_session: expectedSession, reason: "session_must_equal_isolated_agentic_spec_run_session" }; }
357
+ function isPassingCompletion(value) { return value?.no_ship === false && ["pass", "complete", "completed"].includes(String(value?.status || value?.verdict || "").toLowerCase()); }
358
+ function isBlockingCompletion(value) { return value?.no_ship === true || ["blocked", "fail", "failed", "needs_human"].includes(String(value?.status || value?.verdict || "").toLowerCase()); }
359
+ function blocked(phase, reason) { return { schema: "agentic-spec-run.blocked.v1", status: "blocked", no_ship: true, phase, reason }; }
360
+ async function readJson(file) { return JSON.parse(await fs.readFile(file, "utf8")); }
361
+ async function readJsonIfExists(file) { try { return await readJson(file); } catch { return null; } }
362
+ async function readJsonlIfExists(file) { try { const text = await fs.readFile(file, "utf8"); return text.split(/\n/u).filter(Boolean).map((line) => JSON.parse(line)); } catch { return []; } }
363
+ async function writeJson(file, value) { await fs.writeFile(file, `${JSON.stringify(value, null, 2)}\n`, "utf8"); }
364
+ function sleep(ms) { return new Promise((resolve) => setTimeout(resolve, ms)); }
365
+ function print(value) { console.log(JSON.stringify(value, null, 2)); }
@@ -2,12 +2,12 @@
2
2
  import { readFileSync } from "node:fs";
3
3
 
4
4
  const files = {
5
- workerPool: ".pi/extensions/zob-harness/src/worker-pool.ts",
5
+ workerPool: ".pi/extensions/zob-harness/src/domains/governance/worker-pool.ts",
6
6
  runtime: ".pi/extensions/zob-harness/src/runtime/tools-worker-pool.ts",
7
- goalRoom: ".pi/extensions/zob-harness/src/goal-room.ts",
8
- constants: ".pi/extensions/zob-harness/src/constants.ts",
9
- schemas: ".pi/extensions/zob-harness/src/schemas.ts",
10
- governedRequests: ".pi/extensions/zob-harness/src/governed-requests.ts",
7
+ goalRoom: ".pi/extensions/zob-harness/src/domains/goal/goal-room.ts",
8
+ constants: ".pi/extensions/zob-harness/src/core/constants.ts",
9
+ schemas: ".pi/extensions/zob-harness/src/runtime/schemas.ts",
10
+ governedRequests: ".pi/extensions/zob-harness/src/domains/governance/governed-requests.ts",
11
11
  governedRuntime: ".pi/extensions/zob-harness/src/runtime/tools-governed-requests.ts",
12
12
  registry: ".pi/capabilities/zob-public-runtime-capabilities.json",
13
13
  };
@@ -2,12 +2,12 @@
2
2
  import { existsSync, readFileSync } from 'node:fs';
3
3
 
4
4
  const files = [
5
- '.pi/extensions/zob-harness/src/zagents.ts',
5
+ '.pi/extensions/zob-harness/src/domains/coms/zagents.ts',
6
6
  '.pi/extensions/zob-harness/src/runtime/commands.ts',
7
7
  '.pi/extensions/zob-harness/src/runtime/events.ts',
8
8
  '.pi/extensions/zob-harness/src/runtime/state.ts',
9
9
  '.pi/extensions/zob-harness/src/runtime/widget.ts',
10
- '.pi/extensions/zob-harness/src/coms-v2/zpeer.ts',
10
+ '.pi/extensions/zob-harness/src/domains/coms/coms-v2/zpeer.ts',
11
11
  '.pi/capabilities/zob-public-runtime-capabilities.json',
12
12
  '.pi/skills/zob-zagent-creator/SKILL.md',
13
13
  ];
@@ -20,8 +20,8 @@ function requireIncludes(file, needles, label = file) {
20
20
  }
21
21
  }
22
22
 
23
- const zagents = contents['.pi/extensions/zob-harness/src/zagents.ts'];
24
- requireIncludes('.pi/extensions/zob-harness/src/zagents.ts', [
23
+ const zagents = contents['.pi/extensions/zob-harness/src/domains/coms/zagents.ts'];
24
+ requireIncludes('.pi/extensions/zob-harness/src/domains/coms/zagents.ts', [
25
25
  'const ZAGENTS_DIR = ".pi/zagents"',
26
26
  'const ZTEAMS_DIR = ".pi/zteams"',
27
27
  'const ZAGENT_PROMPTS_DIR = ".pi/zagents/prompts"',
@@ -32,7 +32,11 @@ requireIncludes('.pi/extensions/zob-harness/src/zagents.ts', [
32
32
  'export function listZagentManifests',
33
33
  'export function listZteamManifests',
34
34
  'export function zteamAllowsZpeerContact',
35
+ 'export function resolveZagentTeamMemberships',
36
+ 'export function resolveZagentRuntimeRoomBindings',
35
37
  'localOnly: true',
38
+ 'zagent.model must be a safe Pi --model pattern',
39
+ 'zagent.defaultMode must be one of',
36
40
  'networkEnabled: false',
37
41
  'bodyStored: false',
38
42
  ]);
@@ -54,6 +58,10 @@ requireIncludes('.pi/extensions/zob-harness/src/runtime/commands.ts', [
54
58
  'normalizeZagentRoomBindings',
55
59
  'readZagentPrompt',
56
60
  'ZOB_ZAGENT_ID=${member.id} pi',
61
+ '--model ${model}',
62
+ 'modelIdHashes: plan.modelIds.map((modelId) => sha256(modelId))',
63
+ 'defaultModeHashes: plan.defaultModes.map((mode) => sha256(mode))',
64
+ 'defaultMode=${defaultMode}',
57
65
  'After each session starts, run /zagent use <id> to bind its ZPeer alias/rooms.',
58
66
  'Project-local full-session ZAgents',
59
67
  'Project-local ZTeams',
@@ -68,12 +76,15 @@ requireIncludes('.pi/extensions/zob-harness/src/runtime/events.ts', [
68
76
  'process.env.ZOB_ZAGENT_ID?.trim()',
69
77
  'loadZagentManifest(repoRoot, zagentId)',
70
78
  'readZagentPrompt(repoRoot, manifest.promptRef)',
71
- 'normalizeZagentRoomBindings(manifest.rooms, manifest.defaultRoom, manifest.activeRoom)',
79
+ 'resolveZagentRuntimeRoomBindings(repoRoot, manifest)',
72
80
  'state.zagent = nextZagent',
81
+ 'defaultMode: manifest.defaultMode',
73
82
  'loadActiveZagentFromEnv(state, ctx.cwd)',
83
+ 'state.zagent.defaultMode && state.zagent.defaultMode !== state.activeMode',
74
84
  'ZAGENT RUNTIME ACTIVATION',
75
85
  'ZAgents are full Pi sessions tied to ZPeer/live coordination, not delegate subagents.',
76
86
  'team: zagent?.team ?? basePeer.team',
87
+ 'teams: resolved.teamIds',
77
88
  'roleId: zagent?.id ?? basePeer.roleId',
78
89
  'agent: zagent?.id ?? basePeer.agent',
79
90
  'team: zagent?.team ?? state.zobLive.peerCard.team',
@@ -84,14 +95,16 @@ requireIncludes('.pi/extensions/zob-harness/src/runtime/events.ts', [
84
95
  requireIncludes('.pi/extensions/zob-harness/src/runtime/state.ts', [
85
96
  'export interface ZagentRuntimeState',
86
97
  'team?: string;',
98
+ 'teams?: string[];',
87
99
  'role?: string;',
88
100
  'alias?: string;',
89
101
  'rooms: ZAgentRoomBinding[];',
102
+ 'defaultMode?: ModeName;',
90
103
  'zagent: ZagentRuntimeState;',
91
104
  'zagent: { rooms: [], errors: [] }',
92
105
  ], 'state');
93
106
 
94
- requireIncludes('.pi/extensions/zob-harness/src/coms-v2/zpeer.ts', [
107
+ requireIncludes('.pi/extensions/zob-harness/src/domains/coms/coms-v2/zpeer.ts', [
95
108
  'import { loadZteamManifest, zteamAllowsZpeerContact } from "../zagents.js"',
96
109
  'const zteam = loadZteamManifest(repoRoot, self.team)',
97
110
  'zteam fallback blocked',
@@ -138,8 +151,24 @@ requireIncludes('.pi/skills/zob-zagent-creator/SKILL.md', [
138
151
  '/zteam launch-plan <team-id>',
139
152
  'ZOB_ZAGENT_ID=<id> pi',
140
153
  'do not automatically spawn processes',
154
+ '.pi/model-catalog.json',
155
+ '.pi/model-catalog.example.json',
156
+ '.pi/model-routing.json',
157
+ 'costTier',
158
+ 'qualityTier',
159
+ 'ZOB_ZAGENT_ID=<id> pi --model <model>',
160
+ 'Default ZOB mode selection',
161
+ 'defaultMode',
162
+ 'never choose `vanilla` by default',
141
163
  'Do not add a scaffold slash command',
142
164
  'J\'ai besoin d\'une team de trois agents',
165
+ 'metadata.entryAgent',
166
+ 'start [agent]',
167
+ 'attach [agent]',
168
+ 'window <agent>',
169
+ 'list',
170
+ 'resolve_target_agent',
171
+ 'Unknown agent/window',
143
172
  ], 'zagent skill');
144
173
 
145
174
  const scaffoldCommand = ['/zteam', 'scaffold'].join(' ');
@@ -96,12 +96,13 @@ async function main() {
96
96
  if (existsSync(repoNodeModules)) symlinkSync(repoNodeModules, join(outDir, 'node_modules'), 'dir');
97
97
 
98
98
  const compiledSrc = join(outDir, '.pi/extensions/zob-harness/src');
99
- const zpeer = await import(`${compiledSrc}/coms-v2/zpeer.js`);
100
- const zpeerProfile = await import(`${compiledSrc}/coms-v2/zpeer-profile.js`);
99
+ const compiledComsV2 = `${compiledSrc}/domains/coms/coms-v2`;
100
+ const zpeer = await import(`${compiledComsV2}/zpeer.js`);
101
+ const zpeerProfile = await import(`${compiledComsV2}/zpeer-profile.js`);
101
102
  const toolsComs = await import(`${compiledSrc}/runtime/tools-coms.js`);
102
- const localTransport = await import(`${compiledSrc}/coms-v2/local-transport.js`);
103
- const envelope = await import(`${compiledSrc}/coms-v2/envelope.js`);
104
- const hashing = await import(`${compiledSrc}/utils/hashing.js`);
103
+ const localTransport = await import(`${compiledComsV2}/local-transport.js`);
104
+ const envelope = await import(`${compiledComsV2}/envelope.js`);
105
+ const hashing = await import(`${compiledSrc}/core/utils/hashing.js`);
105
106
 
106
107
  process.env.ZOB_ZPEER_PROFILE_ID = 'profile-alpha';
107
108
  delete process.env.ZPEER_PROFILE;
@@ -330,6 +331,12 @@ async function main() {
330
331
  assert(useShared.ok === true && useShared.peer.zpeerRoomId === 'shared-room' && useShared.peer.zpeerAlias === 'sharedalpha', 'useZpeerRoom must switch active room and alias');
331
332
  alpha = zpeer.useZpeerRoom(repoRoot, useShared.peer, 'room-one').peer;
332
333
 
334
+ zpeer.ensureZpeerFields(repoRoot, makePeer({ alias: 'beta', roomId: 'room-one', endpoint: join(root, 'dead-beta-ghost.sock'), endpointHash: hashing.sha256(join(root, 'dead-beta-ghost.sock')), sha256: hashing.sha256, heartbeatAt: oldHeartbeatAt }), 'room-one', 'beta');
335
+ const ghostDuplicateSummary = zpeer.buildZpeerRoomSummary(repoRoot, alpha);
336
+ assert(ghostDuplicateSummary.aliases.filter((alias) => alias === 'beta').length >= 2, 'ghost duplicate fixture must include stale/offline alias plus live alias');
337
+ assert(ghostDuplicateSummary.onlineAliases.filter((alias) => alias === 'beta').length === 1, 'only the live beta alias should be listed as online');
338
+ assert(!ghostDuplicateSummary.duplicateAliases.includes('beta'), 'offline alias ghosts must not be reported as live duplicate aliases');
339
+
333
340
  const directPromptCountBefore = receivedPrompts.length;
334
341
  const directResponseCountBefore = receivedResponses.length;
335
342
  assert(alpha.team !== beta.team, 'same-room non-worker allowance fixture must cover cross-team peers');
@@ -2,19 +2,19 @@
2
2
  import { readFileSync } from 'node:fs';
3
3
 
4
4
  const files = [
5
- '.pi/extensions/zob-harness/src/coms-v2/zpeer.ts',
6
- '.pi/extensions/zob-harness/src/coms-v2/registry.ts',
7
- '.pi/extensions/zob-harness/src/coms-v2/zpeer-profile.ts',
8
- '.pi/extensions/zob-harness/src/coms-v2/transcript-capture.ts',
5
+ '.pi/extensions/zob-harness/src/domains/coms/coms-v2/zpeer.ts',
6
+ '.pi/extensions/zob-harness/src/domains/coms/coms-v2/registry.ts',
7
+ '.pi/extensions/zob-harness/src/domains/coms/coms-v2/zpeer-profile.ts',
8
+ '.pi/extensions/zob-harness/src/domains/coms/coms-v2/transcript-capture.ts',
9
9
  '.pi/extensions/zob-harness/src/runtime/commands.ts',
10
10
  '.pi/extensions/zob-harness/src/runtime/tools-coms.ts',
11
11
  '.pi/extensions/zob-harness/src/runtime/events.ts',
12
12
  '.pi/extensions/zob-harness/src/runtime/state.ts',
13
- '.pi/extensions/zob-harness/src/goal-runtime.ts',
13
+ '.pi/extensions/zob-harness/src/runtime/goal-runtime.ts',
14
14
  '.pi/extensions/zob-harness/src/runtime/widget.ts',
15
- '.pi/extensions/zob-harness/src/mission-control.ts',
16
- '.pi/extensions/zob-harness/src/schemas.ts',
17
- '.pi/extensions/zob-harness/src/constants.ts',
15
+ '.pi/extensions/zob-harness/src/domains/coms/mission-control.ts',
16
+ '.pi/extensions/zob-harness/src/runtime/schemas.ts',
17
+ '.pi/extensions/zob-harness/src/core/constants.ts',
18
18
  '.pi/capabilities/zob-public-runtime-capabilities.json',
19
19
  ];
20
20
  const contents = Object.fromEntries(files.map((file) => [file, readFileSync(file, 'utf8')]));
@@ -30,13 +30,13 @@ for (const needle of ['parameters: ZpeerAskParams', 'sendZpeerPrompt(ctx.cwd', '
30
30
  }
31
31
  if (toolsComs.includes('emitZpeerAskEvent({ kind: "attempt", status: "agent-request"')) failures.push('zpeer_ask async must not emit pre-ACK attempt feed noise');
32
32
  if (!toolsComs.includes('peerAliasInRoom(self, requestedRoomId)') || !toolsComs.includes('peerAliasInRoom(self, eventRoomId)')) failures.push('zpeer_ask feed events must use room-scoped sender alias for explicit roomId');
33
- const schemas = contents['.pi/extensions/zob-harness/src/schemas.ts'];
33
+ const schemas = contents['.pi/extensions/zob-harness/src/runtime/schemas.ts'];
34
34
  for (const needle of ['const ZpeerAskParams', 'targetAlias', 'message', 'roomId', 'Default async', '["async", "await", "long"]', 'timeoutMs', 'ZpeerAskParams']) {
35
35
  if (!schemas.includes(needle)) failures.push(`zpeer_ask schema missing ${needle}`);
36
36
  }
37
37
  const registry = contents['.pi/capabilities/zob-public-runtime-capabilities.json'];
38
38
  if (!registry.includes('"name": "zpeer_ask"') || !registry.includes('rate/loop guarded')) failures.push('capability registry missing zpeer_ask visibility/safety notes');
39
- const constants = contents['.pi/extensions/zob-harness/src/constants.ts'];
39
+ const constants = contents['.pi/extensions/zob-harness/src/core/constants.ts'];
40
40
  const zpeerComsAllowlistBlock = constants.match(/export const ZOB_COMS_TOOLS = \[[^\n]*\] as const;/)?.[0] ?? '';
41
41
  if (!zpeerComsAllowlistBlock.includes('"zpeer_ask"')) failures.push('ZOB_COMS_TOOLS missing zpeer_ask active allowlist entry');
42
42
  const modeBlocks = Object.fromEntries([...constants.matchAll(/\n (explore|plan|implement|oracle|orchestrator|factory): \[[^\n]*\],/g)].map((match) => [match[1], match[0]]));
@@ -51,9 +51,9 @@ for (const forbidden of ['transientPrompt:', 'transientResponse:', 'prompt:', 'o
51
51
  if (appendBlocks.some((block) => block.includes(forbidden))) failures.push(`zpeer_ask appendEntry contains forbidden raw key/value ${forbidden}`);
52
52
  }
53
53
 
54
- const zpeer = contents['.pi/extensions/zob-harness/src/coms-v2/zpeer.ts'];
55
- const liveRegistry = contents['.pi/extensions/zob-harness/src/coms-v2/registry.ts'];
56
- const zpeerProfile = contents['.pi/extensions/zob-harness/src/coms-v2/zpeer-profile.ts'];
54
+ const zpeer = contents['.pi/extensions/zob-harness/src/domains/coms/coms-v2/zpeer.ts'];
55
+ const liveRegistry = contents['.pi/extensions/zob-harness/src/domains/coms/coms-v2/registry.ts'];
56
+ const zpeerProfile = contents['.pi/extensions/zob-harness/src/domains/coms/coms-v2/zpeer-profile.ts'];
57
57
  for (const needle of ['networkEnabled: false', 'localOnly: true', 'bodyStored: false', 'sendZobLocalEnvelope', 'taskHash', 'outputHash', 'ZpeerSendMode', 'status: "waiting"', 'status: "reply"', 'zpeerMembershipsForPeer', 'joinZpeerRoom', 'leaveZpeerRoom', 'useZpeerRoom', 'clearZpeerRoom', 'preservedSelf: true', 'roomId?: string', 'buildZpeerPeerRoomSummaries', 'active: membership.roomId === activeRoomId', 'peerRespondsToAliasPing', 'activeAliasCollision', 'zpeer-alias-ping']) {
58
58
  if (!zpeer.includes(needle)) failures.push(`zpeer missing ${needle}`);
59
59
  }
@@ -139,7 +139,7 @@ for (const needle of ['readZpeerNewCarryoverProfile(repoRoot)', 'const carryover
139
139
  }
140
140
  if (!events.includes('event.source === "extension" && !event.text.trim()') || !events.includes('action: "handled" as const')) failures.push('runtime must handle empty extension follow-ups without continuing the agent');
141
141
  if (!events.includes('ZPeer async reply received from @') || !events.includes('{ triggerTurn: true, deliverAs: "followUp" }')) failures.push('runtime must resume an idle agent with a follow-up when an async ZPeer reply arrives');
142
- for (const needle of ['ZPEER AWARENESS (transient, rebuilt each turn)', 'zpeer_ask with mode=\\"async\\"', 'Passive wait rule', 'stop the turn and remain idle', 'avoid spam', 'Raw ZPeer bodies are transient', 'registerMessageRenderer("zob-zpeer-event"', 'scheduleZpeerHeartbeat', 'clearZpeerHeartbeatTimer', 'refreshZpeerSelf(repoRoot', 'kind: "response_sent"', 'kind: "inbound"']) {
142
+ for (const needle of ['ZPEER AWARENESS (transient, rebuilt each turn)', 'buildZpeerPeerRoomSummaries(repoRoot, state.zobLive.peerCard)', '- rooms:', 'explicit roomId', 'zpeer_ask with mode=\\"async\\"', 'Passive wait rule', 'stop the turn and remain idle', 'avoid spam', 'Raw ZPeer bodies are transient', 'registerMessageRenderer("zob-zpeer-event"', 'scheduleZpeerHeartbeat', 'clearZpeerHeartbeatTimer', 'refreshZpeerSelf(repoRoot', 'kind: "response_sent"', 'kind: "inbound"']) {
143
143
  if (!events.includes(needle)) failures.push(`runtime missing zpeer awareness/event support ${needle}`);
144
144
  }
145
145
  const responseSentBlock = events.match(/setZpeerLastEvent\(state, \{\s*kind: "response_sent"[\s\S]*?customType: "zob-zpeer-event"[\s\S]*?triggerTurn: false[\s\S]*?\}\);/)?.[0] ?? '';
@@ -150,7 +150,7 @@ for (const needle of ['roomId:', 'fromAlias:', 'toAlias:', 'status: "response_se
150
150
  for (const forbidden of ['transientPrompt:', 'transientResponse:', 'prompt:', 'response:', 'body:', 'content: responseText', 'message:', 'text:', 'rationale:', 'diff:', 'patch:']) {
151
151
  if (responseSentBlock.includes(forbidden)) failures.push(`response_sent feed card contains forbidden raw/body-like field ${forbidden}`);
152
152
  }
153
- const transcriptCapture = contents['.pi/extensions/zob-harness/src/coms-v2/transcript-capture.ts'];
153
+ const transcriptCapture = contents['.pi/extensions/zob-harness/src/domains/coms/coms-v2/transcript-capture.ts'];
154
154
  const artifactBlock = transcriptCapture.match(/const artifact = \{[\s\S]*?\n \};/)?.[0] ?? '';
155
155
  if (!artifactBlock) failures.push('redacted capture artifact shape not found');
156
156
  for (const needle of ['rawBodiesStored: false', 'redactedBodiesStored: true', 'comsLedgerBodyStored: false', 'bodyStored: false', 'taskHash: input.taskHash', 'outputHash: input.outputHash']) {
@@ -175,7 +175,7 @@ if (!stateTs?.includes('ZobLiveLastEvent')) failures.push('runtime state missing
175
175
  for (const needle of ['ZobPassivePeerWaitState', 'passivePeerWait?: ZobPassivePeerWaitState', 'schema: "zob.passive-peer-wait.v1"', 'suppressGoalContinuation: true', 'bodyStored: false', 'localOnly: true', 'networkEnabled: false']) {
176
176
  if (!stateTs.includes(needle)) failures.push(`runtime state missing passive peer wait field ${needle}`);
177
177
  }
178
- const goalRuntime = contents['.pi/extensions/zob-harness/src/goal-runtime.ts'];
178
+ const goalRuntime = contents['.pi/extensions/zob-harness/src/runtime/goal-runtime.ts'];
179
179
  for (const needle of ['state.zobLive.passivePeerWait?.suppressGoalContinuation === true', 'clearRuntimeGoalContinuationTimer(state)', 'return;']) {
180
180
  if (!goalRuntime.includes(needle)) failures.push(`goal runtime missing passive peer continuation suppression ${needle}`);
181
181
  }
@@ -189,7 +189,7 @@ for (const forbidden of ['transientPrompt:', 'transientResponse:', 'message:', '
189
189
  for (const needle of ['clearPassivePeerWaitForResponse', 'envelope.type !== "response"', 'envelope.msgId === wait.msgId', 'state.zobLive.passivePeerWait = undefined', 'envelope.sender === wait.targetAlias']) {
190
190
  if (!events.includes(needle)) failures.push(`runtime events missing passive wait response clear ${needle}`);
191
191
  }
192
- if (!contents['.pi/extensions/zob-harness/src/mission-control.ts'].includes('zpeerRooms')) failures.push('Mission Control missing zpeerRooms summary');
192
+ if (!contents['.pi/extensions/zob-harness/src/domains/coms/mission-control.ts'].includes('zpeerRooms')) failures.push('Mission Control missing zpeerRooms summary');
193
193
 
194
194
  if (failures.length > 0) {
195
195
  console.error(`zpeer static smoke FAIL\n- ${failures.join('\n- ')}`);