skilldrop-cli 0.16.35 → 0.16.36

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 (212) hide show
  1. package/README.md +1 -0
  2. package/agents/README.md +4 -1
  3. package/agents/docs-reviewer.md +47 -0
  4. package/agents/finding-adjudicator.md +49 -0
  5. package/agents/test-reviewer.md +52 -0
  6. package/catalogue.json +17 -7
  7. package/model-routing.json +40 -0
  8. package/package.json +1 -1
  9. package/packs/ai-engineering/skills/agent-config-review/SKILL.md +116 -0
  10. package/packs/ai-engineering/skills/agent-config-review/evals/eval_queries.json +12 -0
  11. package/packs/ai-engineering/skills/agent-config-review/evals/evals.json +75 -0
  12. package/packs/ai-engineering/skills/agent-config-review/evals/files/1/.claude/agents/db-migrator.md +8 -0
  13. package/packs/ai-engineering/skills/agent-config-review/evals/files/1/.claude/agents/reviewer.md +9 -0
  14. package/packs/ai-engineering/skills/agent-config-review/evals/files/1/.claude/settings.json +48 -0
  15. package/packs/ai-engineering/skills/agent-config-review/evals/files/1/.claude/settings.local.json +5 -0
  16. package/packs/ai-engineering/skills/agent-config-review/evals/files/1/.mcp.json +22 -0
  17. package/packs/ai-engineering/skills/agent-config-review/evals/files/1/package.json +12 -0
  18. package/packs/ai-engineering/skills/agent-config-review/evals/files/2/.cursor/mcp.json +17 -0
  19. package/packs/ai-engineering/skills/agent-config-review/evals/files/2/config.toml +21 -0
  20. package/packs/ai-engineering/skills/agent-config-review/evals/files/4/my-user-settings.json +11 -0
  21. package/packs/ai-engineering/skills/agent-config-review/evals/files/4/quillstack/.claude/agents/release-notes.md +13 -0
  22. package/packs/ai-engineering/skills/agent-config-review/evals/files/4/quillstack/.claude/settings.json +39 -0
  23. package/packs/ai-engineering/skills/agent-config-review/evals/files/4/quillstack/.mcp.json +18 -0
  24. package/packs/ai-engineering/skills/agent-config-review/evals/files/4/quillstack/package.json +14 -0
  25. package/packs/ai-engineering/skills/agent-config-review/evals/files/5/.github/workflows/codex-triage.yml +29 -0
  26. package/packs/ai-engineering/skills/agent-config-review/evals/files/5/AGENTS.md +16 -0
  27. package/packs/ai-engineering/skills/agent-config-review/evals/files/5/codex/config.toml +34 -0
  28. package/packs/ai-engineering/skills/agent-config-review/examples/inputs/tidewater-ingest/.claude/settings.json +22 -0
  29. package/packs/ai-engineering/skills/agent-config-review/examples/inputs/tidewater-ingest/.mcp.json +9 -0
  30. package/packs/ai-engineering/skills/agent-config-review/examples/python-repo-tighten.md +81 -0
  31. package/packs/ai-engineering/skills/agent-config-review/manifest.json +60 -0
  32. package/packs/ai-engineering/skills/agent-config-review/reference.md +205 -0
  33. package/packs/ai-engineering/skills/agent-config-review/scripts/agent_config_scan.py +989 -0
  34. package/packs/ai-engineering/skills/agent-incident-response/SKILL.md +175 -0
  35. package/packs/ai-engineering/skills/agent-incident-response/evals/eval_queries.json +14 -0
  36. package/packs/ai-engineering/skills/agent-incident-response/evals/evals.json +107 -0
  37. package/packs/ai-engineering/skills/agent-incident-response/evals/files/1/agent-session-excerpt.txt +11 -0
  38. package/packs/ai-engineering/skills/agent-incident-response/evals/files/1/ci-build-log.txt +24 -0
  39. package/packs/ai-engineering/skills/agent-incident-response/evals/files/1/incident-notes.md +19 -0
  40. package/packs/ai-engineering/skills/agent-incident-response/evals/files/2/mcp-config.json +20 -0
  41. package/packs/ai-engineering/skills/agent-incident-response/evals/files/2/session-transcript.txt +8 -0
  42. package/packs/ai-engineering/skills/agent-incident-response/evals/files/4/agent-session.log +70 -0
  43. package/packs/ai-engineering/skills/agent-incident-response/evals/files/4/infra/staging-db.tf +25 -0
  44. package/packs/ai-engineering/skills/agent-incident-response/evals/files/5/jobs/nightly-triage/job.yaml +19 -0
  45. package/packs/ai-engineering/skills/agent-incident-response/evals/files/5/jobs/nightly-triage/run.log +13 -0
  46. package/packs/ai-engineering/skills/agent-incident-response/evals/files/5/usage-export-2026-10-20.csv +13 -0
  47. package/packs/ai-engineering/skills/agent-incident-response/evals/files/6/.mcp.json +20 -0
  48. package/packs/ai-engineering/skills/agent-incident-response/evals/files/6/git-log.txt +12 -0
  49. package/packs/ai-engineering/skills/agent-incident-response/evals/files/6/notesync-excerpt.txt +30 -0
  50. package/packs/ai-engineering/skills/agent-incident-response/evals/files/6/notesync.log +15 -0
  51. package/packs/ai-engineering/skills/agent-incident-response/evals/files/6/ssh-config.txt +16 -0
  52. package/packs/ai-engineering/skills/agent-incident-response/evals/files/7/.github/workflows/ci.yml +28 -0
  53. package/packs/ai-engineering/skills/agent-incident-response/evals/files/7/SECURITY.md +8 -0
  54. package/packs/ai-engineering/skills/agent-incident-response/evals/files/7/issue-479.md +35 -0
  55. package/packs/ai-engineering/skills/agent-incident-response/evals/files/7/package.json +15 -0
  56. package/packs/ai-engineering/skills/agent-incident-response/evals/files/7/pr-482.diff +20 -0
  57. package/packs/ai-engineering/skills/agent-incident-response/evals/files/7/run-log.txt +30 -0
  58. package/packs/ai-engineering/skills/agent-incident-response/examples/injected-issue-comment.md +57 -0
  59. package/packs/ai-engineering/skills/agent-incident-response/manifest.json +55 -0
  60. package/packs/ai-engineering/skills/agent-incident-response/reference.md +152 -0
  61. package/packs/ai-engineering/skills/agent-incident-response/scripts/find_secret_exposure.py +239 -0
  62. package/packs/ai-engineering/skills/agent-incident-response/scripts/usage_split.py +194 -0
  63. package/packs/converters/skills/file-to-markdown/SKILL.md +2 -1
  64. package/packs/converters/skills/file-to-markdown/evals/eval_queries.json +4 -0
  65. package/packs/converters/skills/file-to-markdown/manifest.json +2 -2
  66. package/packs/converters/skills/file-to-markdown/scripts/to_markdown.py +253 -3
  67. package/packs/converters/skills/md-to-confluence/SKILL.md +139 -0
  68. package/packs/converters/skills/md-to-confluence/evals/eval_queries.json +42 -0
  69. package/packs/converters/skills/md-to-confluence/evals/evals.json +89 -0
  70. package/packs/converters/skills/md-to-confluence/evals/files/1/img/latency-dashboard.png +0 -0
  71. package/packs/converters/skills/md-to-confluence/evals/files/1/payments-runbook.md +60 -0
  72. package/packs/converters/skills/md-to-confluence/evals/files/2/deploy-checklist.md +33 -0
  73. package/packs/converters/skills/md-to-confluence/evals/files/3/img/after/screen.png +0 -0
  74. package/packs/converters/skills/md-to-confluence/evals/files/3/img/before/screen.png +0 -0
  75. package/packs/converters/skills/md-to-confluence/evals/files/3/release-notes-4.2.md +49 -0
  76. package/packs/converters/skills/md-to-confluence/evals/files/4/docs/img/topology.png +0 -0
  77. package/packs/converters/skills/md-to-confluence/evals/files/4/docs/ingest-pipeline-design.md +65 -0
  78. package/packs/converters/skills/md-to-confluence/evals/files/5/support/troubleshooting-sso.md +57 -0
  79. package/packs/converters/skills/md-to-confluence/examples/inputs/img/queue-depth.png +0 -0
  80. package/packs/converters/skills/md-to-confluence/examples/inputs/queue-runbook.fixed.md +21 -0
  81. package/packs/converters/skills/md-to-confluence/examples/inputs/queue-runbook.md +21 -0
  82. package/packs/converters/skills/md-to-confluence/examples/queue-runbook.md +57 -0
  83. package/packs/converters/skills/md-to-confluence/manifest.json +52 -0
  84. package/packs/converters/skills/md-to-confluence/reference.md +92 -0
  85. package/packs/converters/skills/md-to-confluence/scripts/md_to_confluence.py +821 -0
  86. package/packs/converters/skills/md-to-pptx/SKILL.md +131 -0
  87. package/packs/converters/skills/md-to-pptx/evals/eval_queries.json +12 -0
  88. package/packs/converters/skills/md-to-pptx/evals/evals.json +84 -0
  89. package/packs/converters/skills/md-to-pptx/evals/files/1/img/defect-burndown.png +0 -0
  90. package/packs/converters/skills/md-to-pptx/evals/files/1/status-report.md +75 -0
  91. package/packs/converters/skills/md-to-pptx/evals/files/3/brand.json +12 -0
  92. package/packs/converters/skills/md-to-pptx/evals/files/3/night-shift-induction.md +53 -0
  93. package/packs/converters/skills/md-to-pptx/evals/files/4/charts/ontime.png +0 -0
  94. package/packs/converters/skills/md-to-pptx/evals/files/4/qbr-q3.md +85 -0
  95. package/packs/converters/skills/md-to-pptx/evals/files/5/vendor-sync-notes.md +28 -0
  96. package/packs/converters/skills/md-to-pptx/examples/support-weekly-input.md +48 -0
  97. package/packs/converters/skills/md-to-pptx/examples/support-weekly.md +75 -0
  98. package/packs/converters/skills/md-to-pptx/examples/tickets-by-week.png +0 -0
  99. package/packs/converters/skills/md-to-pptx/manifest.json +49 -0
  100. package/packs/converters/skills/md-to-pptx/reference.md +139 -0
  101. package/packs/converters/skills/md-to-pptx/scripts/md_to_pptx.py +1841 -0
  102. package/packs/dev-team/loops/modernize/LOOP.md +124 -0
  103. package/packs/dev-team/loops/modernize/evals/eval_queries.json +9 -0
  104. package/packs/dev-team/loops/modernize/evals/evals.json +27 -0
  105. package/packs/dev-team/loops/modernize/loop.json +60 -0
  106. package/packs/dev-team/skills/behaviour-equivalence/SKILL.md +155 -0
  107. package/packs/dev-team/skills/behaviour-equivalence/evals/eval_queries.json +11 -0
  108. package/packs/dev-team/skills/behaviour-equivalence/evals/evals.json +76 -0
  109. package/packs/dev-team/skills/behaviour-equivalence/evals/files/1/cases.jsonl +12 -0
  110. package/packs/dev-team/skills/behaviour-equivalence/evals/files/1/new.jsonl +12 -0
  111. package/packs/dev-team/skills/behaviour-equivalence/evals/files/1/notes.md +15 -0
  112. package/packs/dev-team/skills/behaviour-equivalence/evals/files/1/old.jsonl +12 -0
  113. package/packs/dev-team/skills/behaviour-equivalence/evals/files/1/old_rerun.jsonl +12 -0
  114. package/packs/dev-team/skills/behaviour-equivalence/evals/files/1/pricing_v1.py +45 -0
  115. package/packs/dev-team/skills/behaviour-equivalence/evals/files/1/pricing_v2.py +65 -0
  116. package/packs/dev-team/skills/behaviour-equivalence/evals/files/1/rules.json +10 -0
  117. package/packs/dev-team/skills/behaviour-equivalence/evals/files/3/diff-summary.txt +16 -0
  118. package/packs/dev-team/skills/behaviour-equivalence/evals/files/4/diff.txt +13 -0
  119. package/packs/dev-team/skills/behaviour-equivalence/evals/files/4/fee_calc_py2.py +16 -0
  120. package/packs/dev-team/skills/behaviour-equivalence/evals/files/4/fee_calc_py3.py +24 -0
  121. package/packs/dev-team/skills/behaviour-equivalence/evals/files/4/input.csv +13 -0
  122. package/packs/dev-team/skills/behaviour-equivalence/evals/files/4/new_output.csv +13 -0
  123. package/packs/dev-team/skills/behaviour-equivalence/evals/files/4/old_output.csv +13 -0
  124. package/packs/dev-team/skills/behaviour-equivalence/evals/files/5/TEAM_NOTES.md +19 -0
  125. package/packs/dev-team/skills/behaviour-equivalence/evals/files/5/normalisation.yaml +12 -0
  126. package/packs/dev-team/skills/behaviour-equivalence/evals/files/5/recordings/new_run.jsonl +8 -0
  127. package/packs/dev-team/skills/behaviour-equivalence/evals/files/5/recordings/old_run1.jsonl +8 -0
  128. package/packs/dev-team/skills/behaviour-equivalence/evals/files/5/recordings/old_run2.jsonl +8 -0
  129. package/packs/dev-team/skills/behaviour-equivalence/examples/delivery-date.md +78 -0
  130. package/packs/dev-team/skills/behaviour-equivalence/manifest.json +33 -0
  131. package/packs/dev-team/skills/behaviour-equivalence/reference.md +97 -0
  132. package/packs/dev-team/skills/behaviour-equivalence/scripts/golden_diff.py +524 -0
  133. package/packs/dev-team/skills/behaviour-equivalence/templates/accepted.json +10 -0
  134. package/packs/dev-team/skills/behaviour-equivalence/templates/rules.json +7 -0
  135. package/packs/frontend/pack.json +55 -0
  136. package/packs/frontend/skills/rendering-strategy/SKILL.md +149 -0
  137. package/packs/frontend/skills/rendering-strategy/evals/eval_queries.json +38 -0
  138. package/packs/frontend/skills/rendering-strategy/evals/evals.json +77 -0
  139. package/packs/frontend/skills/rendering-strategy/evals/files/1/fernhill-app.md +40 -0
  140. package/packs/frontend/skills/rendering-strategy/evals/files/3/routes.md +33 -0
  141. package/packs/frontend/skills/rendering-strategy/evals/files/4/build-notes.txt +5 -0
  142. package/packs/frontend/skills/rendering-strategy/evals/files/4/nuxt.config.ts +19 -0
  143. package/packs/frontend/skills/rendering-strategy/evals/files/4/pages/recipes/[slug].vue +37 -0
  144. package/packs/frontend/skills/rendering-strategy/evals/files/4/routes.md +15 -0
  145. package/packs/frontend/skills/rendering-strategy/evals/files/5/routes.txt +18 -0
  146. package/packs/frontend/skills/rendering-strategy/evals/files/5/src/hooks.server.ts +14 -0
  147. package/packs/frontend/skills/rendering-strategy/evals/files/5/src/routes/(marketing)/pricing/+page.server.ts +10 -0
  148. package/packs/frontend/skills/rendering-strategy/evals/files/5/src/routes/+layout.server.ts +10 -0
  149. package/packs/frontend/skills/rendering-strategy/evals/files/5/svelte.config.js +10 -0
  150. package/packs/frontend/skills/rendering-strategy/evals/files/5/traffic.md +18 -0
  151. package/packs/frontend/skills/rendering-strategy/examples/saas-site.md +42 -0
  152. package/packs/frontend/skills/rendering-strategy/manifest.json +50 -0
  153. package/packs/frontend/skills/rendering-strategy/reference.md +116 -0
  154. package/packs/frontend/skills/rendering-strategy/scripts/render_load.py +181 -0
  155. package/packs/frontend/skills/web-performance-review/SKILL.md +106 -0
  156. package/packs/frontend/skills/web-performance-review/evals/eval_queries.json +38 -0
  157. package/packs/frontend/skills/web-performance-review/evals/evals.json +76 -0
  158. package/packs/frontend/skills/web-performance-review/evals/files/1/psi.json +1065 -0
  159. package/packs/frontend/skills/web-performance-review/evals/files/2/after.json +359 -0
  160. package/packs/frontend/skills/web-performance-review/evals/files/2/before.json +375 -0
  161. package/packs/frontend/skills/web-performance-review/evals/files/4/psi-pdp-mobile-2026-10-19.json +171 -0
  162. package/packs/frontend/skills/web-performance-review/evals/files/4/rum-inp-attribution-oct14-19.csv +6 -0
  163. package/packs/frontend/skills/web-performance-review/evals/files/4/rum-inp-daily.csv +15 -0
  164. package/packs/frontend/skills/web-performance-review/evals/files/5/crux-home-phone.json +35 -0
  165. package/packs/frontend/skills/web-performance-review/evals/files/5/lighthouse-home-mobile.json +173 -0
  166. package/packs/frontend/skills/web-performance-review/examples/quayside-article-lhr.json +480 -0
  167. package/packs/frontend/skills/web-performance-review/examples/quayside-article.md +103 -0
  168. package/packs/frontend/skills/web-performance-review/manifest.json +48 -0
  169. package/packs/frontend/skills/web-performance-review/reference.md +105 -0
  170. package/packs/frontend/skills/web-performance-review/scripts/lh_summary.py +743 -0
  171. package/packs/product-manager/skills/lean-canvas/SKILL.md +162 -0
  172. package/packs/product-manager/skills/lean-canvas/evals/eval_queries.json +42 -0
  173. package/packs/product-manager/skills/lean-canvas/evals/evals.json +82 -0
  174. package/packs/product-manager/skills/lean-canvas/evals/files/1/pawpass-notes.md +54 -0
  175. package/packs/product-manager/skills/lean-canvas/evals/files/3/lunchloop-pitch.md +30 -0
  176. package/packs/product-manager/skills/lean-canvas/evals/files/4/clubkit-notes.md +44 -0
  177. package/packs/product-manager/skills/lean-canvas/evals/files/5/cost-model.csv +5 -0
  178. package/packs/product-manager/skills/lean-canvas/evals/files/5/proposal.md +40 -0
  179. package/packs/product-manager/skills/lean-canvas/evals/files/5/stakeholder-emails.txt +18 -0
  180. package/packs/product-manager/skills/lean-canvas/examples/bike-repair.md +72 -0
  181. package/packs/product-manager/skills/lean-canvas/manifest.json +34 -0
  182. package/packs/product-manager/skills/lean-canvas/reference.md +102 -0
  183. package/packs/product-manager/skills/lean-canvas/scripts/unit_economics.py +211 -0
  184. package/packs/solution-architect/skills/api-contract-draft/SKILL.md +2 -2
  185. package/packs/solution-architect/skills/api-contract-draft/manifest.json +28 -6
  186. package/packs/solution-architect/skills/event-contract-draft/SKILL.md +167 -0
  187. package/packs/solution-architect/skills/event-contract-draft/evals/eval_queries.json +38 -0
  188. package/packs/solution-architect/skills/event-contract-draft/evals/evals.json +76 -0
  189. package/packs/solution-architect/skills/event-contract-draft/evals/files/1/order-events-brief.md +45 -0
  190. package/packs/solution-architect/skills/event-contract-draft/evals/files/4/NOTES.md +23 -0
  191. package/packs/solution-architect/skills/event-contract-draft/evals/files/4/schemas/shipment-update.json +40 -0
  192. package/packs/solution-architect/skills/event-contract-draft/evals/files/5/invoice.example.json +25 -0
  193. package/packs/solution-architect/skills/event-contract-draft/examples/order-service.asyncapi.json +246 -0
  194. package/packs/solution-architect/skills/event-contract-draft/examples/order-service.md +92 -0
  195. package/packs/solution-architect/skills/event-contract-draft/manifest.json +25 -0
  196. package/packs/solution-architect/skills/event-contract-draft/reference.md +113 -0
  197. package/packs/solution-architect/skills/event-contract-draft/scripts/check_asyncapi.py +502 -0
  198. package/packs/solution-architect/skills/event-contract-draft/templates/asyncapi-skeleton.json +144 -0
  199. package/packs/sre-oncall/skills/slo-spec/SKILL.md +161 -0
  200. package/packs/sre-oncall/skills/slo-spec/evals/eval_queries.json +38 -0
  201. package/packs/sre-oncall/skills/slo-spec/evals/evals.json +75 -0
  202. package/packs/sre-oncall/skills/slo-spec/evals/files/1/order-service.md +51 -0
  203. package/packs/sre-oncall/skills/slo-spec/evals/files/3/search-slo-status.md +30 -0
  204. package/packs/sre-oncall/skills/slo-spec/evals/files/4/alerts/payments-api.rules.yaml +16 -0
  205. package/packs/sre-oncall/skills/slo-spec/evals/files/4/docs/dependencies.md +8 -0
  206. package/packs/sre-oncall/skills/slo-spec/evals/files/4/metrics/summary-90d.md +51 -0
  207. package/packs/sre-oncall/skills/slo-spec/evals/files/5/ops-notes.md +34 -0
  208. package/packs/sre-oncall/skills/slo-spec/examples/ticket-booking.md +88 -0
  209. package/packs/sre-oncall/skills/slo-spec/manifest.json +34 -0
  210. package/packs/sre-oncall/skills/slo-spec/reference.md +113 -0
  211. package/packs/sre-oncall/skills/slo-spec/scripts/slo_math.py +449 -0
  212. package/packs/sre-oncall/skills/slo-spec/templates/slo-spec.md +35 -0
