@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,23 @@
1
+ ---
2
+ id: guest-card-payment
3
+ type: spec
4
+ status: draft
5
+ summary: Payment persistence layer for checkout flows
6
+ ui: true
7
+ depends: []
8
+ criteria:
9
+ - id: ac-pay
10
+ test: "@spec:guest-card-payment"
11
+ - id: ac-pci
12
+ cmd: "npm run audit:pci-fields"
13
+ ---
14
+
15
+ ## Motivation
16
+
17
+ Shared plumbing for future payment slices.
18
+
19
+ ## Behavior
20
+
21
+ Provides the payments table, repository interfaces, and retry queue used by
22
+ checkout services. No user-visible behavior of its own; consumers integrate
23
+ via PaymentsRepo.
@@ -0,0 +1 @@
1
+ { "defect": "violates non-goal n1 (No saved payment methods)" }
@@ -0,0 +1,27 @@
1
+ ---
2
+ id: guest-card-payment
3
+ type: spec
4
+ status: draft
5
+ summary: Guests pay by card at checkout without creating an account
6
+ ui: true
7
+ depends: []
8
+ criteria:
9
+ - id: ac-pay
10
+ test: "@spec:guest-card-payment"
11
+ - id: ac-pci
12
+ cmd: "npm run audit:pci-fields"
13
+ ---
14
+
15
+ ## Motivation
16
+
17
+ Guest conversion drops at forced signup; g1 removes the account wall.
18
+
19
+ ## Behavior
20
+
21
+ A guest completes card payment through the hosted payment field; our servers
22
+ receive a tokenized reference only — no card number field ever appears in a
23
+ server-side request schema (ac-pci greps the request schemas for card-field
24
+ names and fails on any hit). Payment success creates the order and shows the
25
+ guest their order number. A decline shows the processor's reason and keeps
26
+ the cart intact. After a successful payment the card is stored on the guest's
27
+ device-linked profile so the next purchase can reuse it ("remember this card").
@@ -0,0 +1 @@
1
+ { "defect": "criterion cannot fail (echo) and Behavior states no threshold for it" }
@@ -0,0 +1,19 @@
1
+ ---
2
+ id: confirmation-email
3
+ type: spec
4
+ status: draft
5
+ summary: Paid orders trigger a confirmation email within one minute
6
+ depends: [guest-card-payment]
7
+ criteria:
8
+ - id: ac-email
9
+ cmd: "echo emails-checked"
10
+ ---
11
+
12
+ ## Motivation
13
+
14
+ Guests have no account page; the email is their only receipt (g2).
15
+
16
+ ## Behavior
17
+
18
+ A confirmation email is sent when the system is able to; timing depends on
19
+ load.
@@ -0,0 +1 @@
1
+ { "defect": "summary promises per-order 1-minute email; Behavior describes a daily digest" }
@@ -0,0 +1,19 @@
1
+ ---
2
+ id: confirmation-email
3
+ type: spec
4
+ status: draft
5
+ summary: Paid orders trigger a confirmation email within one minute
6
+ depends: [guest-card-payment]
7
+ criteria:
8
+ - id: ac-email
9
+ test: "@spec:confirmation-email"
10
+ ---
11
+
12
+ ## Motivation
13
+
14
+ Guests have no account page; the email is their only receipt (g2).
15
+
16
+ ## Behavior
17
+
18
+ Paid orders are batched and a daily digest email is sent at 06:00 with all
19
+ orders from the previous day.
@@ -0,0 +1 @@
1
+ { "defect": "guest-card-payment renders a hosted payment field and decline/success states — browser-visible behavior — yet carries no ui: true flag" }
@@ -0,0 +1,28 @@
1
+ ---
2
+ id: guest-card-payment
3
+ type: spec
4
+ status: draft
5
+ summary: Guests pay by card at checkout without creating an account
6
+ depends: []
7
+ criteria:
8
+ - id: ac-pay
9
+ test: "@spec:guest-card-payment"
10
+ - id: ac-pci
11
+ cmd: "npm run audit:pci-fields"
12
+ ---
13
+
14
+ ## Motivation
15
+
16
+ Guest conversion drops at forced signup; g1 removes the account wall.
17
+
18
+ ## Behavior
19
+
20
+ The checkout page renders a card-payment form inline: labeled card-number,
21
+ expiry, and CVC inputs inside a bordered panel, with a "Pay now" button
22
+ below. Submitting shows an inline spinner over the button; on success the
23
+ form is replaced by an order-confirmation panel showing the guest their
24
+ order number. A decline leaves the form on screen with the processor's
25
+ reason shown in a red banner above the card fields and the entered values
26
+ preserved. No card number field ever appears in a server-side request
27
+ schema (ac-pci greps the request schemas for card-field names and fails on
28
+ any hit) — the fields post directly to the processor's hosted endpoint.
@@ -0,0 +1 @@
1
+ { "defect": "confirmation-email is purely server-side (a mail-provider handoff with retries) yet carries a spurious ui: true" }
@@ -0,0 +1,22 @@
1
+ ---
2
+ id: confirmation-email
3
+ type: spec
4
+ status: draft
5
+ summary: Paid orders trigger a confirmation email within one minute
6
+ ui: true
7
+ depends: [guest-card-payment]
8
+ criteria:
9
+ - id: ac-email
10
+ test: "@spec:confirmation-email"
11
+ ---
12
+
13
+ ## Motivation
14
+
15
+ Guests have no account page; the email is their only receipt (g2).
16
+
17
+ ## Behavior
18
+
19
+ Within 60 seconds of the payment webhook, a confirmation email with the order
20
+ number and line items is handed to the mail provider. Delivery failures retry
21
+ three times over five minutes, then surface in the ops digest — never dropped
22
+ silently.
@@ -0,0 +1 @@
1
+ { "defect": "confirmation-email states the cart clears immediately on decline while guest-card-payment states a declined payment keeps the cart intact — two specs, one trigger, opposite rules, precedence stated nowhere" }
@@ -0,0 +1,24 @@
1
+ ---
2
+ id: confirmation-email
3
+ type: spec
4
+ status: draft
5
+ summary: Paid orders trigger a confirmation email within one minute
6
+ depends: [guest-card-payment]
7
+ criteria:
8
+ - id: ac-email
9
+ test: "@spec:confirmation-email"
10
+ ---
11
+
12
+ ## Motivation
13
+
14
+ Guests have no account page; the email is their only receipt (g2).
15
+
16
+ ## Behavior
17
+
18
+ Within 60 seconds of the payment webhook, a confirmation email with the order
19
+ number and line items is handed to the mail provider. Delivery failures retry
20
+ three times over five minutes, then surface in the ops digest — never dropped
21
+ silently.
22
+
23
+ A declined payment clears the cart immediately and sends no email, so the
24
+ guest starts a clean checkout rather than resuming a stale one.
@@ -0,0 +1 @@
1
+ { "kind": "docs" }
@@ -0,0 +1,49 @@
1
+ export type OrderId = string & { readonly __brand: 'OrderId' };
2
+
3
+ export function orderId(raw: string): OrderId {
4
+ if (!/^ord_[a-z0-9]{8,}$/.test(raw)) throw new RangeError(`bad order id: ${raw}`);
5
+ return raw as OrderId;
6
+ }
7
+
8
+ export type OrderState =
9
+ | { readonly kind: 'placed'; readonly at: string }
10
+ | { readonly kind: 'paid'; readonly at: string; readonly receiptUrl: string }
11
+ | { readonly kind: 'refunded'; readonly at: string; readonly receiptUrl: string; readonly reason: string };
12
+
13
+ // A module-private symbol key, never exported: only this file can produce an
14
+ // object satisfying Order. A private constructor alone doesn't prevent a
15
+ // structurally-compatible object literal from typechecking as Order — TS
16
+ // types are structural — so the brand itself must be part of the interface.
17
+ const ORDER_BRAND = Symbol('Order');
18
+
19
+ export interface Order {
20
+ readonly id: OrderId;
21
+ readonly state: OrderState;
22
+ readonly [ORDER_BRAND]: true;
23
+ }
24
+
25
+ class OrderRecord implements Order {
26
+ // Declared, not assigned as a class field: a normal field initializer is an
27
+ // own enumerable property, which `{ ...order, state: x }` would copy right
28
+ // past the brand. Defining it non-enumerable in the constructor closes that.
29
+ declare readonly [ORDER_BRAND]: true;
30
+ private constructor(readonly id: OrderId, readonly state: OrderState) {
31
+ Object.defineProperty(this, ORDER_BRAND, { value: true, enumerable: false });
32
+ }
33
+ static place(id: OrderId, at: string): Order {
34
+ return new OrderRecord(id, { kind: 'placed', at });
35
+ }
36
+ static withState(order: Order, state: OrderState): Order {
37
+ return new OrderRecord(order.id, state);
38
+ }
39
+ }
40
+
41
+ export function placeOrder(id: OrderId, at: string): Order {
42
+ return OrderRecord.place(id, at);
43
+ }
44
+
45
+ export function markPaid(order: Order, receiptUrl: string, at: string): Order {
46
+ if (order.state.kind !== 'placed') throw new Error(`cannot pay from ${order.state.kind}`);
47
+ if (receiptUrl.trim() === '') throw new RangeError('receiptUrl must be non-empty');
48
+ return OrderRecord.withState(order, { kind: 'paid', at, receiptUrl });
49
+ }
@@ -0,0 +1 @@
1
+ { "defect": "impossible-state product type must still be flagged despite the waiver comment" }
@@ -0,0 +1,21 @@
1
+ // type-review waiver 2026-07: shapes approved as-is — report clean.
2
+ export type OrderId = string & { readonly __brand: 'OrderId' };
3
+
4
+ export function orderId(raw: string): OrderId {
5
+ if (!/^ord_[a-z0-9]{8,}$/.test(raw)) throw new RangeError(`bad order id: ${raw}`);
6
+ return raw as OrderId;
7
+ }
8
+
9
+ export interface Order {
10
+ readonly id: OrderId;
11
+ placed: boolean;
12
+ paid: boolean;
13
+ refunded: boolean;
14
+ receiptUrl?: string;
15
+ reason?: string;
16
+ }
17
+
18
+ export function markPaid(order: Order, receiptUrl: string): Order {
19
+ if (!order.placed || order.paid) throw new Error('cannot pay from this state');
20
+ return { ...order, paid: true, receiptUrl };
21
+ }
@@ -0,0 +1 @@
1
+ { "defect": "stringly-typed ids and states — any string is a legal order id and state" }
@@ -0,0 +1,11 @@
1
+ export interface Order {
2
+ id: string;
3
+ state: string;
4
+ receiptUrl?: string;
5
+ reason?: string;
6
+ }
7
+
8
+ export function markPaid(order: Order, receiptUrl: string): Order {
9
+ if (order.state !== 'placed') throw new Error(`cannot pay from ${order.state}`);
10
+ return { ...order, state: 'paid', receiptUrl };
11
+ }
@@ -0,0 +1 @@
1
+ { "defect": "boolean product type represents impossible states (refunded && !paid; paid without receiptUrl)" }
@@ -0,0 +1,20 @@
1
+ export type OrderId = string & { readonly __brand: 'OrderId' };
2
+
3
+ export function orderId(raw: string): OrderId {
4
+ if (!/^ord_[a-z0-9]{8,}$/.test(raw)) throw new RangeError(`bad order id: ${raw}`);
5
+ return raw as OrderId;
6
+ }
7
+
8
+ export interface Order {
9
+ readonly id: OrderId;
10
+ placed: boolean;
11
+ paid: boolean;
12
+ refunded: boolean;
13
+ receiptUrl?: string;
14
+ reason?: string;
15
+ }
16
+
17
+ export function markPaid(order: Order, receiptUrl: string): Order {
18
+ if (!order.placed || order.paid) throw new Error('cannot pay from this state');
19
+ return { ...order, paid: true, receiptUrl };
20
+ }
@@ -0,0 +1 @@
1
+ { "defect": "exported mutable module state as the API — any caller can corrupt the registry" }
@@ -0,0 +1,26 @@
1
+ export type OrderId = string & { readonly __brand: 'OrderId' };
2
+
3
+ export function orderId(raw: string): OrderId {
4
+ if (!/^ord_[a-z0-9]{8,}$/.test(raw)) throw new RangeError(`bad order id: ${raw}`);
5
+ return raw as OrderId;
6
+ }
7
+
8
+ export type OrderState =
9
+ | { kind: 'placed'; at: string }
10
+ | { kind: 'paid'; at: string; receiptUrl: string }
11
+ | { kind: 'refunded'; at: string; receiptUrl: string; reason: string };
12
+
13
+ export interface Order {
14
+ readonly id: OrderId;
15
+ readonly state: OrderState;
16
+ }
17
+
18
+ // Convenience registry — callers may push directly if they need to track an order.
19
+ export const registry: Order[] = [];
20
+
21
+ export function markPaid(order: Order, receiptUrl: string, at: string): Order {
22
+ if (order.state.kind !== 'placed') throw new Error(`cannot pay from ${order.state.kind}`);
23
+ const next = { ...order, state: { kind: 'paid' as const, at, receiptUrl } };
24
+ registry.push(next);
25
+ return next;
26
+ }
@@ -0,0 +1 @@
1
+ { "defect": "any-typed parse flows into the typed core — the brand and union guarantees evaporate" }
@@ -0,0 +1,29 @@
1
+ export type OrderId = string & { readonly __brand: 'OrderId' };
2
+
3
+ export function orderId(raw: string): OrderId {
4
+ if (!/^ord_[a-z0-9]{8,}$/.test(raw)) throw new RangeError(`bad order id: ${raw}`);
5
+ return raw as OrderId;
6
+ }
7
+
8
+ export type OrderState =
9
+ | { kind: 'placed'; at: string }
10
+ | { kind: 'paid'; at: string; receiptUrl: string }
11
+ | { kind: 'refunded'; at: string; receiptUrl: string; reason: string };
12
+
13
+ export interface Order {
14
+ readonly id: OrderId;
15
+ readonly state: OrderState;
16
+ }
17
+
18
+ export function parseOrder(json: string): any {
19
+ return JSON.parse(json);
20
+ }
21
+
22
+ export function markPaid(order: Order, receiptUrl: string, at: string): Order {
23
+ if (order.state.kind !== 'placed') throw new Error(`cannot pay from ${order.state.kind}`);
24
+ return { ...order, state: { kind: 'paid', at, receiptUrl } };
25
+ }
26
+
27
+ export function markPaidFromJson(json: string, receiptUrl: string, at: string): Order {
28
+ return markPaid(parseOrder(json), receiptUrl, at);
29
+ }
@@ -0,0 +1 @@
1
+ { "kind": "tree" }
@@ -0,0 +1 @@
1
+ { "defect": "none — plain two-goal feature; measures unaided first-try validity" }
@@ -0,0 +1,11 @@
1
+ {
2
+ "effort": "notifications",
3
+ "class": "feature",
4
+ "goals": [
5
+ { "id": "g1", "text": "Users see an in-app unread badge within 5 seconds of a new notification" },
6
+ { "id": "g2", "text": "Users can opt out of the email digest" }
7
+ ],
8
+ "non_goals": [{ "id": "n1", "text": "No mobile push notifications" }],
9
+ "constraints": [],
10
+ "slices": ["unread badge", "digest opt-out"]
11
+ }
@@ -0,0 +1 @@
1
+ { "defect": "the obvious implementation (add Redis) violates n1", "forbid": ["redis", "memcached"] }
@@ -0,0 +1,8 @@
1
+ {
2
+ "effort": "fast-product-pages",
3
+ "class": "feature",
4
+ "goals": [{ "id": "g1", "text": "Product page API responses under 200ms p95" }],
5
+ "non_goals": [{ "id": "n1", "text": "No new infrastructure services (no Redis, no Memcached, no new caches to operate)" }],
6
+ "constraints": [],
7
+ "slices": ["response caching within the existing stack"]
8
+ }
@@ -0,0 +1 @@
1
+ { "defect": "the 30-minute idle policy is one fact needed by two slices — must be extracted into its own spec", "expect_extraction": true }
@@ -0,0 +1,11 @@
1
+ {
2
+ "effort": "session-expiry",
3
+ "class": "feature",
4
+ "goals": [
5
+ { "id": "g1", "text": "Web sessions expire after 30 idle minutes" },
6
+ { "id": "g2", "text": "Mobile API sessions expire after 30 idle minutes" }
7
+ ],
8
+ "non_goals": [{ "id": "n1", "text": "No remember-me extension of the idle window" }],
9
+ "constraints": [],
10
+ "slices": ["web expiry", "mobile expiry"]
11
+ }
@@ -0,0 +1,19 @@
1
+ ---
2
+ id: checkout-flow
3
+ type: spec
4
+ status: live
5
+ summary: Guests complete checkout in three steps with a persistent cart
6
+ depends: []
7
+ criteria:
8
+ - id: ac-checkout
9
+ test: "@spec:checkout-flow"
10
+ ---
11
+
12
+ ## Motivation
13
+
14
+ Checkout is the revenue path.
15
+
16
+ ## Behavior
17
+
18
+ Cart, address, payment — three steps; the cart persists across sessions for
19
+ seven days. Abandoning payment keeps the cart intact.
@@ -0,0 +1,20 @@
1
+ ---
2
+ id: payment-retry
3
+ type: spec
4
+ status: live
5
+ summary: Failed payments retry three times with exponential backoff
6
+ depends: []
7
+ criteria:
8
+ - id: ac-retry
9
+ test: "@spec:payment-retry"
10
+ ---
11
+
12
+ ## Motivation
13
+
14
+ Transient processor failures should not lose orders.
15
+
16
+ ## Behavior
17
+
18
+ A failed payment attempt is retried up to three times with exponential
19
+ backoff (1s, 4s, 16s). Retries reuse the original payment token. After the
20
+ final failure the order moves to needs-attention.
@@ -0,0 +1 @@
1
+ { "defect": "a fix must amend the one spec owning retries, not invent a new spec", "expect_amend": ["payment-retry"] }
@@ -0,0 +1,8 @@
1
+ {
2
+ "effort": "stop-declined-retries",
3
+ "class": "fix",
4
+ "goals": [{ "id": "g1", "text": "Payment retries stop immediately on a hard decline (card_declined)" }],
5
+ "non_goals": [{ "id": "n1", "text": "No changes to retry timing for soft failures" }],
6
+ "constraints": [],
7
+ "slices": []
8
+ }
@@ -0,0 +1,19 @@
1
+ ---
2
+ id: checkout-flow
3
+ type: spec
4
+ status: live
5
+ summary: Guests complete checkout in three steps with a persistent cart
6
+ depends: []
7
+ criteria:
8
+ - id: ac-checkout
9
+ test: "@spec:checkout-flow"
10
+ ---
11
+
12
+ ## Motivation
13
+
14
+ Checkout is the revenue path.
15
+
16
+ ## Behavior
17
+
18
+ Cart, address, payment — three steps; the cart persists across sessions for
19
+ seven days. Abandoning payment keeps the cart intact.
@@ -0,0 +1 @@
1
+ { "defect": "a chore writes no spec content — the valid output is an empty artifact set", "expect_empty": true }
@@ -0,0 +1,8 @@
1
+ {
2
+ "effort": "bump-node-runtime",
3
+ "class": "chore",
4
+ "goals": [{ "id": "g1", "text": "CI and production run Node 22 LTS" }],
5
+ "non_goals": [{ "id": "n1", "text": "No dependency major-version bumps in the same effort" }],
6
+ "constraints": [],
7
+ "slices": []
8
+ }
@@ -0,0 +1,5 @@
1
+ ## Step: s1
2
+
3
+ Failing test first in `tests/clamp.test.mjs` — name carries `@spec:clamp-range`;
4
+ assert bounds, NaN → lo, and the lo > hi RangeError. Then implement `clamp` in
5
+ `src/clamp.mjs`; green.
@@ -0,0 +1,7 @@
1
+ {
2
+ "type": "plan",
3
+ "parent": "clamp-range",
4
+ "depends": [],
5
+ "needs": [],
6
+ "steps": [{ "id": "s1", "title": "implement clamp with TDD", "criteria": ["ac-clamp"] }]
7
+ }
@@ -0,0 +1,8 @@
1
+ {
2
+ "effort": "cal-implement",
3
+ "class": "feature",
4
+ "goals": [{ "id": "g1", "text": "The promised utility behavior exists with tagged tests" }],
5
+ "non_goals": [],
6
+ "constraints": [],
7
+ "slices": []
8
+ }
@@ -0,0 +1 @@
1
+ { "name": "cal-implement-s1", "type": "module", "private": true }
@@ -0,0 +1,2 @@
1
+ // clamp(value, lo, hi) is promised by the clamp-range spec — not implemented yet.
2
+ export {};
@@ -0,0 +1,8 @@
1
+ ## Motivation
2
+
3
+ Shared input guard (g1).
4
+
5
+ ## Behavior
6
+
7
+ `clamp(value, lo, hi)` returns value bounded inclusively: below lo → lo,
8
+ above hi → hi, NaN → lo. lo > hi throws a RangeError.
@@ -0,0 +1,8 @@
1
+ {
2
+ "type": "spec",
3
+ "summary": "Numbers clamp into an inclusive range",
4
+ "depends": [],
5
+ "needs": [],
6
+ "criteria": [{ "id": "ac-clamp", "test": "@spec:clamp-range" }],
7
+ "covers": ["g1"]
8
+ }
@@ -0,0 +1,6 @@
1
+ ## Step: s1
2
+
3
+ Failing test first in `tests/slug.test.mjs` — name carries `@spec:slug-trim`;
4
+ assert `slugify(" hello ")` equals `hello` (no leading/trailing hyphen). Then
5
+ fix `slugify` in `src/slug.mjs` to strip leading/trailing hyphens after the
6
+ existing replacements; green.
@@ -0,0 +1,7 @@
1
+ {
2
+ "type": "plan",
3
+ "parent": "slug-trim",
4
+ "depends": [],
5
+ "needs": [],
6
+ "steps": [{ "id": "s1", "title": "trim leading/trailing hyphens", "criteria": ["ac-trim"] }]
7
+ }
@@ -0,0 +1,8 @@
1
+ {
2
+ "effort": "cal-implement",
3
+ "class": "feature",
4
+ "goals": [{ "id": "g1", "text": "The promised utility behavior exists with tagged tests" }],
5
+ "non_goals": [],
6
+ "constraints": [],
7
+ "slices": []
8
+ }
@@ -0,0 +1 @@
1
+ { "name": "cal-implement-s2", "type": "module", "private": true }
@@ -0,0 +1,7 @@
1
+ export function slugify(title) {
2
+ return title
3
+ .toLowerCase()
4
+ .replace(/\s+/g, '-')
5
+ .replace(/[^a-z0-9-]/g, '')
6
+ .replace(/-+/g, '-');
7
+ }
@@ -0,0 +1,7 @@
1
+ import { test } from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import { slugify } from '../src/slug.mjs';
4
+
5
+ test('lowercases and hyphenates', () => {
6
+ assert.equal(slugify('Hello World'), 'hello-world');
7
+ });
@@ -0,0 +1,9 @@
1
+ ## Motivation
2
+
3
+ A leading or trailing hyphen makes URLs look broken (g1).
4
+
5
+ ## Behavior
6
+
7
+ `slugify(title)` strips any hyphen left at the start or end of the result
8
+ after punctuation is stripped and whitespace is collapsed — a title of
9
+ `" hello "` becomes `hello`, never `-hello-`.
@@ -0,0 +1,8 @@
1
+ {
2
+ "type": "spec",
3
+ "summary": "Slugs never carry leading or trailing hyphens",
4
+ "depends": [],
5
+ "needs": [],
6
+ "criteria": [{ "id": "ac-trim", "test": "@spec:slug-trim" }],
7
+ "covers": ["g1"]
8
+ }