@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,190 @@
1
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
2
+ import { join } from 'node:path';
3
+ import { parseArgs } from 'node:util';
4
+ import { EXIT } from '../cli.js';
5
+ import { configPath } from '../config.js';
6
+ import { writeDoc } from '../fm.js';
7
+ import { commitWithTrailer, primaryRoot, tryGit } from '../gitio.js';
8
+ import { resolveHarness } from '../harness.js';
9
+ import { installPayload, mergeSettings, preflightPayload, readIfExists, recordHarness, writeSettings, } from '../install.js';
10
+ import { probe } from '../probe.js';
11
+ import { acquireLock } from '../lock.js';
12
+ import { renderRefusal, v } from '../refusal.js';
13
+ import { kv } from '../toon.js';
14
+ const DEFAULT_CONFIG = `# witness.config.yaml — the whole surface
15
+ schema: 1
16
+ # paths: { specs: docs/specs, plans: docs/plans } # optional canon roots (defaults: specs, plans); git mv existing docs when changing
17
+ # docs: # repo docs registry — enumerated keys, unknown keys refused
18
+ # conventions: [docs/code/architecture.md] # → injected into code-reviewer at implement + ship (cache-keyed)
19
+ gates:
20
+ model: claude-fable-5 # reviewer model pin — exact id, aliases refused
21
+ # any gate block may pin its own model (wins over the global pin), e.g.
22
+ # decompose: { reviewers: [slicing-critic], model: claude-opus-4-8 }
23
+ decompose: { reviewers: [slicing-critic] }
24
+ plan: { reviewers: [plan-critic] }
25
+ implement:
26
+ reviewers: # per change class; a plain list = all classes
27
+ feature: [code-reviewer, silent-failure-hunter, type-design, pr-test, design-reviewer]
28
+ fix: [code-reviewer, silent-failure-hunter, design-reviewer]
29
+ chore: [code-reviewer]
30
+ ship: { reviewers: [drift-reviewer, code-reviewer] }
31
+ criteria:
32
+ runner: 'npm test -- -t "@spec:{id}"' # or: runner: full-suite + report: junit:**/reports/junit.xml
33
+ ship: { test: "npm test", lint: "npm run lint", branch: main }
34
+ `;
35
+ const GITIGNORE_BLOCK = `# witness local (never committed)
36
+ .witness/lock
37
+ .witness/txn.json
38
+ .witness/allow.json
39
+ .witness/calibration.local.yaml
40
+ .witness/screens/
41
+ `;
42
+ const PRINCIPLES_BODY = `# Principles
43
+
44
+ Repo-wide rules and trade-offs. Amend via \`witness write\`; chores may name
45
+ this doc as their plan's parent.
46
+ `;
47
+ const USAGE = 'usage: witness init [--agent claude-code|pi|auto]';
48
+ // `--agent` is what makes a second run legal. A bare `witness init` keeps its tested
49
+ // contract — it refuses `already-initialized` — because scaffolding twice is a mistake.
50
+ // Installing a payload set twice is not: it is how a Claude Code repo gains Pi support,
51
+ // and how a half-finished install is completed.
52
+ export async function run(ctx, argv = []) {
53
+ let agent;
54
+ try {
55
+ agent = parseArgs({ args: argv, options: { agent: { type: 'string' } }, allowPositionals: false })
56
+ .values.agent;
57
+ }
58
+ catch {
59
+ ctx.err(USAGE);
60
+ return EXIT.REFUSED;
61
+ }
62
+ const rootRes = primaryRoot(ctx.cwd);
63
+ if (!rootRes.ok) {
64
+ renderRefusal(rootRes.violations).forEach(ctx.err);
65
+ return EXIT.REFUSED;
66
+ }
67
+ const root = rootRes.value;
68
+ const scaffolded = existsSync(configPath(root));
69
+ if (scaffolded && agent === undefined) {
70
+ renderRefusal([v('witness.config.yaml', 'already-initialized', 'config exists', 'witness is already set up here — witness init --agent <name> installs an agent payload set')]).forEach(ctx.err);
71
+ return EXIT.REFUSED;
72
+ }
73
+ let harness;
74
+ if (agent !== undefined) {
75
+ // `auto` is the one value that consults the detection rungs; a named agent is a
76
+ // claim and refuses when false, listing the harnesses that exist.
77
+ const hxR = agent === 'auto'
78
+ ? resolveHarness(ctx.env, {})
79
+ : resolveHarness({ WITNESS_HARNESS: agent }, {});
80
+ if (!hxR.ok) {
81
+ renderRefusal(hxR.violations.map((x) => ({ ...x, field: '--agent' }))).forEach(ctx.err);
82
+ return EXIT.REFUSED;
83
+ }
84
+ harness = hxR.value.harness;
85
+ // Revision 6: pre-flight before the lock and before any scaffold write, so a
86
+ // refusal here leaves the repo exactly as it was.
87
+ const pre = preflightPayload(root, harness);
88
+ if (!pre.ok) {
89
+ renderRefusal(pre.violations).forEach(ctx.err);
90
+ return EXIT.REFUSED;
91
+ }
92
+ }
93
+ const lock = acquireLock(root);
94
+ if (!lock.ok) {
95
+ renderRefusal(lock.violations).forEach(ctx.err);
96
+ return EXIT.BLOCKED;
97
+ }
98
+ try {
99
+ const files = [];
100
+ if (!scaffolded) {
101
+ writeFileSync(configPath(root), DEFAULT_CONFIG);
102
+ mkdirSync(join(root, 'plans'), { recursive: true });
103
+ mkdirSync(join(root, '.witness', 'journal'), { recursive: true });
104
+ writeFileSync(join(root, 'plans', '.gitkeep'), '');
105
+ writeFileSync(join(root, '.witness', 'journal', '.gitkeep'), '');
106
+ const gi = join(root, '.gitignore');
107
+ const existing = existsSync(gi) ? readFileSync(gi, 'utf8') : '';
108
+ if (!existing.includes('.witness/lock')) {
109
+ writeFileSync(gi, existing + (existing && !existing.endsWith('\n') ? '\n' : '') + GITIGNORE_BLOCK);
110
+ }
111
+ writeDoc(join(root, 'specs', 'principles.md'), {
112
+ meta: { id: 'principles', type: 'principles', status: 'draft', depends: [], needs: [] },
113
+ body: PRINCIPLES_BODY,
114
+ });
115
+ files.push('witness.config.yaml', '.gitignore', 'specs/principles.md', 'plans/.gitkeep', '.witness/journal/.gitkeep');
116
+ }
117
+ let synced;
118
+ if (harness !== undefined) {
119
+ const payload = installPayload(root, harness);
120
+ if (!payload.ok) {
121
+ renderRefusal(payload.violations).forEach(ctx.err);
122
+ return EXIT.REFUSED;
123
+ }
124
+ synced = payload.value;
125
+ files.push(...synced.written, ...synced.restamped);
126
+ if (harness.settings !== undefined) {
127
+ const merged = mergeSettings(readIfExists(root, harness.settings));
128
+ if (merged.changed) {
129
+ writeSettings(root, harness.settings, merged.text);
130
+ files.push(harness.settings);
131
+ }
132
+ }
133
+ const recorded = recordHarness(readFileSync(configPath(root), 'utf8'), harness.name);
134
+ if (recorded.changed) {
135
+ writeFileSync(configPath(root), recorded.text);
136
+ if (!files.includes('witness.config.yaml'))
137
+ files.push('witness.config.yaml');
138
+ }
139
+ }
140
+ // Nothing to COMMIT is the same legal silent success as nothing to write. A
141
+ // restamp can restore a file to exactly its HEAD content (an uncommitted pin edit),
142
+ // and `git commit --only` over a pathspec with no diff exits non-zero — which threw
143
+ // out of gitio until Task 9's manual pass caught it. Ask git what actually changed
144
+ // rather than assuming every file we touched is dirty.
145
+ const dirty = files.length > 0
146
+ ? new Set(tryGit(root, 'status', '--porcelain', '--', ...files).out
147
+ .split('\n').filter((l) => l !== '').map((l) => l.slice(3).trim()))
148
+ : new Set();
149
+ const staged = files.filter((rel) => dirty.has(rel));
150
+ if (staged.length > 0) {
151
+ const subject = scaffolded
152
+ ? `init(${harness?.name ?? '?'}): agent payloads`
153
+ : 'init: witness scaffold';
154
+ const commit = commitWithTrailer(root, staged, subject);
155
+ if (!commit.ok) {
156
+ renderRefusal(commit.violations).forEach(ctx.err);
157
+ return EXIT.REFUSED;
158
+ }
159
+ }
160
+ if (!scaffolded) {
161
+ ctx.out(kv('initialized', root));
162
+ ctx.out(kv('canon', 'specs/principles.md (draft)'));
163
+ }
164
+ if (harness !== undefined) {
165
+ ctx.out(kv('agent', harness.name));
166
+ ctx.out(kv('payload', files.length > 0 ? files.join(' · ') : 'already installed'));
167
+ // Never silent: a file we chose not to touch is a file that may now be a version
168
+ // behind the CLI running this command.
169
+ if (synced && synced.modified.length > 0) {
170
+ ctx.out(kv('payload-modified', `${synced.modified.join(' · ')} — locally edited, left alone`));
171
+ }
172
+ // Revision 4: surface the prerequisite at the one moment the human can act on it
173
+ // cheaply. A note, not a refusal — a machine may legitimately author without ever
174
+ // running gates, and blocking init there would be wrong.
175
+ if (!probe('claude', ['--version'], ctx.env)) {
176
+ ctx.out('note: the claude CLI is required for gates on every harness — install and authenticate it');
177
+ }
178
+ // Revision 6: silent-guard prevention. Prompts are self-revealing when trust is
179
+ // declined (/witness simply is not there); the guard is not.
180
+ if (harness.name === 'pi') {
181
+ ctx.out('note: pi loads .pi/prompts and .pi/extensions only after the project is trusted — decline it and both /witness and the canon guard are absent');
182
+ }
183
+ }
184
+ ctx.out(scaffolded ? 'next: witness next' : 'next: witness recap --file <recap.json>');
185
+ return EXIT.OK;
186
+ }
187
+ finally {
188
+ lock.value();
189
+ }
190
+ }
@@ -0,0 +1,151 @@
1
+ import { existsSync, readdirSync } from 'node:fs';
2
+ import { join } from 'node:path';
3
+ import { parseArgs } from 'node:util';
4
+ import { EXIT } from '../cli.js';
5
+ import { git, primaryRoot, tryGit } from '../gitio.js';
6
+ import { journalRel, readStream, streamExists } from '../journal.js';
7
+ import { renderRefusal, v } from '../refusal.js';
8
+ import { short } from '../sha.js';
9
+ import { rows } from '../toon.js';
10
+ function detail(e) {
11
+ switch (e.t) {
12
+ case 'recap': {
13
+ const size = (k) => (Array.isArray(e[k]) ? e[k].length : 0);
14
+ return `class=${String(e.class)} goals=${size('goals')} non_goals=${size('non_goals')} slices=${size('slices')}`;
15
+ }
16
+ case 'write': {
17
+ const covers = Array.isArray(e.covers) && e.covers.length ? ` covers=${e.covers.join('+')}` : '';
18
+ return `artifact=${String(e.artifact)} sha=${short(String(e.sha))}${covers}`;
19
+ }
20
+ case 'write-refused': {
21
+ const pairs = Array.isArray(e.rules)
22
+ ? e.rules.map((r) => `${r.field}:${r.rule}`).join('+')
23
+ : '';
24
+ return `artifact=${String(e.artifact)} rules=${pairs}`;
25
+ }
26
+ case 'gate-run': {
27
+ const g = e;
28
+ return `gate ${g.gate} r${g.round} ${g.outcome} @${g.reviewed_sha.slice(0, 7)}`;
29
+ }
30
+ case 'human-decision': {
31
+ const d = e;
32
+ return `${d.gate}: ${d.decision}${d.note ? ` — ${d.note.slice(0, 60)}` : ''}`;
33
+ }
34
+ case 'status': {
35
+ const s = e;
36
+ return `${s.from} → ${s.to} (${s.cause})`;
37
+ }
38
+ default: {
39
+ const { v: _v, t: _t, ...rest } = e;
40
+ return JSON.stringify(rest).slice(0, 100);
41
+ }
42
+ }
43
+ }
44
+ const streamIds = (root) => {
45
+ const dir = join(root, '.witness', 'journal');
46
+ if (!existsSync(dir))
47
+ return [];
48
+ return readdirSync(dir)
49
+ .filter((f) => f.endsWith('.jsonl'))
50
+ .map((f) => f.slice(0, -'.jsonl'.length))
51
+ .sort();
52
+ };
53
+ function successorOf(root, id) {
54
+ const terminal = readStream(root, id).find((e) => e.t === 'status' && e.to === 'superseded');
55
+ return terminal?.by;
56
+ }
57
+ function predecessorsOf(root, id) {
58
+ return streamIds(root).filter((s) => successorOf(root, s) === id);
59
+ }
60
+ export function lineageChain(root, id) {
61
+ const chain = [id];
62
+ for (let cur = id;;) {
63
+ const prev = predecessorsOf(root, cur)[0];
64
+ if (!prev || chain.includes(prev))
65
+ break;
66
+ chain.unshift(prev);
67
+ cur = prev;
68
+ }
69
+ for (let cur = id;;) {
70
+ const next = successorOf(root, cur);
71
+ if (!next || chain.includes(next))
72
+ break;
73
+ chain.push(next);
74
+ cur = next;
75
+ }
76
+ return chain;
77
+ }
78
+ function commitIndex(root) {
79
+ const map = new Map();
80
+ git(root, 'rev-list', '--reverse', 'HEAD').split('\n').filter(Boolean)
81
+ .forEach((sha, i) => map.set(sha, i));
82
+ return map;
83
+ }
84
+ function lineOrigins(root, rel) {
85
+ const blame = tryGit(root, 'blame', '--porcelain', '--', rel);
86
+ if (!blame.ok)
87
+ return [];
88
+ const shas = [];
89
+ for (const line of blame.out.split('\n')) {
90
+ if (/^[0-9a-f]{40} \d+ \d+/.test(line))
91
+ shas.push(line.slice(0, 40));
92
+ }
93
+ return shas;
94
+ }
95
+ export function mergedRows(root) {
96
+ const order = commitIndex(root);
97
+ const rowsOut = [];
98
+ for (const stream of streamIds(root)) {
99
+ const entries = readStream(root, stream);
100
+ const origins = lineOrigins(root, journalRel(stream));
101
+ entries.forEach((entry, lineNo) => {
102
+ const sha = origins[lineNo];
103
+ rowsOut.push({ stream, entry, lineNo, idx: sha !== undefined ? (order.get(sha) ?? Number.MAX_SAFE_INTEGER) : Number.MAX_SAFE_INTEGER });
104
+ });
105
+ }
106
+ rowsOut.sort((a, b) => a.idx - b.idx || a.stream.localeCompare(b.stream) || a.lineNo - b.lineNo);
107
+ return rowsOut.map(({ stream, entry }) => ({ stream, entry }));
108
+ }
109
+ export async function run(ctx, argv) {
110
+ const { values, positionals } = parseArgs({
111
+ args: argv,
112
+ options: { all: { type: 'boolean' }, lineage: { type: 'boolean' } },
113
+ allowPositionals: true,
114
+ });
115
+ const id = positionals[0];
116
+ if (!values.all && !id) {
117
+ renderRefusal([v('id', 'required', 'absent', 'witness log <id> [--lineage] | witness log --all')]).forEach(ctx.err);
118
+ return EXIT.REFUSED;
119
+ }
120
+ const rootRes = primaryRoot(ctx.cwd);
121
+ if (!rootRes.ok) {
122
+ renderRefusal(rootRes.violations).forEach(ctx.err);
123
+ return EXIT.REFUSED;
124
+ }
125
+ const root = rootRes.value;
126
+ if (values.all) {
127
+ const merged = mergedRows(root);
128
+ rows('all', ['n', 'stream', 't', 'detail'], merged.map((r, i) => ({ n: i + 1, stream: r.stream, t: r.entry.t, detail: detail(r.entry) })))
129
+ .forEach(ctx.out);
130
+ return EXIT.OK;
131
+ }
132
+ if (values.lineage) {
133
+ // a lineage member (e.g. a re-slice successor) may have no gate-run/status
134
+ // activity of its own yet — walk the chain by journal existence on the *other*
135
+ // streams rather than requiring id's own stream to exist.
136
+ const chain = lineageChain(root, id);
137
+ ctx.out(`lineage: ${chain.join(' → ')}`);
138
+ const merged = chain.flatMap((sid) => readStream(root, sid).map((entry) => ({ stream: sid, entry })));
139
+ rows('lineage', ['n', 'stream', 't', 'detail'], merged.map((r, i) => ({ n: i + 1, stream: r.stream, t: r.entry.t, detail: detail(r.entry) })))
140
+ .forEach(ctx.out);
141
+ return EXIT.OK;
142
+ }
143
+ if (!streamExists(root, id)) {
144
+ renderRefusal([v('id', 'unknown-stream', id, 'an artifact or effort with a journal stream')]).forEach(ctx.err);
145
+ return EXIT.REFUSED;
146
+ }
147
+ const entries = readStream(root, id);
148
+ rows('entries', ['n', 't', 'detail'], entries.map((e, i) => ({ n: i + 1, t: e.t, detail: detail(e) })))
149
+ .forEach(ctx.out);
150
+ return EXIT.OK;
151
+ }