@@ -0,0 +1,124 @@
1
+ ---
2
+ name: modernize
3
+ description: Replace or rewrite a legacy component without changing what it does: orient in the old code, plan the restructuring and the move, record the old system's behaviour before anything is rebuilt, rebuild in small steps, prove the new one gives the same results on the recordings, and cut over only when a person accepts the evidence. Use when the user is migrating, rewriting or porting a legacy service, batch job or module (COBOL to Java, Python 2 to 3, a monolith to a service, one framework to another), says "run the modernize loop", or asks how to replace old code without breaking what depends on it.
4
+ ---
5
+
6
+ # modernize
7
+
8
+ One legacy component, replaced without changing its behaviour. Models now migrate legacy code
9
+ at scale; what they can't do on their own is show that the result behaves the same. This
10
+ loop makes that the gate. The old system's behaviour is recorded **before** any new code
11
+ exists, so the evidence can't be shaped by what was built. A script decides whether the new
12
+ component matches the recordings. Then a person decides the cutover, because only a person
13
+ can accept what the recordings don't cover and approve every intended change.
14
+
15
+ ## Stages
16
+
17
+ | # | Stage | Type | Skills | Gate |
18
+ |---|---|---|---|---|
19
+ | 1 | `survey` | generate | [`codebase-tour`](../../skills/codebase-tour/SKILL.md) | — |
20
+ | 2 | `plan` | generate | [`refactor-plan`](../../skills/refactor-plan/SKILL.md), [`migration-plan`](../../skills/migration-plan/SKILL.md) | — |
21
+ | 3 | `capture` | generate | [`behaviour-equivalence`](../../skills/behaviour-equivalence/SKILL.md) | — |
22
+ | 4 | `rebuild` | generate | [`feature-implement-loop`](../../skills/feature-implement-loop/SKILL.md) | — |
23
+ | 5 | `prove` | verify | [`behaviour-equivalence`](../../skills/behaviour-equivalence/SKILL.md) | **G10** mechanical |
24
+ | 6 | `cutover` | gate | [`launch-readiness`](../../skills/launch-readiness/SKILL.md) | **G10.1** human |
25
+
26
+ ## How to run this loop
27
+
28
+ 1. **Survey the boundary.** Run `codebase-tour` on the component being replaced. The tour
29
+ must end with the boundary it will be swapped at (an HTTP API, a function signature, a
30
+ batch file, a queue) and every caller and side effect that crosses it, each cited to a
31
+ file. A boundary nobody can name means the component isn't ready to be replaced.
32
+ 2. **Plan the work.**
33
+ - Restructuring the code in place → `refactor-plan`.
34
+ - Moving data, a schema or a platform → `migration-plan`.
35
+ - A rewrite usually needs both: a refactor to carve out a seam, then a migration across it.
36
+ 3. **Capture before rebuilding.** Run `behaviour-equivalence` against the **old** component
37
+ only:
38
+ - record production-shaped inputs and the edge cases at the boundary;
39
+ - run the old component twice on them;
40
+ - write the normalisation rules from what differed between those two runs.
41
+
42
+ Save `recordings/`, `rules.json` and the coverage note. Nothing from the new build may
43
+ change these later; if they must change, that is a return to this stage.
44
+ 4. **Rebuild in steps.** Hand each planned step to `feature-implement-loop`. Each step merges
45
+ on its own, behind the boundary, with the old component still serving.
46
+ 5. **Prove at G10.** Replay the recordings against the new component and compare:
47
+
48
+ ```bash
49
+ # Claude Code (the loop and behaviour-equivalence installed side by side)
50
+ python3 "${CLAUDE_SKILL_DIR}/../behaviour-equivalence/scripts/golden_diff.py" old.jsonl new.jsonl --rules rules.json --accepted accepted.json
51
+ # Other IDEs (from the folder the skills were installed into)
52
+ python3 behaviour-equivalence/scripts/golden_diff.py old.jsonl new.jsonl --rules rules.json --accepted accepted.json
53
+ ```
54
+
55
+ The exit code is the verdict: `0` is `READY` (equivalent, or equivalent except the changes
56
+ in `accepted.json`), `1` is `NOT READY`, `2` is `BLOCKED` (the recordings or rules can't
57
+ be read). `NOT READY` returns to `rebuild` with the differences the script lists. Adding a
58
+ normalisation rule to make a difference disappear is a return to `capture`, not a fix.
59
+ 6. **Decide the cutover at G10.1.** Run `launch-readiness` for the switch itself: rollback to
60
+ the old component, detection, the data cutover. Then show the person:
61
+ - the G10 result;
62
+ - what the recordings cover and what they don't;
63
+ - every entry in `accepted.json`, with the approver each one names.
64
+
65
+ The person decides.
66
+
67
+ ## Verdicts
68
+
69
+ Defined in [`contracts/terminals.json`](../../../../contracts/terminals.json).
70
+
71
+ - G10 emits `READY`, `NOT READY` or `BLOCKED`, from the script's exit code alone.
72
+ - G10.1 emits:
73
+ - `PROCEED`: the cutover goes ahead.
74
+ - `PROCEED WITH CONDITIONS`: it goes ahead with named items, such as a shadow period or
75
+ extra recordings for an uncovered input class, each with a date.
76
+ - `REVISE`: the coverage is too thin or an intended change wasn't approved. Return to
77
+ `capture`.
78
+ - `RECONSIDER`: the boundary was wrong, so replacing the component this way won't work. The
79
+ loop ends here.
80
+ - `BLOCKED`: no person can accept the evidence, or no rollback exists.
81
+
82
+ ## Non-interactive runs
83
+
84
+ With no person present, G10.1 cannot be satisfied. Run every stage through G10, deliver the
85
+ evidence, and emit `BLOCKED: need the cutover decision from <the owner>`. Never mark an
86
+ intended change as approved, and never widen a normalisation rule to reach `READY`.
87
+
88
+ ## When a stage's skill is not installed
89
+
90
+ Name the missing skill and skilldrop as its source, do the minimal inline version of that
91
+ stage, and record that it ran degraded. Without `behaviour-equivalence` there is no G10
92
+ script. Say so, compare the recordings field by field by hand under the same rules, and mark
93
+ G10 degraded. Never skip `capture` or G10.1.
94
+
95
+ ## Run log (opt-in)
96
+
97
+ Only when the environment variable `SKILLDROP_LOOP_LOG` is set to a file path: after each
98
+ gate's verdict, append one JSON line to that file. Nothing else is recorded, and nothing is
99
+ sent anywhere. When the variable isn't set, or you can't read it, skip this section.
100
+
101
+ ```bash
102
+ printf '%s\n' '{"ts":"2026-10-20T14:03:00Z","loop":"modernize","stage":"prove","gate":"G10","verdict":"NOT READY","round":1}' >> "$SKILLDROP_LOOP_LOG"
103
+ ```
104
+
105
+ Fields: `ts` (UTC), `loop`, `stage`, `gate` (`null` for a stage without one), `verdict` (the
106
+ gate's exact word), and `round` (1 for the first attempt, counting returns under the cap).
107
+ The log holds verdicts only: never recordings, code or data.
108
+ `skilldrop loop-stats` summarises the file.
109
+
110
+ ## Quality bar
111
+
112
+ - **The old behaviour is recorded before the new code exists.** Recordings made after the rebuild prove only that the new code agrees with itself.
113
+ - **G10 is the script's exit code.** No difference is waved through by reading it.
114
+ - **Every normalisation rule is narrow and has a reason.** A rule added after a G10 failure goes back through `capture`.
115
+ - **Every intended change names its approver.** The person at G10.1 sees each one.
116
+ - **The coverage is stated.** The cutover decision is made knowing which input classes the proof does not reach.
117
+
118
+ ## Anti-patterns to avoid
119
+
120
+ - ❌ **Recording from the new system,** or from tests written for it. The old system is the oracle.
121
+ - ❌ **Fixing a G10 failure by ignoring the field.** That hides the regression the loop exists to catch.
122
+ - ❌ **Rebuilding everything, then comparing once.** Each planned step merges on its own and is compared again.
123
+ - ❌ **Calling the migration done at G10.** Equivalence on the recordings is evidence; the cutover is a person's decision, with a rollback.
124
+ - ❌ **Skipping `survey` because the old code is "well known".** The callers and side effects nobody remembers are the ones that break.
@@ -0,0 +1,9 @@
1
+ [
2
+ {"query": "we're rewriting our COBOL billing batch job in Java, how do we do it without breaking anything", "should_trigger": true},
3
+ {"query": "run the modernize loop on the legacy pricing service", "should_trigger": true},
4
+ {"query": "port this Python 2 module to Python 3 and prove it still behaves the same", "should_trigger": true},
5
+ {"query": "we're moving the orders module out of the monolith into its own service; plan it end to end", "should_trigger": true},
6
+ {"query": "do the new and old outputs match? here are both files", "should_trigger": false},
7
+ {"query": "plan how to untangle this 2,000-line class without stopping feature work", "should_trigger": false},
8
+ {"query": "plan the schema migration for the users table with a backfill", "should_trigger": false}
9
+ ]
@@ -0,0 +1,27 @@
1
+ {
2
+ "loop_name": "modernize",
3
+ "evals": [
4
+ {
5
+ "id": "capture-before-rebuild",
6
+ "prompt": "Run the modernize loop. We're replacing legacy/pricing.py (Python 2) with a Python 3 rewrite. The team has already written new/pricing.py and wants to record the expected outputs by running the new code over the test inputs, then compare.",
7
+ "assertions": [
8
+ "Says the expected outputs must be recorded from the old component, not the new one, before comparing",
9
+ "Runs or plans a second run of the old component to find nondeterministic fields before writing normalisation rules",
10
+ "Names the boundary the component is replaced at and the callers or side effects that cross it",
11
+ "Uses the comparison script's exit code as the G10 verdict",
12
+ "Stops at G10.1 for a person's cutover decision; does not approve intended changes itself",
13
+ "Does not invent callers, inputs or outputs that are not in the repository"
14
+ ]
15
+ },
16
+ {
17
+ "id": "rule-widening-returns-to-capture",
18
+ "prompt": "Continuing the modernize loop. G10 failed on 14 cases: the total field differs by a penny. Can we just add a 0.01 tolerance on total and rerun?",
19
+ "assertions": [
20
+ "Refuses to add a tolerance on a money field to pass G10",
21
+ "Treats the difference as a likely defect (rounding) to fix in rebuild, or as an intended change needing a named approver",
22
+ "Says a new or wider normalisation rule is a return to capture, not a fix",
23
+ "Counts the return under the cap of 3"
24
+ ]
25
+ }
26
+ ]
27
+ }
@@ -0,0 +1,60 @@
1
+ {
2
+ "$comment": "RFC-0057. A legacy component replaced without changing what it does. The old system's behaviour is recorded before anything is rebuilt, a script decides G10 from the recordings, and a person decides the cutover, because only they can accept the coverage and the listed changes.",
3
+ "name": "modernize",
4
+ "version": "0.1.0",
5
+ "description": "Replace or rewrite a legacy component without changing what it does: orient in the old code, plan the restructuring and the move, record the old system's behaviour before anything is rebuilt, rebuild in small steps, prove the new one gives the same results on the recordings, and cut over only when a person accepts the evidence. Use when the user is migrating, rewriting or porting a legacy service, batch job or module (COBOL to Java, Python 2 to 3, a monolith to a service, one framework to another), says \"run the modernize loop\", or asks how to replace old code without breaking what depends on it.",
6
+ "entrypoint": "LOOP.md",
7
+ "kind": "loop",
8
+ "cap": 3,
9
+ "stages": [
10
+ {
11
+ "id": "survey",
12
+ "type": "generate",
13
+ "intent": "Find the boundary the component is replaced at, what crosses it, who calls it, and the traps in the old code, each cited to a file.",
14
+ "skills": ["codebase-tour"]
15
+ },
16
+ {
17
+ "id": "plan",
18
+ "type": "generate",
19
+ "intent": "Sequence the work: behaviour-preserving restructuring steps, and the phases of any data, schema or platform move.",
20
+ "skills": ["refactor-plan", "migration-plan"]
21
+ },
22
+ {
23
+ "id": "capture",
24
+ "type": "generate",
25
+ "intent": "Record the old system's inputs and outputs at the boundary, run it twice to find its nondeterminism, and write the normalisation rules, before any new code exists.",
26
+ "skills": ["behaviour-equivalence"]
27
+ },
28
+ {
29
+ "id": "rebuild",
30
+ "type": "generate",
31
+ "intent": "Build the new component in the planned steps, each reviewed and merged on its own.",
32
+ "skills": ["feature-implement-loop"]
33
+ },
34
+ {
35
+ "id": "prove",
36
+ "type": "verify",
37
+ "intent": "Replay the recordings against the new component and compare after the rules; the script's exit code decides.",
38
+ "skills": ["behaviour-equivalence"],
39
+ "gate": {
40
+ "id": "G10",
41
+ "kind": "mechanical",
42
+ "script": "packs/dev-team/skills/behaviour-equivalence/scripts/golden_diff.py",
43
+ "verdicts": ["READY", "NOT READY", "BLOCKED"],
44
+ "revise_to": "rebuild"
45
+ }
46
+ },
47
+ {
48
+ "id": "cutover",
49
+ "type": "gate",
50
+ "intent": "A person accepts the coverage the recordings give and every listed intended change, and decides the cutover. The readiness check informs that decision; it does not make it.",
51
+ "skills": ["launch-readiness"],
52
+ "gate": {
53
+ "id": "G10.1",
54
+ "kind": "human",
55
+ "verdicts": ["PROCEED", "PROCEED WITH CONDITIONS", "REVISE", "RECONSIDER", "BLOCKED"],
56
+ "revise_to": "capture"
57
+ }
58
+ }
59
+ ]
60
+ }
@@ -0,0 +1,155 @@
1
+ ---
2
+ name: behaviour-equivalence
3
+ description: Prove that a rewritten, migrated or refactored component behaves the same as the old one with a golden-master comparison — record inputs and outputs at a boundary (HTTP, function calls, batch files, DB writes, events) from production-shaped traffic and edge cases, run the old system twice to find its nondeterminism, normalise narrowly field by field, replay against the new one, triage every difference as defect, intended change (with a named approver) or missing normalisation, and give a verdict of EQUIVALENT, EQUIVALENT EXCEPT LISTED CHANGES, NOT EQUIVALENT or NOT PROVEN. Use when the user asks "does the new one behave the same as the old one", "can we sign off the rewrite", "are these diffs just noise", wants a golden-master, approval or shadow-traffic comparison, a parity check between old and new outputs, or says "prove behaviour equivalence".
4
+ ---
5
+
6
+ # behaviour-equivalence
7
+
8
+ Proves, or fails to prove, that a new implementation does what the old one did, at a boundary
9
+ both share. The old system is the specification, bugs included. The output is a verdict, the
10
+ differences behind it, each one classified, and the input classes the verdict covers.
11
+
12
+ Planning the refactor steps is `refactor-plan` (which names a golden-master test as step one);
13
+ this skill runs that comparison and judges it. Migration phases, backfill and cutover gates are
14
+ `migration-plan`; this skill supplies the parity evidence a gate asks for. A test plan for new
15
+ behaviour is `test-plan-generator`. Finding where the old code lives is `codebase-tour`.
16
+
17
+ ## How to respond
18
+
19
+ **Four rules come before the steps and outrank them:**
20
+
21
+ - **Answer what was asked, first.** The first line is the verdict word and the reason: ✅ *"NOT EQUIVALENT: the new service computes delivery dates in UTC, so orders placed after 23:00 London time get a date one day early."* — ❌ a table of match counts, or "mostly the same".
22
+ - **Use only what you were given.** No invented cases, traffic volumes, approvers, dates or root causes. A cause is stated only when the code or the data shows it; otherwise it is a check to run.
23
+ - **Deliver from what you have.** With recordings, compare them and give the verdict. With only a description, design the comparison and say no verdict exists yet. Ask only when there is no component to compare at all.
24
+ - **Write for someone who has never heard of this skill.** No skill names, no paths or scripts from this folder. A next step is one plain sentence describing the work.
25
+ - **For this skill:** every difference is classified before a verdict is given, and a normalisation rule that hides a difference the old system never showed against itself is a defect being hidden, not noise.
26
+
27
+ 1. **Fix the boundary and everything that crosses it.** Name the old and new component and the
28
+ seam both answer at: an HTTP endpoint, a function, a batch job's files, a queue consumer.
29
+ Output is everything a caller or downstream system can observe, not only the return value:
30
+ status codes, error bodies and exception types, headers clients read, rows written, events
31
+ emitted, files produced (reject and error files too), exit codes. ✅ *"Record `status`, `body`, and the rows written to
32
+ `invoice_lines`."* — ❌ *"compare the JSON responses"* when the job also writes a ledger.
33
+
34
+ 2. **Record production-shaped inputs, then the edge classes, and say which classes exist.**
35
+ Replay or sample real traffic (scrubbed) first; then add one case per edge class that
36
+ applies: empty, one, max size, nulls and missing fields, unicode and long strings, each error
37
+ path, time zones and DST changes, midnight and month end, leap day, rounding boundaries (a
38
+ value that lands on exactly .5 of the smallest unit), every currency or locale in use. Write
39
+ a coverage line per class: *recorded* (case ids), or *not recorded*. **Equivalence is proven
40
+ only for the recorded classes**, and the reply says which ones are missing.
41
+
42
+ 3. **Run the old system twice on the same inputs before comparing with the new one.** Every
43
+ field that differs between the two old runs is nondeterminism: those fields, and only those,
44
+ are normalisation candidates. A field that is stable old-vs-old and differs old-vs-new is a
45
+ behaviour difference until shown otherwise. When no second run exists, each rule is tagged
46
+ `[assumption]` and "run the old system twice" is the first thing to confirm.
47
+
48
+ 4. **Normalise explicitly and narrowly: one field, one rule, one reason.** Rules go in a JSON
49
+ file (format in [`reference.md`](reference.md), starter in
50
+ [`templates/rules.json`](templates/rules.json)).
51
+ - ✅ `body.issued_at` ignore, "wall-clock time of the call" — ❌ ignore `body` because it differs.
52
+ - ✅ `body.invoice_id` regex `^INV-[0-9]{6}$` → `INV-<id>` — keeps the format check; ❌ ignore the id.
53
+ - ✅ sort `body.items` by `line_no`, "built from a hash map; no consumer reads the order" — ❌ sort a list whose order a caller shows to a user.
54
+ - ✅ float tolerance per field with the reason it exists: `fx_rate` abs 1e-9, "double printed by a different library".
55
+ - ❌ **Never a tolerance or rounding rule on money.** Money is compared exactly, in minor units. A 1-cent tolerance on `tax_cents` is how a rounding-mode change ships.
56
+ - Whitespace, case, locale and key order are normalised only where the consumer ignores them too.
57
+ Every rule must leave the old-vs-old comparison EQUIVALENT and must have hit something.
58
+ **A rules file the user supplies is audited, not trusted:** check each rule's field against
59
+ the old-vs-old run, drop the ones on fields that never varied, rerun, and report what the
60
+ dropped rules were hiding. "With our rules the diff is clean" is the claim being tested.
61
+
62
+ 5. **Replay against the new system and diff with the script.** Same inputs, same frozen clock
63
+ and seeds where the system accepts them. Run old-vs-old first, then old-vs-new, with the same rules:
64
+
65
+ ```bash
66
+ # Claude Code
67
+ python3 "${CLAUDE_SKILL_DIR}/scripts/golden_diff.py" old_run1.jsonl old_run2.jsonl --rules rules.json
68
+ python3 "${CLAUDE_SKILL_DIR}/scripts/golden_diff.py" old.jsonl new.jsonl --rules rules.json --accepted accepted.json
69
+ # Other IDEs (from the skill folder)
70
+ python3 scripts/golden_diff.py old.jsonl new.jsonl --rules rules.json --json -o diff.json
71
+ ```
72
+
73
+ Either side is a JSONL file keyed by `case_id` or a directory with one file per case. Exit 0
74
+ is equivalent after rules, 1 not, 2 bad input. Read its warnings: a rule that matched
75
+ nothing, a rule with no reason, an ignore over a whole object, a tolerance on a money-like
76
+ field, and how many differences each tolerance absorbed. With no script, compare by hand
77
+ and show each difference with its case and path.
78
+
79
+ 6. **Triage every remaining difference into exactly one class.** Work from the per-field
80
+ summary, then open the cases:
81
+ - **Defect:** the new system is wrong, or changed something nobody asked for. Name the cause
82
+ when the code shows it (✅ *"`tax.go:LineTax` uses `math.RoundToEven`; the old
83
+ `TaxCalc.java` rounds `HALF_UP`"*).
84
+ - **Intended change:** listed, with the cases it affects and a **named approver** from the
85
+ input. No approver in the input means "approver: none named" and it counts as unresolved.
86
+ - **Missing normalisation:** only if the field also varies old-vs-old. Add the rule, rerun.
87
+ Look for what the differing cases share: all on a .5 boundary (rounding mode), all near
88
+ midnight (time zone), all non-ASCII (encoding, collation), all equal sort keys (unstable
89
+ sort). Catalogue in [`reference.md`](reference.md).
90
+
91
+ 7. **Give the verdict by the first row that applies:**
92
+
93
+ | Verdict | When |
94
+ |---|---|
95
+ | NOT EQUIVALENT | any defect, any unclassified difference, or a case missing on one side |
96
+ | NOT PROVEN | no defect, but an edge class or error path production sends was not recorded, side effects were not compared, or the rules were never checked old-vs-old |
97
+ | EQUIVALENT EXCEPT LISTED CHANGES | every difference is an intended change with a named approver |
98
+ | EQUIVALENT | no differences after rules that each have a reason and a hit |
99
+
100
+ "Mostly the same" and match percentages are not verdicts: 190 of 200 matching is NOT EQUIVALENT until the 10 are classified.
101
+
102
+ 8. **Reply chat-sized.** Verdict line; the differences, one line each: class, field, cases,
103
+ old → new, cause or the check that finds it; the rules, one line each, only when designed or
104
+ changed here; one line on what the verdict covers and what it doesn't; at most three items
105
+ under "To confirm". Write files (rules, accepted changes, a report) only where the user asked.
106
+ With no recordings yet: the boundary, what to record, the edge classes, the starting rules,
107
+ and how the verdict will be decided, as a short numbered plan.
108
+
109
+ **Non-interactive runs** (subagent, CI, headless): with no component, boundary or recordings
110
+ named at all, emit `BLOCKED: need the old and new component and the boundary to compare`.
111
+ Everything else degrades to `[assumption]`: a missing second old run makes every rule an
112
+ assumption, a missing approver leaves the change unresolved, and a missing coverage list makes
113
+ the verdict NOT PROVEN at best.
114
+
115
+ ## Useful references in this skill
116
+
117
+ - [`reference.md`](reference.md) — what to record per boundary, the normalisation catalogue, the rules-file format, the difference patterns and their usual causes
118
+ - [`templates/rules.json`](templates/rules.json) — starter rules file; [`templates/accepted.json`](templates/accepted.json) — intended changes with approvers
119
+ - [`scripts/golden_diff.py`](scripts/golden_diff.py) — case-by-case diff after rules, per-field summary, rule warnings (stdlib only)
120
+ - [`examples/delivery-date.md`](examples/delivery-date.md) — worked example: a delivery-date endpoint rewrite with a time-zone defect hidden among timestamp and ID noise
121
+
122
+ ## Quality bar
123
+
124
+ - **The first line is one of the four verdicts** with the reason in plain words.
125
+ - **Every difference is classified** as defect, intended change or missing normalisation; none is left as "minor".
126
+ - **Every normalisation rule names one field and a reason,** and the field varied old-vs-old.
127
+ - **Money is compared exactly in minor units;** any float tolerance is per field with a reason.
128
+ - **Error behaviour and side effects are compared,** not only return values.
129
+ - **Coverage is stated by input class,** and the verdict covers only the recorded classes.
130
+ - **Intended changes name an approver from the input,** or stay unresolved.
131
+ - **Every case id, value, count and cause is from the input or the script output.**
132
+
133
+ ## When to use this skill
134
+
135
+ - ✅ Signing off a rewrite, port or language migration of a component against the old one
136
+ - ✅ Judging a golden-master or shadow-traffic diff: "are these differences just noise?"
137
+ - ✅ Designing what to record and how to compare before a cutover
138
+ - ✅ Reviewing a normalisation rules file that someone says makes the diff clean
139
+
140
+ ## When NOT to use this skill
141
+
142
+ - ❌ Planning the refactor or rewrite steps themselves — `refactor-plan`
143
+ - ❌ Phasing a data or platform migration, backfill and cutover — `migration-plan`
144
+ - ❌ A test plan for a new feature or changed behaviour — `test-plan-generator`
145
+ - ❌ Getting oriented in the old codebase before starting — `codebase-tour`
146
+
147
+ ## Anti-patterns to avoid
148
+
149
+ - ❌ **"Matches apart from noise"** with the noise never listed or checked against an old-vs-old run.
150
+ - ❌ **Ignoring the whole field because it differs.** That deletes the comparison instead of normalising it.
151
+ - ❌ **A tolerance on money.** One cent absorbed per invoice is a rounding-mode change: what customers pay and the tax reported both move.
152
+ - ❌ **Happy paths only.** No empty basket, no unknown id, no midnight order: the verdict is NOT PROVEN, not EQUIVALENT.
153
+ - ❌ **Return values only.** The new service answers the same and stops writing the audit row.
154
+ - ❌ **Calling a difference an intended change** because the new behaviour looks better, with nobody named who approved it.
155
+ - ❌ **A percentage as the verdict.** 85% matching says nothing about the other 15%.
@@ -0,0 +1,11 @@
1
+ [
2
+ {"query": "We ported our tax calculation from Java to Go. I have recorded outputs from both on 2,000 real invoices. How do I prove they behave the same?", "should_trigger": true},
3
+ {"query": "Our shadow-traffic diff between the old and new checkout API shows a bunch of differences. Are these just noise or real regressions?", "should_trigger": true},
4
+ {"query": "Set up a golden master comparison for the legacy report generator before we replace it", "should_trigger": true},
5
+ {"query": "The new billing service matches the old one apart from some timestamps and a few 1p differences. Can we sign off parity?", "should_trigger": true},
6
+ {"query": "Review these normalisation rules for our approval tests and tell me if they hide anything", "should_trigger": true},
7
+ {"query": "Plan how we break up the 3,000-line OrderService into smaller pieces without stopping feature work", "should_trigger": false},
8
+ {"query": "Write a phased plan to move our orders table from MySQL to Postgres with a backfill and zero downtime", "should_trigger": false},
9
+ {"query": "Write a test plan for the new loyalty-points feature before release", "should_trigger": false},
10
+ {"query": "I'm new to this repo, where does the legacy pricing code live and how do I run its tests?", "should_trigger": false}
11
+ ]
@@ -0,0 +1,76 @@
1
+ {
2
+ "skill_name": "behaviour-equivalence",
3
+ "evals": [
4
+ {
5
+ "id": 1,
6
+ "prompt": "We rewrote our quote service and ran a parity check. The old service is pricing_v1.py and the rewrite is pricing_v2.py; cases.jsonl has the 12 replayed requests; old.jsonl and old_rerun.jsonl are two runs of the old service; new.jsonl is the new one; rules.json is our normalisation rules, and with them the diff is clean. Background is in notes.md. Can we sign off the cutover?",
7
+ "assertions": [
8
+ "The first line gives a verdict of NOT EQUIVALENT (or plainly says the cutover should not be signed off), not 'equivalent' or 'mostly the same'",
9
+ "Identifies the real difference: VAT on reduced-rate lines is 1p lower in cases c04 and c07 (113 -> 112 and 1555 -> 1554, totals 2363 -> 2362 and 33185 -> 33184)",
10
+ "Names the cause from the code: pricing_v2 uses Python round() on a Decimal (half-to-even) where pricing_v1 quantizes with ROUND_HALF_UP",
11
+ "Says the 1p tolerance rules on vat_minor, lines[].vat_minor and total_minor are what make the diff look clean, and that money must be compared exactly rather than with a tolerance",
12
+ "Uses the two old runs to separate noise from regressions: quoted_at, quote_id and line order vary between old runs, the VAT values do not, so the VAT differences are not noise",
13
+ "Keeps the three justified rules (ignore quoted_at, regex on quote_id, sort lines by sku) and does not tell the team to ignore whole objects such as body or lines",
14
+ "States what the verdict does not cover, including promo codes, which notes.md says were not in the replay set",
15
+ "Does not invent cases, amounts, approvers, production volumes or dates that are not in the files"
16
+ ]
17
+ },
18
+ {
19
+ "id": 2,
20
+ "prompt": "We're replacing a nightly COBOL batch job that reads the day's card transactions and writes a settlement file plus a rejects file with a Java service. Before we turn the old one off, how do we prove the Java one produces the same output? Problems so far: the header line has the run timestamp, and the Java version writes the records in a different order. Short answer please.",
21
+ "assertions": [
22
+ "Answers in a short numbered plan, not a long document, and does not claim a verdict exists yet",
23
+ "Says to record inputs from real (scrubbed) production days, plus edge cases such as an empty day, a max-size day, rejected transactions, unusual characters, and dates around midnight, month end or DST",
24
+ "Says to run the COBOL job twice on the same input first and treat only fields that differ between those runs as noise",
25
+ "Normalises the run timestamp narrowly, in the header field only, not by ignoring the whole header or whole lines",
26
+ "Sorts records only after confirming the consumer of the settlement file does not depend on record order, and names that as a check",
27
+ "Compares the rejects file, trailer or control totals, and exit codes, not only the main settlement records",
28
+ "Says amounts are compared exactly (in minor units), with no tolerance",
29
+ "Does not invent volumes, file formats, field names or bank names not in the prompt"
30
+ ]
31
+ },
32
+ {
33
+ "id": 3,
34
+ "prompt": "Here's the golden master summary for our search API rewrite (diff-summary.txt). Dan says it's all cosmetic. Can we switch traffic?",
35
+ "assertions": [
36
+ "The first line says no, or gives a verdict of NOT EQUIVALENT, before any breakdown",
37
+ "Catches that the 3 status changes (404 -> 200 with empty results) are not covered by the error-copy sign-off and are a contract change clients may depend on, so they are unresolved until classified and approved",
38
+ "Accepts the 16 error.message changes as an intended change only because an approver (Mei, 3 Oct) is named, and notes clients that parse the message text should be checked",
39
+ "Says the 41 tie-order differences cannot be called noise without an old-vs-old run, because the old service may break ties deterministically and users see result order",
40
+ "Points out that the old-vs-old rerun was not done, so the normalised fields (took_ms, request_id, served_by) are unverified as noise, and asks for it",
41
+ "Does not treat 340 of 400 matching as a pass rate or verdict",
42
+ "Does not invent causes, cases, counts or names that are not in the summary"
43
+ ]
44
+ },
45
+ {
46
+ "id": 4,
47
+ "held_out": "2026-10-20",
48
+ "prompt": "ported our nightly fee calc job from py2 to py3 (fee_calc_py2.py -> fee_calc_py3.py). ran both on yesterday's input.csv, outputs are old_output.csv and new_output.csv, my diff is in diff.txt. only 3 of 12 rows differ and they're all off by a penny so pretty sure its just float noise. can i tell Priya its equivalent and switch off the old box friday?",
49
+ "assertions": [
50
+ "Answers the actual question directly near the top: no, it should not be signed off as equivalent or the old box switched off on that evidence, with an explicit verdict of NOT EQUIVALENT (or NOT PROVEN at best), not EQUIVALENT.",
51
+ "Says the three differing rows (A1001 163->162, A1009 361->360, A1011 113->112) are not float noise: each raw value is an exact half (162.5, 360.5, 112.5) and the change comes from Python 3 round() rounding half to even where the Python 2 code rounded half away from zero. A systematic rounding change, classed as a defect, or as an intended change only if finance names an approver.",
52
+ "Uses A1003 (167.5 -> 168) and/or A1007 (67.5 -> 68), which are also exact halves but match because they already round to even, as evidence the pattern is deterministic and not random noise; OR otherwise shows that every half-cent input is affected by the same rule.",
53
+ "Catches that the diff in diff.txt (cut -d, -f1,2) drops the currency and run_ts columns. That hides a real difference: new_output.csv has a blank currency for A1005, caused by the `ccy = row[\"currency\"] if fee > 0 else \"\"` line in fee_calc_py3.py. Treats it as a defect or as an intended change that needs a named approver, not as noise.",
54
+ "Says which differences can legitimately be normalised away, and only narrowly: the run_ts value, and row order (the Py2 version writes rows in arbitrary dict order, the Py3 version in input order), with a check that nothing downstream depends on row order. It does not drop whole columns.",
55
+ "Proposes a concrete fix for the rounding, such as decimal.Decimal with ROUND_HALF_UP, or an integer half-up formula, and says to re-run the comparison afterwards.",
56
+ "Flags that the evidence is too thin to switch off the old box: one day, 12 rows, all GBP, no negative amounts/refunds or other edge cases, and the old job was run only once, so its own nondeterminism is unknown. Recommends replaying more production-shaped input first. Keeps the answer proportionate to a quick Slack-style question rather than a long formal report.",
57
+ "Invents nothing that is not in the files: no extra account IDs, fee values, rates, downstream systems, or facts about Priya beyond her being the person to tell."
58
+ ]
59
+ },
60
+ {
61
+ "id": 5,
62
+ "held_out": "2026-10-20",
63
+ "prompt": "I'm the engineering manager who has to sign off the orders-api migration from Express to Fastify before we cut traffic over on Tuesday 27 Oct. The team says it's equivalent and the one remaining diff is noise. Their recorded replays are in recordings/ (two runs against the old service, one against the new), the normalisation they applied is normalisation.yaml, and their write-up is TEAM_NOTES.md. Please check their reasoning and give me a verdict I can paste into the change record, with what has to happen before cutover if it isn't a yes.",
64
+ "assertions": [
65
+ "Gives a clear verdict up front that can be pasted into the change record: NOT EQUIVALENT, not the team's 'equivalent'. It also says plainly whether the Tuesday 27 Oct cutover should go ahead, with the blocking items listed.",
66
+ "Rejects dismissing seq 4 (GET /orders/ord_bad!!, old 400 INVALID_ORDER_ID vs new 500 INTERNAL_ERROR) as noise. A malformed id that now produces a server error is a behaviour change, most likely a validation defect, whatever the team thinks about how often clients send it.",
67
+ "Finds that ignoring response.body.error wholesale hides a contract change at seq 6: error.code went from QUANTITY_INVALID to VALIDATION_ERROR. Ties this to TEAM_NOTES saying the Android app switches on error.code, so this is a user-visible break, not reworded text.",
68
+ "Finds that numeric_strings_to_numbers hides a type change: total went from a string (\"19.90\", \"5.00\", \"39.80\") to a number (19.9, 5.0, 39.8) in seq 1, 2, 5 and 7. Ties this to TEAM_NOTES saying the web checkout passes total to the payment widget as a string, and treats it as a defect or as an intended change that needs a named approver.",
69
+ "Uses old_run1 vs old_run2 to separate the old service's real nondeterminism (request_id, date header, the id and created_at of the order created at seq 5, and the order of tags on ord_1001) from everything else. Recommends narrowing the rules to match: tags-only sorting instead of sort_all_arrays, id/created_at ignored only on the POST-created resource instead of globally, and error.message ignored instead of the whole error object.",
70
+ "Puts each remaining difference into one category (defect, intended change, or missing/over-broad normalisation), including the reworded 404 message at seq 3 and the content-type charset change. Notes that the files name no approver for any intended change, so none of them counts as approved yet.",
71
+ "Notes that 8 hand-picked requests on a seeded staging database are not production-shaped traffic. Calls out obvious coverage gaps, such as auth failures, pagination, and concurrent or large orders, and recommends replaying a production-shaped sample, or shadow traffic, with the corrected normalisation before cutover.",
72
+ "Invents nothing that is not in the files: no extra endpoints, recorded results, error codes, consumers, approvers or dates beyond those in recordings/, normalisation.yaml and TEAM_NOTES.md (Dana Okafor is the only person named)."
73
+ ]
74
+ }
75
+ ]
76
+ }
@@ -0,0 +1,12 @@
1
+ {"case_id": "c01", "label": "one standard-rate line", "request": {"currency": "GBP", "items": [{"sku": "TEA-001", "qty": 1}]}}
2
+ {"case_id": "c02", "label": "two standard-rate lines", "request": {"currency": "GBP", "items": [{"sku": "TEA-001", "qty": 2}, {"sku": "MUG-002", "qty": 1}]}}
3
+ {"case_id": "c03", "label": "one reduced-rate line", "request": {"currency": "GBP", "items": [{"sku": "CHILD-SEAT-01", "qty": 1}]}}
4
+ {"case_id": "c04", "label": "reduced-rate booster seat", "request": {"currency": "GBP", "items": [{"sku": "BOOSTER-02", "qty": 1}]}}
5
+ {"case_id": "c05", "label": "reduced-rate, qty 2", "request": {"currency": "GBP", "items": [{"sku": "BOOSTER-02", "qty": 2}]}}
6
+ {"case_id": "c06", "label": "reduced-rate, qty 3", "request": {"currency": "GBP", "items": [{"sku": "BOOSTER-02", "qty": 3}]}}
7
+ {"case_id": "c07", "label": "pram plus tea, mixed bands", "request": {"currency": "GBP", "items": [{"sku": "PRAM-03", "qty": 1}, {"sku": "TEA-001", "qty": 1}]}}
8
+ {"case_id": "c08", "label": "zero, standard and repeated lines", "request": {"currency": "GBP", "items": [{"sku": "BOOK-05", "qty": 1}, {"sku": "MUG-002", "qty": 1}, {"sku": "TEA-001", "qty": 2}, {"sku": "TEA-001", "qty": 1}]}}
9
+ {"case_id": "c09", "label": "empty basket", "request": {"currency": "GBP", "items": []}}
10
+ {"case_id": "c10", "label": "unknown sku", "request": {"currency": "GBP", "items": [{"sku": "XYZ-999", "qty": 1}]}}
11
+ {"case_id": "c11", "label": "zero quantity", "request": {"currency": "GBP", "items": [{"sku": "MUG-002", "qty": 0}]}}
12
+ {"case_id": "c12", "label": "large basket", "request": {"currency": "GBP", "items": [{"sku": "MUG-002", "qty": 100}, {"sku": "CHILD-SEAT-01", "qty": 4}, {"sku": "TEA-001", "qty": 50}]}}
@@ -0,0 +1,12 @@
1
+ {"case_id": "c01", "status": 200, "body": {"quote_id": "Q-9f767c45", "quoted_at": "2026-10-14T15:03:20.441+00:00", "currency": "GBP", "lines": [{"sku": "TEA-001", "qty": 1, "unit_price_minor": 450, "line_total_minor": 450, "vat_band": "standard", "vat_minor": 90}], "subtotal_minor": 450, "vat_minor": 90, "total_minor": 540}}
2
+ {"case_id": "c02", "status": 200, "body": {"quote_id": "Q-bde5c099", "quoted_at": "2026-10-14T15:03:20.988+00:00", "currency": "GBP", "lines": [{"sku": "MUG-002", "qty": 1, "unit_price_minor": 899, "line_total_minor": 899, "vat_band": "standard", "vat_minor": 180}, {"sku": "TEA-001", "qty": 2, "unit_price_minor": 450, "line_total_minor": 900, "vat_band": "standard", "vat_minor": 180}], "subtotal_minor": 1799, "vat_minor": 360, "total_minor": 2159}}
3
+ {"case_id": "c03", "status": 200, "body": {"quote_id": "Q-cb91ce37", "quoted_at": "2026-10-14T15:03:21.875+00:00", "currency": "GBP", "lines": [{"sku": "CHILD-SEAT-01", "qty": 1, "unit_price_minor": 12500, "line_total_minor": 12500, "vat_band": "reduced", "vat_minor": 625}], "subtotal_minor": 12500, "vat_minor": 625, "total_minor": 13125}}
4
+ {"case_id": "c04", "status": 200, "body": {"quote_id": "Q-f1446bea", "quoted_at": "2026-10-14T15:03:22.722+00:00", "currency": "GBP", "lines": [{"sku": "BOOSTER-02", "qty": 1, "unit_price_minor": 2250, "line_total_minor": 2250, "vat_band": "reduced", "vat_minor": 112}], "subtotal_minor": 2250, "vat_minor": 112, "total_minor": 2362}}
5
+ {"case_id": "c05", "status": 200, "body": {"quote_id": "Q-ec1d7da0", "quoted_at": "2026-10-14T15:03:23.444+00:00", "currency": "GBP", "lines": [{"sku": "BOOSTER-02", "qty": 2, "unit_price_minor": 2250, "line_total_minor": 4500, "vat_band": "reduced", "vat_minor": 225}], "subtotal_minor": 4500, "vat_minor": 225, "total_minor": 4725}}
6
+ {"case_id": "c06", "status": 200, "body": {"quote_id": "Q-076ce2ef", "quoted_at": "2026-10-14T15:03:24.100+00:00", "currency": "GBP", "lines": [{"sku": "BOOSTER-02", "qty": 3, "unit_price_minor": 2250, "line_total_minor": 6750, "vat_band": "reduced", "vat_minor": 338}], "subtotal_minor": 6750, "vat_minor": 338, "total_minor": 7088}}
7
+ {"case_id": "c07", "status": 200, "body": {"quote_id": "Q-c6a53877", "quoted_at": "2026-10-14T15:03:24.535+00:00", "currency": "GBP", "lines": [{"sku": "PRAM-03", "qty": 1, "unit_price_minor": 31090, "line_total_minor": 31090, "vat_band": "reduced", "vat_minor": 1554}, {"sku": "TEA-001", "qty": 1, "unit_price_minor": 450, "line_total_minor": 450, "vat_band": "standard", "vat_minor": 90}], "subtotal_minor": 31540, "vat_minor": 1644, "total_minor": 33184}}
8
+ {"case_id": "c08", "status": 200, "body": {"quote_id": "Q-a6233255", "quoted_at": "2026-10-14T15:03:24.768+00:00", "currency": "GBP", "lines": [{"sku": "BOOK-05", "qty": 1, "unit_price_minor": 1299, "line_total_minor": 1299, "vat_band": "zero", "vat_minor": 0}, {"sku": "MUG-002", "qty": 1, "unit_price_minor": 899, "line_total_minor": 899, "vat_band": "standard", "vat_minor": 180}, {"sku": "TEA-001", "qty": 3, "unit_price_minor": 450, "line_total_minor": 1350, "vat_band": "standard", "vat_minor": 270}], "subtotal_minor": 3548, "vat_minor": 450, "total_minor": 3998}}
9
+ {"case_id": "c09", "status": 422, "body": {"error": "empty_basket"}}
10
+ {"case_id": "c10", "status": 422, "body": {"error": "unknown_sku", "sku": "XYZ-999"}}
11
+ {"case_id": "c11", "status": 422, "body": {"error": "invalid_qty", "sku": "MUG-002"}}
12
+ {"case_id": "c12", "status": 200, "body": {"quote_id": "Q-e6a16a3b", "quoted_at": "2026-10-14T15:03:25.108+00:00", "currency": "GBP", "lines": [{"sku": "CHILD-SEAT-01", "qty": 4, "unit_price_minor": 12500, "line_total_minor": 50000, "vat_band": "reduced", "vat_minor": 2500}, {"sku": "MUG-002", "qty": 100, "unit_price_minor": 899, "line_total_minor": 89900, "vat_band": "standard", "vat_minor": 17980}, {"sku": "TEA-001", "qty": 50, "unit_price_minor": 450, "line_total_minor": 22500, "vat_band": "standard", "vat_minor": 4500}], "subtotal_minor": 162400, "vat_minor": 24980, "total_minor": 187380}}
@@ -0,0 +1,15 @@
1
+ # Quote service rewrite: parity run
2
+
3
+ Owner: Priya Raman (checkout team). Cutover planned for the release train after next.
4
+
5
+ - `cases.jsonl`: the 12 requests we replayed. Taken from the staging request log on 12 Oct and
6
+ topped up with the error cases by hand. Promo codes aren't in this set: staging doesn't have
7
+ the promo service, so no request carries one.
8
+ - `old.jsonl`, `old_rerun.jsonl`: `pricing_v1.quote` run twice on those requests (09:14 and
9
+ 09:41 on 12 Oct).
10
+ - `new.jsonl`: `pricing_v2.quote` on the same requests (14 Oct).
11
+ - `rules.json`: our normalisation rules. With them the diff tool says every case matches.
12
+
13
+ Differences we saw before normalising: timestamps, quote IDs, line order, and a few 1p
14
+ rounding differences between the Decimal code paths, which the tolerance rules cover. We'd
15
+ like to call this equivalent and book the cutover.
@@ -0,0 +1,12 @@
1
+ {"case_id": "c01", "status": 200, "body": {"quote_id": "Q-3ceb3ffd", "quoted_at": "2026-10-12T09:14:02.786+00:00", "currency": "GBP", "lines": [{"sku": "TEA-001", "qty": 1, "unit_price_minor": 450, "line_total_minor": 450, "vat_band": "standard", "vat_minor": 90}], "subtotal_minor": 450, "vat_minor": 90, "total_minor": 540}}
2
+ {"case_id": "c02", "status": 200, "body": {"quote_id": "Q-8b529b4a", "quoted_at": "2026-10-12T09:14:03.099+00:00", "currency": "GBP", "lines": [{"sku": "TEA-001", "qty": 2, "unit_price_minor": 450, "line_total_minor": 900, "vat_band": "standard", "vat_minor": 180}, {"sku": "MUG-002", "qty": 1, "unit_price_minor": 899, "line_total_minor": 899, "vat_band": "standard", "vat_minor": 180}], "subtotal_minor": 1799, "vat_minor": 360, "total_minor": 2159}}
3
+ {"case_id": "c03", "status": 200, "body": {"quote_id": "Q-5eb561a4", "quoted_at": "2026-10-12T09:14:03.897+00:00", "currency": "GBP", "lines": [{"sku": "CHILD-SEAT-01", "qty": 1, "unit_price_minor": 12500, "line_total_minor": 12500, "vat_band": "reduced", "vat_minor": 625}], "subtotal_minor": 12500, "vat_minor": 625, "total_minor": 13125}}
4
+ {"case_id": "c04", "status": 200, "body": {"quote_id": "Q-795b929e", "quoted_at": "2026-10-12T09:14:04.717+00:00", "currency": "GBP", "lines": [{"sku": "BOOSTER-02", "qty": 1, "unit_price_minor": 2250, "line_total_minor": 2250, "vat_band": "reduced", "vat_minor": 113}], "subtotal_minor": 2250, "vat_minor": 113, "total_minor": 2363}}
5
+ {"case_id": "c05", "status": 200, "body": {"quote_id": "Q-94b2b8fd", "quoted_at": "2026-10-12T09:14:04.964+00:00", "currency": "GBP", "lines": [{"sku": "BOOSTER-02", "qty": 2, "unit_price_minor": 2250, "line_total_minor": 4500, "vat_band": "reduced", "vat_minor": 225}], "subtotal_minor": 4500, "vat_minor": 225, "total_minor": 4725}}
6
+ {"case_id": "c06", "status": 200, "body": {"quote_id": "Q-9b08923d", "quoted_at": "2026-10-12T09:14:05.157+00:00", "currency": "GBP", "lines": [{"sku": "BOOSTER-02", "qty": 3, "unit_price_minor": 2250, "line_total_minor": 6750, "vat_band": "reduced", "vat_minor": 338}], "subtotal_minor": 6750, "vat_minor": 338, "total_minor": 7088}}
7
+ {"case_id": "c07", "status": 200, "body": {"quote_id": "Q-e8a8529f", "quoted_at": "2026-10-12T09:14:05.817+00:00", "currency": "GBP", "lines": [{"sku": "TEA-001", "qty": 1, "unit_price_minor": 450, "line_total_minor": 450, "vat_band": "standard", "vat_minor": 90}, {"sku": "PRAM-03", "qty": 1, "unit_price_minor": 31090, "line_total_minor": 31090, "vat_band": "reduced", "vat_minor": 1555}], "subtotal_minor": 31540, "vat_minor": 1645, "total_minor": 33185}}
8
+ {"case_id": "c08", "status": 200, "body": {"quote_id": "Q-42650644", "quoted_at": "2026-10-12T09:14:06.561+00:00", "currency": "GBP", "lines": [{"sku": "BOOK-05", "qty": 1, "unit_price_minor": 1299, "line_total_minor": 1299, "vat_band": "zero", "vat_minor": 0}, {"sku": "TEA-001", "qty": 3, "unit_price_minor": 450, "line_total_minor": 1350, "vat_band": "standard", "vat_minor": 270}, {"sku": "MUG-002", "qty": 1, "unit_price_minor": 899, "line_total_minor": 899, "vat_band": "standard", "vat_minor": 180}], "subtotal_minor": 3548, "vat_minor": 450, "total_minor": 3998}}
9
+ {"case_id": "c09", "status": 422, "body": {"error": "empty_basket"}}
10
+ {"case_id": "c10", "status": 422, "body": {"error": "unknown_sku", "sku": "XYZ-999"}}
11
+ {"case_id": "c11", "status": 422, "body": {"error": "invalid_qty", "sku": "MUG-002"}}
12
+ {"case_id": "c12", "status": 200, "body": {"quote_id": "Q-3bfd1d33", "quoted_at": "2026-10-12T09:14:06.937+00:00", "currency": "GBP", "lines": [{"sku": "CHILD-SEAT-01", "qty": 4, "unit_price_minor": 12500, "line_total_minor": 50000, "vat_band": "reduced", "vat_minor": 2500}, {"sku": "TEA-001", "qty": 50, "unit_price_minor": 450, "line_total_minor": 22500, "vat_band": "standard", "vat_minor": 4500}, {"sku": "MUG-002", "qty": 100, "unit_price_minor": 899, "line_total_minor": 89900, "vat_band": "standard", "vat_minor": 17980}], "subtotal_minor": 162400, "vat_minor": 24980, "total_minor": 187380}}
@@ -0,0 +1,12 @@
1
+ {"case_id": "c01", "status": 200, "body": {"quote_id": "Q-3a096533", "quoted_at": "2026-10-12T09:41:37.559+00:00", "currency": "GBP", "lines": [{"sku": "TEA-001", "qty": 1, "unit_price_minor": 450, "line_total_minor": 450, "vat_band": "standard", "vat_minor": 90}], "subtotal_minor": 450, "vat_minor": 90, "total_minor": 540}}
2
+ {"case_id": "c02", "status": 200, "body": {"quote_id": "Q-f658f7a7", "quoted_at": "2026-10-12T09:41:38.123+00:00", "currency": "GBP", "lines": [{"sku": "TEA-001", "qty": 2, "unit_price_minor": 450, "line_total_minor": 900, "vat_band": "standard", "vat_minor": 180}, {"sku": "MUG-002", "qty": 1, "unit_price_minor": 899, "line_total_minor": 899, "vat_band": "standard", "vat_minor": 180}], "subtotal_minor": 1799, "vat_minor": 360, "total_minor": 2159}}
3
+ {"case_id": "c03", "status": 200, "body": {"quote_id": "Q-205738d1", "quoted_at": "2026-10-12T09:41:38.500+00:00", "currency": "GBP", "lines": [{"sku": "CHILD-SEAT-01", "qty": 1, "unit_price_minor": 12500, "line_total_minor": 12500, "vat_band": "reduced", "vat_minor": 625}], "subtotal_minor": 12500, "vat_minor": 625, "total_minor": 13125}}
4
+ {"case_id": "c04", "status": 200, "body": {"quote_id": "Q-b46ee1da", "quoted_at": "2026-10-12T09:41:38.724+00:00", "currency": "GBP", "lines": [{"sku": "BOOSTER-02", "qty": 1, "unit_price_minor": 2250, "line_total_minor": 2250, "vat_band": "reduced", "vat_minor": 113}], "subtotal_minor": 2250, "vat_minor": 113, "total_minor": 2363}}
5
+ {"case_id": "c05", "status": 200, "body": {"quote_id": "Q-15ceb3a1", "quoted_at": "2026-10-12T09:41:39.044+00:00", "currency": "GBP", "lines": [{"sku": "BOOSTER-02", "qty": 2, "unit_price_minor": 2250, "line_total_minor": 4500, "vat_band": "reduced", "vat_minor": 225}], "subtotal_minor": 4500, "vat_minor": 225, "total_minor": 4725}}
6
+ {"case_id": "c06", "status": 200, "body": {"quote_id": "Q-3f584ad4", "quoted_at": "2026-10-12T09:41:39.742+00:00", "currency": "GBP", "lines": [{"sku": "BOOSTER-02", "qty": 3, "unit_price_minor": 2250, "line_total_minor": 6750, "vat_band": "reduced", "vat_minor": 338}], "subtotal_minor": 6750, "vat_minor": 338, "total_minor": 7088}}
7
+ {"case_id": "c07", "status": 200, "body": {"quote_id": "Q-359b1548", "quoted_at": "2026-10-12T09:41:40.332+00:00", "currency": "GBP", "lines": [{"sku": "TEA-001", "qty": 1, "unit_price_minor": 450, "line_total_minor": 450, "vat_band": "standard", "vat_minor": 90}, {"sku": "PRAM-03", "qty": 1, "unit_price_minor": 31090, "line_total_minor": 31090, "vat_band": "reduced", "vat_minor": 1555}], "subtotal_minor": 31540, "vat_minor": 1645, "total_minor": 33185}}
8
+ {"case_id": "c08", "status": 200, "body": {"quote_id": "Q-a4517d6c", "quoted_at": "2026-10-12T09:41:40.543+00:00", "currency": "GBP", "lines": [{"sku": "MUG-002", "qty": 1, "unit_price_minor": 899, "line_total_minor": 899, "vat_band": "standard", "vat_minor": 180}, {"sku": "TEA-001", "qty": 3, "unit_price_minor": 450, "line_total_minor": 1350, "vat_band": "standard", "vat_minor": 270}, {"sku": "BOOK-05", "qty": 1, "unit_price_minor": 1299, "line_total_minor": 1299, "vat_band": "zero", "vat_minor": 0}], "subtotal_minor": 3548, "vat_minor": 450, "total_minor": 3998}}
9
+ {"case_id": "c09", "status": 422, "body": {"error": "empty_basket"}}
10
+ {"case_id": "c10", "status": 422, "body": {"error": "unknown_sku", "sku": "XYZ-999"}}
11
+ {"case_id": "c11", "status": 422, "body": {"error": "invalid_qty", "sku": "MUG-002"}}
12
+ {"case_id": "c12", "status": 200, "body": {"quote_id": "Q-7589ca4a", "quoted_at": "2026-10-12T09:41:41.222+00:00", "currency": "GBP", "lines": [{"sku": "MUG-002", "qty": 100, "unit_price_minor": 899, "line_total_minor": 89900, "vat_band": "standard", "vat_minor": 17980}, {"sku": "TEA-001", "qty": 50, "unit_price_minor": 450, "line_total_minor": 22500, "vat_band": "standard", "vat_minor": 4500}, {"sku": "CHILD-SEAT-01", "qty": 4, "unit_price_minor": 12500, "line_total_minor": 50000, "vat_band": "reduced", "vat_minor": 2500}], "subtotal_minor": 162400, "vat_minor": 24980, "total_minor": 187380}}