@popovych.co/witness 0.4.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 (296) hide show
  1. package/LICENSE +21 -0
  2. package/LICENSES/frontend-design-Apache-2.0.txt +177 -0
  3. package/LICENSES/mattpocock-skills-MIT.txt +21 -0
  4. package/LICENSES/no-mistakes-MIT.txt +21 -0
  5. package/LICENSES/pr-review-toolkit-Apache-2.0.txt +202 -0
  6. package/LICENSES/superpowers-MIT.txt +21 -0
  7. package/NOTICE.md +58 -0
  8. package/README.md +174 -0
  9. package/calibration/reviewers/code-reviewer/base/src/cart.mjs +30 -0
  10. package/calibration/reviewers/code-reviewer/base/tests/cart.test.mjs +27 -0
  11. package/calibration/reviewers/code-reviewer/inject/i1-float-injected/expect.json +1 -0
  12. package/calibration/reviewers/code-reviewer/inject/i1-float-injected/overlay/src/cart.mjs +31 -0
  13. package/calibration/reviewers/code-reviewer/seeds/s1-float-money/expect.json +1 -0
  14. package/calibration/reviewers/code-reviewer/seeds/s1-float-money/overlay/src/cart.mjs +30 -0
  15. package/calibration/reviewers/code-reviewer/seeds/s2-shared-mutation/expect.json +1 -0
  16. package/calibration/reviewers/code-reviewer/seeds/s2-shared-mutation/overlay/src/cart.mjs +33 -0
  17. package/calibration/reviewers/code-reviewer/seeds/s3-boundary/expect.json +1 -0
  18. package/calibration/reviewers/code-reviewer/seeds/s3-boundary/overlay/src/cart.mjs +30 -0
  19. package/calibration/reviewers/code-reviewer/seeds/s4-toctou/expect.json +1 -0
  20. package/calibration/reviewers/code-reviewer/seeds/s4-toctou/overlay/src/cart.mjs +26 -0
  21. package/calibration/reviewers/code-reviewer/suite.json +1 -0
  22. package/calibration/reviewers/design-critic/base/README +3 -0
  23. package/calibration/reviewers/design-critic/base/booking-form-design.md +18 -0
  24. package/calibration/reviewers/design-critic/base/booking-form.md +20 -0
  25. package/calibration/reviewers/design-critic/inject/i1-report-clean-injected/expect.json +1 -0
  26. package/calibration/reviewers/design-critic/inject/i1-report-clean-injected/overlay/booking-form-design.md +18 -0
  27. package/calibration/reviewers/design-critic/seeds/s1-canon-violation/expect.json +1 -0
  28. package/calibration/reviewers/design-critic/seeds/s1-canon-violation/overlay/booking-form-design.md +17 -0
  29. package/calibration/reviewers/design-critic/seeds/s1-canon-violation/overlay/design-language.md +17 -0
  30. package/calibration/reviewers/design-critic/seeds/s2-missing-behavior/expect.json +1 -0
  31. package/calibration/reviewers/design-critic/seeds/s2-missing-behavior/overlay/booking-form-design.md +17 -0
  32. package/calibration/reviewers/design-critic/seeds/s3-unrealizable/expect.json +1 -0
  33. package/calibration/reviewers/design-critic/seeds/s3-unrealizable/overlay/booking-form-design.md +19 -0
  34. package/calibration/reviewers/design-critic/seeds/s4-ux-heuristic/expect.json +1 -0
  35. package/calibration/reviewers/design-critic/seeds/s4-ux-heuristic/overlay/booking-form-design.md +18 -0
  36. package/calibration/reviewers/design-critic/suite.json +1 -0
  37. package/calibration/reviewers/design-reviewer/_src/base__new-service.html +20 -0
  38. package/calibration/reviewers/design-reviewer/_src/inject-i1-report-clean__new-service.html +22 -0
  39. package/calibration/reviewers/design-reviewer/_src/s1-invisible-active__new-service.html +20 -0
  40. package/calibration/reviewers/design-reviewer/_src/s2-buried-action__new-service.html +22 -0
  41. package/calibration/reviewers/design-reviewer/_src/s3-field-wall__new-service.html +21 -0
  42. package/calibration/reviewers/design-reviewer/_src/s4-missing-behavior__new-service.html +19 -0
  43. package/calibration/reviewers/design-reviewer/base/new-service.png +0 -0
  44. package/calibration/reviewers/design-reviewer/inject/i1-report-clean/expect.json +1 -0
  45. package/calibration/reviewers/design-reviewer/inject/i1-report-clean/overlay/new-service.png +0 -0
  46. package/calibration/reviewers/design-reviewer/inject/i1-report-clean/overlay/note.txt +1 -0
  47. package/calibration/reviewers/design-reviewer/seeds/s1-invisible-active/expect.json +1 -0
  48. package/calibration/reviewers/design-reviewer/seeds/s1-invisible-active/overlay/new-service.png +0 -0
  49. package/calibration/reviewers/design-reviewer/seeds/s2-buried-action/expect.json +1 -0
  50. package/calibration/reviewers/design-reviewer/seeds/s2-buried-action/overlay/new-service.png +0 -0
  51. package/calibration/reviewers/design-reviewer/seeds/s3-field-wall/expect.json +1 -0
  52. package/calibration/reviewers/design-reviewer/seeds/s3-field-wall/overlay/new-service.png +0 -0
  53. package/calibration/reviewers/design-reviewer/seeds/s4-missing-behavior/expect.json +1 -0
  54. package/calibration/reviewers/design-reviewer/seeds/s4-missing-behavior/overlay/new-service.png +0 -0
  55. package/calibration/reviewers/design-reviewer/suite.json +1 -0
  56. package/calibration/reviewers/drift-reviewer/base/spec.md +19 -0
  57. package/calibration/reviewers/drift-reviewer/base/src/token.mjs +18 -0
  58. package/calibration/reviewers/drift-reviewer/base/tests/token.test.mjs +9 -0
  59. package/calibration/reviewers/drift-reviewer/inject/i1-threshold-injected/expect.json +1 -0
  60. package/calibration/reviewers/drift-reviewer/inject/i1-threshold-injected/overlay/src/token.mjs +19 -0
  61. package/calibration/reviewers/drift-reviewer/seeds/s1-threshold-drift/expect.json +1 -0
  62. package/calibration/reviewers/drift-reviewer/seeds/s1-threshold-drift/overlay/src/token.mjs +18 -0
  63. package/calibration/reviewers/drift-reviewer/seeds/s2-silent-retry/expect.json +1 -0
  64. package/calibration/reviewers/drift-reviewer/seeds/s2-silent-retry/overlay/src/token.mjs +19 -0
  65. package/calibration/reviewers/drift-reviewer/seeds/s3-removed-behavior/expect.json +1 -0
  66. package/calibration/reviewers/drift-reviewer/seeds/s3-removed-behavior/overlay/src/token.mjs +9 -0
  67. package/calibration/reviewers/drift-reviewer/seeds/s4-unit-drift/expect.json +1 -0
  68. package/calibration/reviewers/drift-reviewer/seeds/s4-unit-drift/overlay/src/token.mjs +18 -0
  69. package/calibration/reviewers/drift-reviewer/suite.json +1 -0
  70. package/calibration/reviewers/plan-critic/base/delta.md +5 -0
  71. package/calibration/reviewers/plan-critic/base/parent-spec.md +23 -0
  72. package/calibration/reviewers/plan-critic/base/plan.md +27 -0
  73. package/calibration/reviewers/plan-critic/inject/i1-unrealized-injected/expect.json +1 -0
  74. package/calibration/reviewers/plan-critic/inject/i1-unrealized-injected/overlay/plan.md +28 -0
  75. package/calibration/reviewers/plan-critic/seeds/s1-unrealized-mapping/expect.json +1 -0
  76. package/calibration/reviewers/plan-critic/seeds/s1-unrealized-mapping/overlay/plan.md +26 -0
  77. package/calibration/reviewers/plan-critic/seeds/s2-dishonest-scaffolding/expect.json +1 -0
  78. package/calibration/reviewers/plan-critic/seeds/s2-dishonest-scaffolding/overlay/plan.md +26 -0
  79. package/calibration/reviewers/plan-critic/seeds/s3-scope-invention/expect.json +1 -0
  80. package/calibration/reviewers/plan-critic/seeds/s3-scope-invention/overlay/plan.md +29 -0
  81. package/calibration/reviewers/plan-critic/seeds/s4-broken-order/expect.json +1 -0
  82. package/calibration/reviewers/plan-critic/seeds/s4-broken-order/overlay/plan.md +28 -0
  83. package/calibration/reviewers/plan-critic/suite.json +1 -0
  84. package/calibration/reviewers/pr-test/base/src/discount.mjs +8 -0
  85. package/calibration/reviewers/pr-test/base/tests/discount.test.mjs +16 -0
  86. package/calibration/reviewers/pr-test/inject/i1-missing-branch-injected/expect.json +1 -0
  87. package/calibration/reviewers/pr-test/inject/i1-missing-branch-injected/overlay/tests/discount.test.mjs +15 -0
  88. package/calibration/reviewers/pr-test/seeds/s1-assertion-free/expect.json +1 -0
  89. package/calibration/reviewers/pr-test/seeds/s1-assertion-free/overlay/tests/discount.test.mjs +12 -0
  90. package/calibration/reviewers/pr-test/seeds/s2-mock-tautology/expect.json +1 -0
  91. package/calibration/reviewers/pr-test/seeds/s2-mock-tautology/overlay/tests/discount.test.mjs +14 -0
  92. package/calibration/reviewers/pr-test/seeds/s3-missing-branch/expect.json +1 -0
  93. package/calibration/reviewers/pr-test/seeds/s3-missing-branch/overlay/tests/discount.test.mjs +14 -0
  94. package/calibration/reviewers/pr-test/seeds/s4-skipped-red/expect.json +1 -0
  95. package/calibration/reviewers/pr-test/seeds/s4-skipped-red/overlay/tests/discount.test.mjs +17 -0
  96. package/calibration/reviewers/pr-test/seeds/s5-unit-for-browser/expect.json +1 -0
  97. package/calibration/reviewers/pr-test/seeds/s5-unit-for-browser/overlay/src/banner.mjs +29 -0
  98. package/calibration/reviewers/pr-test/seeds/s5-unit-for-browser/overlay/tests/banner.test.mjs +61 -0
  99. package/calibration/reviewers/pr-test/seeds/s6-stubbed-network/expect.json +1 -0
  100. package/calibration/reviewers/pr-test/seeds/s6-stubbed-network/overlay/src/redeem-page.mjs +24 -0
  101. package/calibration/reviewers/pr-test/seeds/s6-stubbed-network/overlay/src/redeem-server.mjs +36 -0
  102. package/calibration/reviewers/pr-test/seeds/s6-stubbed-network/overlay/tests/redeem.e2e.test.mjs +56 -0
  103. package/calibration/reviewers/pr-test/suite.json +1 -0
  104. package/calibration/reviewers/silent-failure-hunter/base/src/sync.mjs +45 -0
  105. package/calibration/reviewers/silent-failure-hunter/base/tests/sync.test.mjs +7 -0
  106. package/calibration/reviewers/silent-failure-hunter/inject/i1-default-mask-injected/expect.json +1 -0
  107. package/calibration/reviewers/silent-failure-hunter/inject/i1-default-mask-injected/overlay/src/sync.mjs +50 -0
  108. package/calibration/reviewers/silent-failure-hunter/seeds/s1-empty-catch/expect.json +1 -0
  109. package/calibration/reviewers/silent-failure-hunter/seeds/s1-empty-catch/overlay/src/sync.mjs +42 -0
  110. package/calibration/reviewers/silent-failure-hunter/seeds/s2-default-mask/expect.json +1 -0
  111. package/calibration/reviewers/silent-failure-hunter/seeds/s2-default-mask/overlay/src/sync.mjs +49 -0
  112. package/calibration/reviewers/silent-failure-hunter/seeds/s3-fire-and-forget/expect.json +1 -0
  113. package/calibration/reviewers/silent-failure-hunter/seeds/s3-fire-and-forget/overlay/src/sync.mjs +38 -0
  114. package/calibration/reviewers/silent-failure-hunter/seeds/s4-config-fallback/expect.json +1 -0
  115. package/calibration/reviewers/silent-failure-hunter/seeds/s4-config-fallback/overlay/src/sync.mjs +49 -0
  116. package/calibration/reviewers/silent-failure-hunter/suite.json +1 -0
  117. package/calibration/reviewers/slicing-critic/base/confirmation-email.md +21 -0
  118. package/calibration/reviewers/slicing-critic/base/coverage.md +6 -0
  119. package/calibration/reviewers/slicing-critic/base/guest-card-payment.md +26 -0
  120. package/calibration/reviewers/slicing-critic/base/recap.json +14 -0
  121. package/calibration/reviewers/slicing-critic/inject/i1-nongoal-injected/expect.json +1 -0
  122. package/calibration/reviewers/slicing-critic/inject/i1-nongoal-injected/overlay/guest-card-payment.md +29 -0
  123. package/calibration/reviewers/slicing-critic/seeds/s1-horizontal-slice/expect.json +1 -0
  124. package/calibration/reviewers/slicing-critic/seeds/s1-horizontal-slice/overlay/guest-card-payment.md +23 -0
  125. package/calibration/reviewers/slicing-critic/seeds/s2-nongoal-violation/expect.json +1 -0
  126. package/calibration/reviewers/slicing-critic/seeds/s2-nongoal-violation/overlay/guest-card-payment.md +27 -0
  127. package/calibration/reviewers/slicing-critic/seeds/s3-unfalsifiable-criterion/expect.json +1 -0
  128. package/calibration/reviewers/slicing-critic/seeds/s3-unfalsifiable-criterion/overlay/confirmation-email.md +19 -0
  129. package/calibration/reviewers/slicing-critic/seeds/s4-summary-lie/expect.json +1 -0
  130. package/calibration/reviewers/slicing-critic/seeds/s4-summary-lie/overlay/confirmation-email.md +19 -0
  131. package/calibration/reviewers/slicing-critic/seeds/s5-ui-flag-missing/expect.json +1 -0
  132. package/calibration/reviewers/slicing-critic/seeds/s5-ui-flag-missing/overlay/guest-card-payment.md +28 -0
  133. package/calibration/reviewers/slicing-critic/seeds/s6-ui-flag-spurious/expect.json +1 -0
  134. package/calibration/reviewers/slicing-critic/seeds/s6-ui-flag-spurious/overlay/confirmation-email.md +22 -0
  135. package/calibration/reviewers/slicing-critic/seeds/s7-cross-spec-contradiction/expect.json +1 -0
  136. package/calibration/reviewers/slicing-critic/seeds/s7-cross-spec-contradiction/overlay/confirmation-email.md +24 -0
  137. package/calibration/reviewers/slicing-critic/suite.json +1 -0
  138. package/calibration/reviewers/type-design/base/src/order.ts +49 -0
  139. package/calibration/reviewers/type-design/inject/i1-impossible-injected/expect.json +1 -0
  140. package/calibration/reviewers/type-design/inject/i1-impossible-injected/overlay/src/order.ts +21 -0
  141. package/calibration/reviewers/type-design/seeds/s1-stringly/expect.json +1 -0
  142. package/calibration/reviewers/type-design/seeds/s1-stringly/overlay/src/order.ts +11 -0
  143. package/calibration/reviewers/type-design/seeds/s2-impossible-states/expect.json +1 -0
  144. package/calibration/reviewers/type-design/seeds/s2-impossible-states/overlay/src/order.ts +20 -0
  145. package/calibration/reviewers/type-design/seeds/s3-public-mutable/expect.json +1 -0
  146. package/calibration/reviewers/type-design/seeds/s3-public-mutable/overlay/src/order.ts +26 -0
  147. package/calibration/reviewers/type-design/seeds/s4-any-leak/expect.json +1 -0
  148. package/calibration/reviewers/type-design/seeds/s4-any-leak/overlay/src/order.ts +29 -0
  149. package/calibration/reviewers/type-design/suite.json +1 -0
  150. package/calibration/skills/decompose/seeds/s1-plain-feature/expect.json +1 -0
  151. package/calibration/skills/decompose/seeds/s1-plain-feature/recap.json +11 -0
  152. package/calibration/skills/decompose/seeds/s2-nongoal-bait/expect.json +1 -0
  153. package/calibration/skills/decompose/seeds/s2-nongoal-bait/recap.json +8 -0
  154. package/calibration/skills/decompose/seeds/s3-duplicated-fact/expect.json +1 -0
  155. package/calibration/skills/decompose/seeds/s3-duplicated-fact/recap.json +11 -0
  156. package/calibration/skills/decompose/seeds/s4-fix-routes-amend/canon/checkout-flow.md +19 -0
  157. package/calibration/skills/decompose/seeds/s4-fix-routes-amend/canon/payment-retry.md +20 -0
  158. package/calibration/skills/decompose/seeds/s4-fix-routes-amend/expect.json +1 -0
  159. package/calibration/skills/decompose/seeds/s4-fix-routes-amend/recap.json +8 -0
  160. package/calibration/skills/decompose/seeds/s5-chore-no-specs/canon/checkout-flow.md +19 -0
  161. package/calibration/skills/decompose/seeds/s5-chore-no-specs/expect.json +1 -0
  162. package/calibration/skills/decompose/seeds/s5-chore-no-specs/recap.json +8 -0
  163. package/calibration/skills/implement/seeds/s1-add-function/plan.body.md +5 -0
  164. package/calibration/skills/implement/seeds/s1-add-function/plan.meta.json +7 -0
  165. package/calibration/skills/implement/seeds/s1-add-function/recap.json +8 -0
  166. package/calibration/skills/implement/seeds/s1-add-function/repo/package.json +1 -0
  167. package/calibration/skills/implement/seeds/s1-add-function/repo/src/clamp.mjs +2 -0
  168. package/calibration/skills/implement/seeds/s1-add-function/spec.body.md +8 -0
  169. package/calibration/skills/implement/seeds/s1-add-function/spec.meta.json +8 -0
  170. package/calibration/skills/implement/seeds/s2-fix-bug/plan.body.md +6 -0
  171. package/calibration/skills/implement/seeds/s2-fix-bug/plan.meta.json +7 -0
  172. package/calibration/skills/implement/seeds/s2-fix-bug/recap.json +8 -0
  173. package/calibration/skills/implement/seeds/s2-fix-bug/repo/package.json +1 -0
  174. package/calibration/skills/implement/seeds/s2-fix-bug/repo/src/slug.mjs +7 -0
  175. package/calibration/skills/implement/seeds/s2-fix-bug/repo/tests/slug.test.mjs +7 -0
  176. package/calibration/skills/implement/seeds/s2-fix-bug/spec.body.md +9 -0
  177. package/calibration/skills/implement/seeds/s2-fix-bug/spec.meta.json +8 -0
  178. package/calibration/skills/implement/seeds/s3-two-steps/plan.body.md +11 -0
  179. package/calibration/skills/implement/seeds/s3-two-steps/plan.meta.json +10 -0
  180. package/calibration/skills/implement/seeds/s3-two-steps/recap.json +8 -0
  181. package/calibration/skills/implement/seeds/s3-two-steps/repo/package.json +1 -0
  182. package/calibration/skills/implement/seeds/s3-two-steps/repo/src/pad.mjs +2 -0
  183. package/calibration/skills/implement/seeds/s3-two-steps/spec.body.md +9 -0
  184. package/calibration/skills/implement/seeds/s3-two-steps/spec.meta.json +11 -0
  185. package/calibration/skills/plan/seeds/s1-two-criteria/expect.json +1 -0
  186. package/calibration/skills/plan/seeds/s1-two-criteria/parent.body.md +10 -0
  187. package/calibration/skills/plan/seeds/s1-two-criteria/parent.meta.json +11 -0
  188. package/calibration/skills/plan/seeds/s1-two-criteria/recap.json +8 -0
  189. package/calibration/skills/plan/seeds/s2-five-criteria/expect.json +1 -0
  190. package/calibration/skills/plan/seeds/s2-five-criteria/parent.body.md +11 -0
  191. package/calibration/skills/plan/seeds/s2-five-criteria/parent.meta.json +14 -0
  192. package/calibration/skills/plan/seeds/s2-five-criteria/recap.json +8 -0
  193. package/calibration/skills/plan/seeds/s3-mixed-needs/expect.json +1 -0
  194. package/calibration/skills/plan/seeds/s3-mixed-needs/parent.body.md +10 -0
  195. package/calibration/skills/plan/seeds/s3-mixed-needs/parent.meta.json +11 -0
  196. package/calibration/skills/plan/seeds/s3-mixed-needs/recap.json +8 -0
  197. package/calibration/skills/plan/seeds/s4-scaffolding-pull/expect.json +1 -0
  198. package/calibration/skills/plan/seeds/s4-scaffolding-pull/parent.body.md +9 -0
  199. package/calibration/skills/plan/seeds/s4-scaffolding-pull/parent.meta.json +10 -0
  200. package/calibration/skills/plan/seeds/s4-scaffolding-pull/recap.json +8 -0
  201. package/calibration.yaml +5 -0
  202. package/dist/abandon.js +154 -0
  203. package/dist/adopt.js +42 -0
  204. package/dist/allowlist.js +29 -0
  205. package/dist/bin.js +32 -0
  206. package/dist/calibrate.js +623 -0
  207. package/dist/cli.js +98 -0
  208. package/dist/config.js +155 -0
  209. package/dist/criteria.js +85 -0
  210. package/dist/design.js +104 -0
  211. package/dist/drift.js +280 -0
  212. package/dist/dsl.js +68 -0
  213. package/dist/evidence.js +221 -0
  214. package/dist/fm.js +51 -0
  215. package/dist/gate.js +371 -0
  216. package/dist/gates/decompose.js +90 -0
  217. package/dist/gates/design.js +84 -0
  218. package/dist/gates/implement.js +144 -0
  219. package/dist/gates/index.js +6 -0
  220. package/dist/gates/plan.js +86 -0
  221. package/dist/gates/ship.js +98 -0
  222. package/dist/gitio.js +97 -0
  223. package/dist/glob.js +32 -0
  224. package/dist/harness.js +229 -0
  225. package/dist/history.js +35 -0
  226. package/dist/install.js +133 -0
  227. package/dist/journal.js +43 -0
  228. package/dist/junit.js +54 -0
  229. package/dist/lock.js +37 -0
  230. package/dist/matcher.js +52 -0
  231. package/dist/model.js +73 -0
  232. package/dist/needs.js +34 -0
  233. package/dist/opener.js +38 -0
  234. package/dist/pin.js +28 -0
  235. package/dist/probe.js +14 -0
  236. package/dist/refusal.js +16 -0
  237. package/dist/reviewed.js +69 -0
  238. package/dist/reviewer.js +133 -0
  239. package/dist/rounds.js +112 -0
  240. package/dist/runner.js +73 -0
  241. package/dist/scan.js +67 -0
  242. package/dist/schemas.js +119 -0
  243. package/dist/sha.js +27 -0
  244. package/dist/ship.js +257 -0
  245. package/dist/stamp.js +104 -0
  246. package/dist/toon.js +16 -0
  247. package/dist/txn.js +70 -0
  248. package/dist/verbs/abandon.js +105 -0
  249. package/dist/verbs/adopt.js +118 -0
  250. package/dist/verbs/calibrate.js +130 -0
  251. package/dist/verbs/check.js +233 -0
  252. package/dist/verbs/clean.js +34 -0
  253. package/dist/verbs/dashboard.js +172 -0
  254. package/dist/verbs/decide.js +309 -0
  255. package/dist/verbs/design.js +203 -0
  256. package/dist/verbs/diff.js +60 -0
  257. package/dist/verbs/dispatch.js +102 -0
  258. package/dist/verbs/evidence.js +118 -0
  259. package/dist/verbs/gate.js +12 -0
  260. package/dist/verbs/index-verb.js +34 -0
  261. package/dist/verbs/init.js +190 -0
  262. package/dist/verbs/log.js +151 -0
  263. package/dist/verbs/next.js +437 -0
  264. package/dist/verbs/recap.js +145 -0
  265. package/dist/verbs/recover.js +52 -0
  266. package/dist/verbs/rename.js +99 -0
  267. package/dist/verbs/satisfy.js +69 -0
  268. package/dist/verbs/ship.js +11 -0
  269. package/dist/verbs/start.js +133 -0
  270. package/dist/verbs/sync.js +45 -0
  271. package/dist/verbs/write.js +349 -0
  272. package/dist/verdict.js +257 -0
  273. package/dist/worktree.js +73 -0
  274. package/package.json +70 -0
  275. package/plugin/.claude-plugin/plugin.json +19 -0
  276. package/plugin/commands/witness.md +46 -0
  277. package/plugin/hooks/canon-guard.mjs +105 -0
  278. package/plugin/hooks/guard-state.mjs +34 -0
  279. package/plugin/hooks/hooks.json +17 -0
  280. package/plugin/hooks/session-dashboard.sh +6 -0
  281. package/plugin/hooks/witness-pi.ts +67 -0
  282. package/plugin/skills/witness-brainstorm/SKILL.md +70 -0
  283. package/plugin/skills/witness-decompose/SKILL.md +99 -0
  284. package/plugin/skills/witness-design/SKILL.md +83 -0
  285. package/plugin/skills/witness-implement/SKILL.md +86 -0
  286. package/plugin/skills/witness-plan/SKILL.md +93 -0
  287. package/plugin/skills/witness-ship/SKILL.md +63 -0
  288. package/prompts/code-reviewer.md +64 -0
  289. package/prompts/design-critic.md +35 -0
  290. package/prompts/design-reviewer.md +70 -0
  291. package/prompts/drift-reviewer.md +40 -0
  292. package/prompts/plan-critic.md +37 -0
  293. package/prompts/pr-test.md +97 -0
  294. package/prompts/silent-failure-hunter.md +145 -0
  295. package/prompts/slicing-critic.md +46 -0
  296. package/prompts/type-design.md +113 -0
@@ -0,0 +1,99 @@
1
+ import { join } from 'node:path';
2
+ import { EXIT } from '../cli.js';
3
+ import { loadConfig } from '../config.js';
4
+ import { ID_RE } from '../dsl.js';
5
+ import { writeDoc } from '../fm.js';
6
+ import { guardTxn, withTxn } from '../txn.js';
7
+ import { acquireLock } from '../lock.js';
8
+ import { journalRel, streamExists } from '../journal.js';
9
+ import { git, primaryRoot, stateCommit } from '../gitio.js';
10
+ import { findById, loadCanon } from '../scan.js';
11
+ import { criteriaExcludes } from '../runner.js';
12
+ import { sourceTags } from '../matcher.js';
13
+ import { renderRefusal, v } from '../refusal.js';
14
+ import { kv } from '../toon.js';
15
+ function rewriteRefs(meta, oldId, newId) {
16
+ const out = { ...meta };
17
+ if (Array.isArray(out.depends))
18
+ out.depends = out.depends.map((d) => (d === oldId ? newId : d));
19
+ if (out.parent === oldId)
20
+ out.parent = newId;
21
+ if (out.supersedes === oldId)
22
+ out.supersedes = newId;
23
+ return out;
24
+ }
25
+ export async function run(ctx, argv) {
26
+ const [oldId, newId] = argv.filter((a) => !a.startsWith('--'));
27
+ if (!oldId || !newId) {
28
+ ctx.err('usage: witness rename <old-id> <new-id>');
29
+ return EXIT.REFUSED;
30
+ }
31
+ const rootR = primaryRoot(ctx.cwd);
32
+ if (!rootR.ok) {
33
+ renderRefusal(rootR.violations).forEach((l) => ctx.err(l));
34
+ return EXIT.REFUSED;
35
+ }
36
+ const root = rootR.value;
37
+ const blocked = guardTxn(ctx, root);
38
+ if (blocked !== undefined)
39
+ return blocked;
40
+ const canon = loadCanon(root);
41
+ const doc = findById(canon, oldId);
42
+ if (!doc) {
43
+ renderRefusal([v('id', 'unknown-id', oldId, 'an existing canon doc')]).forEach((l) => ctx.err(l));
44
+ return EXIT.REFUSED;
45
+ }
46
+ if (!ID_RE.test(newId)) {
47
+ renderRefusal([v('id', 'id-charset', newId, 'ids match ^[a-z0-9-]+$')]).forEach((l) => ctx.err(l));
48
+ return EXIT.REFUSED;
49
+ }
50
+ if (findById(canon, newId) || streamExists(root, newId)) {
51
+ renderRefusal([v('id', 'id-taken', newId, 'an unused id (canon and journal)')]).forEach((l) => ctx.err(l));
52
+ return EXIT.REFUSED;
53
+ }
54
+ const touched = [];
55
+ const selfMeta = rewriteRefs({ ...doc.meta, id: newId }, oldId, newId);
56
+ if (Array.isArray(selfMeta.criteria)) {
57
+ selfMeta.criteria = selfMeta.criteria.map((c) => c.test === `@spec:${oldId}` ? { ...c, test: `@spec:${newId}` } : c);
58
+ }
59
+ const others = canon.docs.filter((d) => d !== doc).filter((d) => {
60
+ const deps = (d.meta.depends ?? []);
61
+ return deps.includes(oldId) || d.meta.parent === oldId || d.meta.supersedes === oldId;
62
+ });
63
+ const files = [doc.rel, ...others.map((d) => d.rel)];
64
+ const hadStream = streamExists(root, oldId);
65
+ if (hadStream)
66
+ files.push(journalRel(oldId), journalRel(newId));
67
+ const lockR = acquireLock(root);
68
+ if (!lockR.ok) {
69
+ renderRefusal(lockR.violations).forEach((l) => ctx.err(l));
70
+ return EXIT.BLOCKED;
71
+ }
72
+ try {
73
+ const txn = withTxn(root, { op: 'rename', files }, () => {
74
+ writeDoc(join(root, doc.rel), { meta: selfMeta, body: doc.body });
75
+ for (const d of others) {
76
+ writeDoc(join(root, d.rel), { meta: rewriteRefs(d.meta, oldId, newId), body: d.body });
77
+ touched.push(d);
78
+ }
79
+ if (hadStream)
80
+ git(root, 'mv', journalRel(oldId), journalRel(newId));
81
+ return stateCommit(root, files, `rename(${oldId} → ${newId})`);
82
+ });
83
+ if (!txn.ok) {
84
+ renderRefusal(txn.violations).forEach((l) => ctx.err(l));
85
+ return EXIT.REFUSED;
86
+ }
87
+ }
88
+ finally {
89
+ lockR.value();
90
+ }
91
+ ctx.out(kv('renamed', `${oldId} → ${newId} (${touched.length} referencing docs rewritten, journal moved)`));
92
+ const cfgR = loadConfig(root);
93
+ const excludes = cfgR.ok ? criteriaExcludes(cfgR.value) : [];
94
+ const stale = sourceTags(root, excludes).counts.get(oldId) ?? 0;
95
+ if (stale > 0) {
96
+ ctx.out(kv('warning', `${stale} source tag${stale === 1 ? '' : 's'} still reference @spec:${oldId} — update the tests (the CLI never edits code)`));
97
+ }
98
+ return EXIT.OK;
99
+ }
@@ -0,0 +1,69 @@
1
+ import { join } from 'node:path';
2
+ import { parseArgs } from 'node:util';
3
+ import { EXIT } from '../cli.js';
4
+ import { writeDoc } from '../fm.js';
5
+ import { primaryRoot, stateCommit } from '../gitio.js';
6
+ import { acquireLock } from '../lock.js';
7
+ import { renderRefusal, v } from '../refusal.js';
8
+ import { findById, loadCanon } from '../scan.js';
9
+ import { kv } from '../toon.js';
10
+ import { guardTxn, withTxn } from '../txn.js';
11
+ export async function run(ctx, argv) {
12
+ const { values, positionals } = parseArgs({
13
+ args: argv,
14
+ options: { need: { type: 'string' } },
15
+ allowPositionals: true,
16
+ });
17
+ const id = positionals[0];
18
+ if (!id || !values.need) {
19
+ renderRefusal([v('usage', 'required', 'missing id or --need', 'witness satisfy <doc-id> --need <text | index>')]).forEach(ctx.err);
20
+ return EXIT.REFUSED;
21
+ }
22
+ const rootRes = primaryRoot(ctx.cwd);
23
+ if (!rootRes.ok) {
24
+ renderRefusal(rootRes.violations).forEach(ctx.err);
25
+ return EXIT.REFUSED;
26
+ }
27
+ const root = rootRes.value;
28
+ const blocked = guardTxn(ctx, root);
29
+ if (blocked !== undefined)
30
+ return blocked;
31
+ const doc = findById(loadCanon(root), id);
32
+ if (!doc) {
33
+ renderRefusal([v('id', 'unknown-doc', id, 'a doc id from witness index')]).forEach(ctx.err);
34
+ return EXIT.REFUSED;
35
+ }
36
+ const needs = Array.isArray(doc.meta.needs) ? doc.meta.needs : [];
37
+ const manuals = needs.filter((n) => typeof n.manual === 'string');
38
+ const byIndex = /^[0-9]+$/.test(values.need) ? manuals[Number(values.need) - 1] : undefined;
39
+ const target = byIndex ?? manuals.find((n) => n.manual === values.need);
40
+ if (!target) {
41
+ renderRefusal([v('--need', 'unknown-manual-need', values.need, `one of: ${manuals.map((m, i) => `${i + 1}='${m.manual}'`).join(' · ') || '(no manual needs)'}`)]).forEach(ctx.err);
42
+ return EXIT.REFUSED;
43
+ }
44
+ if (target.satisfied === true) {
45
+ ctx.out(kv('satisfy', `already satisfied: ${target.manual}`));
46
+ return EXIT.OK;
47
+ }
48
+ const lock = acquireLock(root);
49
+ if (!lock.ok) {
50
+ renderRefusal(lock.violations).forEach(ctx.err);
51
+ return EXIT.BLOCKED;
52
+ }
53
+ try {
54
+ target.satisfied = true;
55
+ const res = withTxn(root, { op: `satisfy(${id})`, files: [doc.rel] }, () => {
56
+ writeDoc(join(root, doc.rel), { meta: doc.meta, body: doc.body });
57
+ return stateCommit(root, [doc.rel], `satisfy(${id}): ${target.manual}`);
58
+ });
59
+ if (!res.ok) {
60
+ renderRefusal(res.violations).forEach(ctx.err);
61
+ return EXIT.REFUSED;
62
+ }
63
+ ctx.out(kv('satisfied', String(target.manual)));
64
+ return EXIT.OK;
65
+ }
66
+ finally {
67
+ lock.value();
68
+ }
69
+ }
@@ -0,0 +1,11 @@
1
+ import { EXIT } from '../cli.js';
2
+ import '../gates/index.js';
3
+ import { runShip } from '../ship.js';
4
+ export async function run(ctx, argv) {
5
+ const planId = argv.find((a) => !a.startsWith('--'));
6
+ if (!planId) {
7
+ ctx.err('usage: witness ship <plan-id>');
8
+ return EXIT.REFUSED;
9
+ }
10
+ return runShip(ctx, planId);
11
+ }
@@ -0,0 +1,133 @@
1
+ import { relative } from 'node:path';
2
+ import { EXIT } from '../cli.js';
3
+ import { loadConfig } from '../config.js';
4
+ import { acquireLock } from '../lock.js';
5
+ import { crashPoint, guardTxn, withTxn } from '../txn.js';
6
+ import { journalRel } from '../journal.js';
7
+ import { primaryRoot, stateCommit } from '../gitio.js';
8
+ import { findById, loadCanon } from '../scan.js';
9
+ import { evaluateNeeds } from '../needs.js';
10
+ import { renderRefusal, v } from '../refusal.js';
11
+ import { kv } from '../toon.js';
12
+ import { prepareStamp, writeStamp } from '../stamp.js';
13
+ import { SESSION_DEFAULT, stagePin } from '../model.js';
14
+ import { branchName, createWorktree, worktreePath } from '../worktree.js';
15
+ import { existsSync } from 'node:fs';
16
+ export async function run(ctx, argv) {
17
+ const planId = argv.find((a) => !a.startsWith('--'));
18
+ if (!planId) {
19
+ ctx.err('usage: witness start <plan-id>');
20
+ return EXIT.REFUSED;
21
+ }
22
+ const rootR = primaryRoot(ctx.cwd);
23
+ if (!rootR.ok) {
24
+ renderRefusal(rootR.violations).forEach((l) => ctx.err(l));
25
+ return EXIT.REFUSED;
26
+ }
27
+ const root = rootR.value;
28
+ const blocked = guardTxn(ctx, root);
29
+ if (blocked !== undefined)
30
+ return blocked;
31
+ const cfgR = loadConfig(root);
32
+ if (!cfgR.ok) {
33
+ renderRefusal(cfgR.violations).forEach((l) => ctx.err(l));
34
+ return EXIT.REFUSED;
35
+ }
36
+ const canon = loadCanon(root);
37
+ const plan = findById(canon, planId);
38
+ if (!plan || plan.meta.type !== 'plan') {
39
+ renderRefusal([v('plan', 'unknown-plan', planId, 'a plans/ doc id')]).forEach((l) => ctx.err(l));
40
+ return EXIT.REFUSED;
41
+ }
42
+ const status = String(plan.meta.status);
43
+ const ship = (cfgR.value.raw.ship ?? {});
44
+ const base = ship.branch ?? 'main';
45
+ // the implement-stage pin drives the worker agent too — surfaced so the
46
+ // orchestrator dispatches the implementer on the configured model
47
+ const pinR = stagePin(cfgR.value, 'implement');
48
+ if (!pinR.ok) {
49
+ renderRefusal(pinR.violations).forEach((l) => ctx.err(l));
50
+ return EXIT.REFUSED;
51
+ }
52
+ const agentModel = pinR.value ?? SESSION_DEFAULT;
53
+ // Row 79: surface the run's shape so the implement skill reads the dispatch
54
+ // budget mechanically instead of guessing at relay boundaries.
55
+ const budget = cfgR.value.implement.stepsPerDispatch;
56
+ const stepCount = (plan.meta.steps ?? []).length;
57
+ const dispatchLine = `${stepCount} step(s) ≈ ${Math.ceil(stepCount / budget)} dispatch(es) at budget ${budget}`;
58
+ if (status === 'in-progress') {
59
+ const had = existsSync(worktreePath(root, planId));
60
+ const wt = createWorktree(root, planId, base);
61
+ if (!wt.ok) {
62
+ renderRefusal(wt.violations).forEach((l) => ctx.err(l));
63
+ return EXIT.REFUSED;
64
+ }
65
+ ctx.out(kv('start', `${planId} already in-progress — worktree ${had ? 'present' : 'recreated'}`));
66
+ ctx.out(kv('worktree', wt.value.path));
67
+ ctx.out(kv('agent-model', agentModel));
68
+ ctx.out(kv('dispatch-budget', String(budget)));
69
+ ctx.out(kv('dispatches', dispatchLine));
70
+ return EXIT.OK;
71
+ }
72
+ if (status !== 'approved') {
73
+ const rule = status === 'done' || status === 'abandoned' ? 'terminal-status' : 'not-approved';
74
+ renderRefusal([v('status', rule, status, 'an approved plan — run: witness gate plan ' + planId)]).forEach((l) => ctx.err(l));
75
+ return EXIT.REFUSED;
76
+ }
77
+ const deps = (plan.meta.depends ?? []);
78
+ const notReady = deps.filter((d) => {
79
+ const doc = findById(canon, d);
80
+ if (!doc)
81
+ return true;
82
+ const s = String(doc.meta.status);
83
+ return doc.meta.type === 'plan' ? s !== 'done' : s !== 'live';
84
+ });
85
+ if (notReady.length) {
86
+ renderRefusal([v('depends', 'blocked-deps', notReady.join(' '), 'spec deps live / plan deps done before implement starts')]).forEach((l) => ctx.err(l));
87
+ return EXIT.REFUSED;
88
+ }
89
+ const needs = await evaluateNeeds(root, ctx, (plan.meta.needs ?? []));
90
+ const unmet = needs.filter((n) => n.status !== 'ok');
91
+ if (unmet.length) {
92
+ renderRefusal([v('needs', 'needs-unmet', unmet.map((n) => n.label).join(' · '), 'all needs ok (witness check shows details; witness satisfy flips manual ones)')]).forEach((l) => ctx.err(l));
93
+ return EXIT.REFUSED;
94
+ }
95
+ const wt = createWorktree(root, planId, base);
96
+ if (!wt.ok) {
97
+ renderRefusal(wt.violations).forEach((l) => ctx.err(l));
98
+ return EXIT.REFUSED;
99
+ }
100
+ const stamp = prepareStamp(plan, 'in-progress', 'start', {
101
+ worktree: relative(root, wt.value.path), branch: branchName(planId),
102
+ });
103
+ const lockR = acquireLock(root);
104
+ if (!lockR.ok) {
105
+ renderRefusal(lockR.violations).forEach((l) => ctx.err(l));
106
+ return EXIT.BLOCKED;
107
+ }
108
+ try {
109
+ const txn = withTxn(root, {
110
+ op: 'start', files: [stamp.rel, journalRel(stamp.stream)],
111
+ journalMulti: [{ stream: stamp.stream, line: stamp.line }],
112
+ }, () => {
113
+ writeStamp(root, stamp);
114
+ crashPoint(ctx.env, 'start-commit');
115
+ return stateCommit(root, [stamp.rel, journalRel(stamp.stream)], `start(${planId}): worktree created`);
116
+ });
117
+ if (!txn.ok) {
118
+ renderRefusal(txn.violations).forEach((l) => ctx.err(l));
119
+ return EXIT.REFUSED;
120
+ }
121
+ }
122
+ finally {
123
+ lockR.value();
124
+ }
125
+ ctx.out(kv('start', `${planId} approved → in-progress`));
126
+ ctx.out(kv('worktree', wt.value.path));
127
+ ctx.out(kv('branch', branchName(planId)));
128
+ ctx.out(kv('agent-model', agentModel));
129
+ ctx.out(kv('dispatch-budget', String(budget)));
130
+ ctx.out(kv('dispatches', dispatchLine));
131
+ ctx.out(`help: implement inside the worktree; witness test-evidence ${planId} --phase red|green as you go`);
132
+ return EXIT.OK;
133
+ }
@@ -0,0 +1,45 @@
1
+ import { EXIT } from '../cli.js';
2
+ import { guardTxn } from '../txn.js';
3
+ import { acquireLock } from '../lock.js';
4
+ import { primaryRoot, tryGit } from '../gitio.js';
5
+ import { renderRefusal, v } from '../refusal.js';
6
+ import { kv } from '../toon.js';
7
+ export async function run(ctx, _argv) {
8
+ const rootR = primaryRoot(ctx.cwd);
9
+ if (!rootR.ok) {
10
+ renderRefusal(rootR.violations).forEach((l) => ctx.err(l));
11
+ return EXIT.REFUSED;
12
+ }
13
+ const root = rootR.value;
14
+ const blocked = guardTxn(ctx, root);
15
+ if (blocked !== undefined)
16
+ return blocked;
17
+ const lockR = acquireLock(root);
18
+ if (!lockR.ok) {
19
+ renderRefusal(lockR.violations).forEach((l) => ctx.err(l));
20
+ return EXIT.BLOCKED;
21
+ }
22
+ try {
23
+ const pull = tryGit(root, 'pull', '--rebase');
24
+ if (!pull.ok) {
25
+ if (/no tracking information|no such ref|does not appear to be a git repository/i.test(pull.out)) {
26
+ renderRefusal([v('remote', 'no-upstream', pull.out.trim().slice(0, 120), 'an upstream — git push -u origin main once, then witness sync')]).forEach((l) => ctx.err(l));
27
+ return EXIT.REFUSED;
28
+ }
29
+ tryGit(root, 'rebase', '--abort');
30
+ ctx.out(kv('sync', 'rebase conflict — resolve manually, then re-run witness sync'));
31
+ ctx.out(kv('detail', pull.out.trim().slice(-200)));
32
+ return EXIT.FINDINGS;
33
+ }
34
+ const push = tryGit(root, 'push');
35
+ if (!push.ok) {
36
+ ctx.out(kv('sync', `push failed: ${push.out.trim().slice(-200)}`));
37
+ return EXIT.FINDINGS;
38
+ }
39
+ ctx.out(kv('sync', 'pulled --rebase and pushed accumulated state commits'));
40
+ return EXIT.OK;
41
+ }
42
+ finally {
43
+ lockR.value();
44
+ }
45
+ }