plan-verify 0.0.0-stage → 0.1.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 (222) hide show
  1. package/.github/workflows/ci.yml +48 -0
  2. package/README.md +1431 -2
  3. package/assets/icon.png +0 -0
  4. package/assets/logo-mark.png +0 -0
  5. package/assets/logo.png +0 -0
  6. package/examples/checkout.ears.md +74 -0
  7. package/examples/order-always.ears.md +3 -0
  8. package/examples/order-awkward.ears.md +8 -0
  9. package/examples/order-bad.json +1 -0
  10. package/examples/order-chain.ears.md +9 -0
  11. package/examples/order-chosen.ears.md +7 -0
  12. package/examples/order-clear.ears.md +8 -0
  13. package/examples/order-combined.ears.md +10 -0
  14. package/examples/order-conflict.ears.md +8 -0
  15. package/examples/order-covered.ears.md +9 -0
  16. package/examples/order-duplicate.ears.md +8 -0
  17. package/examples/order-fail.json +5 -0
  18. package/examples/order-misuse.ears.md +7 -0
  19. package/examples/order-negation.ears.md +7 -0
  20. package/examples/order-pass.json +5 -0
  21. package/examples/order-quantity.ears.md +8 -0
  22. package/examples/order-redundant.ears.md +9 -0
  23. package/examples/order-repair.ears.md +9 -0
  24. package/examples/order-repeat.ears.md +13 -0
  25. package/examples/order-same.ears.md +3 -0
  26. package/examples/order-status.ears.md +7 -0
  27. package/examples/order-unanswered.ears.md +8 -0
  28. package/examples/order-unhandled.ears.md +8 -0
  29. package/examples/order-unmeasured.ears.md +10 -0
  30. package/examples/order-unused.ears.md +13 -0
  31. package/examples/order-vacuous.ears.md +9 -0
  32. package/examples/order-vague.ears.md +7 -0
  33. package/examples/order-wide.ears.md +5 -0
  34. package/examples/order-wording.ears.md +8 -0
  35. package/examples/order.ears.md +7 -0
  36. package/examples/payment-if.ears.md +7 -0
  37. package/examples/plan-to-problems/README.md +54 -0
  38. package/examples/plan-to-problems/expected-problems.md +25 -0
  39. package/examples/plan-to-problems/generate.mjs +186 -0
  40. package/examples/plan-to-problems/generated.ears.md +27 -0
  41. package/examples/plan-to-problems/lint.json +576 -0
  42. package/examples/plan-to-problems/plan.md +5 -0
  43. package/examples/plan-to-problems/planted.txt +5 -0
  44. package/examples/plan-to-problems/problems.md +112 -0
  45. package/examples/status-placed.ears.md +3 -0
  46. package/package.json +25 -4
  47. package/scripts/install-scriptc.sh +16 -0
  48. package/skills/plan-verify/SKILL.md +1515 -0
  49. package/src/check.ts +250 -0
  50. package/src/decide.ts +1324 -0
  51. package/src/ears.ts +1520 -0
  52. package/src/jsonvalue.ts +228 -0
  53. package/src/lint.ts +1245 -0
  54. package/src/main.ts +273 -0
  55. package/src/policy.ts +148 -0
  56. package/src/repair.ts +567 -0
  57. package/src/report.ts +329 -0
  58. package/src/scenarios.ts +149 -0
  59. package/src/smt.ts +97 -0
  60. package/src/solver.ts +868 -0
  61. package/src/spec.ts +307 -0
  62. package/src/term.ts +331 -0
  63. package/src/text.ts +95 -0
  64. package/test/fixtures/malformed.ears.md +3 -0
  65. package/test/fixtures/unknown-phrase.ears.md +5 -0
  66. package/test/golden/check-fail-json.exit +1 -0
  67. package/test/golden/check-fail-json.stderr +0 -0
  68. package/test/golden/check-fail-json.stdout +13 -0
  69. package/test/golden/check-fail.exit +1 -0
  70. package/test/golden/check-fail.stderr +0 -0
  71. package/test/golden/check-fail.stdout +7 -0
  72. package/test/golden/check-pass-json.exit +1 -0
  73. package/test/golden/check-pass-json.stderr +0 -0
  74. package/test/golden/check-pass-json.stdout +6 -0
  75. package/test/golden/check-pass.exit +1 -0
  76. package/test/golden/check-pass.stderr +0 -0
  77. package/test/golden/check-pass.stdout +3 -0
  78. package/test/golden/emit-smt-json-usage.exit +1 -0
  79. package/test/golden/emit-smt-json-usage.stderr +0 -0
  80. package/test/golden/emit-smt-json-usage.stdout +4 -0
  81. package/test/golden/emit-smt-json.exit +1 -0
  82. package/test/golden/emit-smt-json.stderr +0 -0
  83. package/test/golden/emit-smt-json.stdout +5 -0
  84. package/test/golden/emit-smt.exit +1 -0
  85. package/test/golden/emit-smt.stderr +0 -0
  86. package/test/golden/emit-smt.stdout +10 -0
  87. package/test/golden/help.exit +1 -0
  88. package/test/golden/help.stderr +0 -0
  89. package/test/golden/help.stdout +8 -0
  90. package/test/golden/lint-awkward.exit +1 -0
  91. package/test/golden/lint-awkward.stderr +0 -0
  92. package/test/golden/lint-awkward.stdout +29 -0
  93. package/test/golden/lint-chain.exit +1 -0
  94. package/test/golden/lint-chain.stderr +0 -0
  95. package/test/golden/lint-chain.stdout +56 -0
  96. package/test/golden/lint-checkout.exit +1 -0
  97. package/test/golden/lint-checkout.stderr +0 -0
  98. package/test/golden/lint-checkout.stdout +301 -0
  99. package/test/golden/lint-clean.exit +1 -0
  100. package/test/golden/lint-clean.stderr +0 -0
  101. package/test/golden/lint-clean.stdout +9 -0
  102. package/test/golden/lint-clear.exit +1 -0
  103. package/test/golden/lint-clear.stderr +0 -0
  104. package/test/golden/lint-clear.stdout +9 -0
  105. package/test/golden/lint-conflict-json.exit +1 -0
  106. package/test/golden/lint-conflict-json.stderr +0 -0
  107. package/test/golden/lint-conflict-json.stdout +80 -0
  108. package/test/golden/lint-conflict.exit +1 -0
  109. package/test/golden/lint-conflict.stderr +0 -0
  110. package/test/golden/lint-conflict.stdout +41 -0
  111. package/test/golden/lint-duplicate.exit +1 -0
  112. package/test/golden/lint-duplicate.stderr +0 -0
  113. package/test/golden/lint-duplicate.stdout +14 -0
  114. package/test/golden/lint-gap.exit +1 -0
  115. package/test/golden/lint-gap.stderr +0 -0
  116. package/test/golden/lint-gap.stdout +12 -0
  117. package/test/golden/lint-misuse.exit +1 -0
  118. package/test/golden/lint-misuse.stderr +0 -0
  119. package/test/golden/lint-misuse.stdout +18 -0
  120. package/test/golden/lint-negation.exit +1 -0
  121. package/test/golden/lint-negation.stderr +0 -0
  122. package/test/golden/lint-negation.stdout +13 -0
  123. package/test/golden/lint-quantity.exit +1 -0
  124. package/test/golden/lint-quantity.stderr +0 -0
  125. package/test/golden/lint-quantity.stdout +41 -0
  126. package/test/golden/lint-redundant.exit +1 -0
  127. package/test/golden/lint-redundant.stderr +0 -0
  128. package/test/golden/lint-redundant.stdout +14 -0
  129. package/test/golden/lint-repair-json.exit +1 -0
  130. package/test/golden/lint-repair-json.stderr +0 -0
  131. package/test/golden/lint-repair-json.stdout +127 -0
  132. package/test/golden/lint-repair.exit +1 -0
  133. package/test/golden/lint-repair.stderr +0 -0
  134. package/test/golden/lint-repair.stdout +54 -0
  135. package/test/golden/lint-repeat.exit +1 -0
  136. package/test/golden/lint-repeat.stderr +0 -0
  137. package/test/golden/lint-repeat.stdout +48 -0
  138. package/test/golden/lint-status.exit +1 -0
  139. package/test/golden/lint-status.stderr +0 -0
  140. package/test/golden/lint-status.stdout +44 -0
  141. package/test/golden/lint-unanswered.exit +1 -0
  142. package/test/golden/lint-unanswered.stderr +0 -0
  143. package/test/golden/lint-unanswered.stdout +12 -0
  144. package/test/golden/lint-unhandled.exit +1 -0
  145. package/test/golden/lint-unhandled.stderr +0 -0
  146. package/test/golden/lint-unhandled.stdout +12 -0
  147. package/test/golden/lint-unmeasured.exit +1 -0
  148. package/test/golden/lint-unmeasured.stderr +0 -0
  149. package/test/golden/lint-unmeasured.stdout +13 -0
  150. package/test/golden/lint-unused-json.exit +1 -0
  151. package/test/golden/lint-unused-json.stderr +0 -0
  152. package/test/golden/lint-unused-json.stdout +37 -0
  153. package/test/golden/lint-unused.exit +1 -0
  154. package/test/golden/lint-unused.stderr +0 -0
  155. package/test/golden/lint-unused.stdout +25 -0
  156. package/test/golden/lint-vacuous-json.exit +1 -0
  157. package/test/golden/lint-vacuous-json.stderr +0 -0
  158. package/test/golden/lint-vacuous-json.stdout +27 -0
  159. package/test/golden/lint-vacuous.exit +1 -0
  160. package/test/golden/lint-vacuous.stderr +0 -0
  161. package/test/golden/lint-vacuous.stdout +19 -0
  162. package/test/golden/lint-vague.exit +1 -0
  163. package/test/golden/lint-vague.stderr +0 -0
  164. package/test/golden/lint-vague.stdout +21 -0
  165. package/test/golden/lint-wide.exit +1 -0
  166. package/test/golden/lint-wide.stderr +0 -0
  167. package/test/golden/lint-wide.stdout +9 -0
  168. package/test/golden/lint-wording.exit +1 -0
  169. package/test/golden/lint-wording.stderr +0 -0
  170. package/test/golden/lint-wording.stdout +21 -0
  171. package/test/golden/malformed-scenario.exit +1 -0
  172. package/test/golden/malformed-scenario.stderr +1 -0
  173. package/test/golden/malformed-scenario.stdout +0 -0
  174. package/test/golden/malformed-spec.exit +1 -0
  175. package/test/golden/malformed-spec.stderr +1 -0
  176. package/test/golden/malformed-spec.stdout +0 -0
  177. package/test/golden/policy-counterexample.exit +1 -0
  178. package/test/golden/policy-counterexample.stderr +0 -0
  179. package/test/golden/policy-counterexample.stdout +13 -0
  180. package/test/golden/policy-covered.exit +1 -0
  181. package/test/golden/policy-covered.stderr +0 -0
  182. package/test/golden/policy-covered.stdout +19 -0
  183. package/test/golden/policy-holds.exit +1 -0
  184. package/test/golden/policy-holds.stderr +0 -0
  185. package/test/golden/policy-holds.stdout +3 -0
  186. package/test/golden/policy-json.exit +1 -0
  187. package/test/golden/policy-json.stderr +0 -0
  188. package/test/golden/policy-json.stdout +34 -0
  189. package/test/golden/policy-status.exit +1 -0
  190. package/test/golden/policy-status.stderr +0 -0
  191. package/test/golden/policy-status.stdout +16 -0
  192. package/test/golden/policy-vacuous.exit +1 -0
  193. package/test/golden/policy-vacuous.stderr +0 -0
  194. package/test/golden/policy-vacuous.stdout +3 -0
  195. package/test/golden/policy-wide.exit +1 -0
  196. package/test/golden/policy-wide.stderr +0 -0
  197. package/test/golden/policy-wide.stdout +15 -0
  198. package/test/golden/policy.exit +1 -0
  199. package/test/golden/policy.stderr +0 -0
  200. package/test/golden/policy.stdout +19 -0
  201. package/test/golden/scenarios-json.exit +1 -0
  202. package/test/golden/scenarios-json.stderr +0 -0
  203. package/test/golden/scenarios-json.stdout +75 -0
  204. package/test/golden/scenarios-status.exit +1 -0
  205. package/test/golden/scenarios-status.stderr +0 -0
  206. package/test/golden/scenarios-status.stdout +24 -0
  207. package/test/golden/scenarios.exit +1 -0
  208. package/test/golden/scenarios.stderr +0 -0
  209. package/test/golden/scenarios.stdout +26 -0
  210. package/test/golden/unit.exit +1 -0
  211. package/test/golden/unit.stderr +0 -0
  212. package/test/golden/unit.stdout +2 -0
  213. package/test/golden/unknown-phrase.exit +1 -0
  214. package/test/golden/unknown-phrase.stderr +1 -0
  215. package/test/golden/unknown-phrase.stdout +0 -0
  216. package/test/golden/usage.exit +1 -0
  217. package/test/golden/usage.stderr +9 -0
  218. package/test/golden/usage.stdout +0 -0
  219. package/test/run-all.ts +499 -0
  220. package/test/soundness.ts +375 -0
  221. package/test/unit.ts +1352 -0
  222. package/tsconfig.json +15 -0
Binary file
Binary file
Binary file
@@ -0,0 +1,74 @@
1
+ # Checkout and a subscription.
2
+ # R4 contradicts R1. R8, R12, and R14 cannot all hold. R16 contradicts R15.
3
+ # A receipt is required only after payment and a confirmed address.
4
+ # Billed seats are left open when checkout starts and stock is not available.
5
+
6
+ event cart_opened: Bool "The customer opens the cart." phrase "the cart is open"
7
+ event item_added: Bool "The customer adds an item." phrase "an item is added"
8
+ event checkout_started: Bool "The customer starts checkout." phrase "checkout is started"
9
+ event payment_submitted: Bool "The customer submits payment." phrase "payment is submitted"
10
+ unwanted event payment_declined: Bool "The card is declined." phrase "payment is declined"
11
+ event address_confirmed: Bool "The customer confirms a shipping address." phrase "the address is confirmed"
12
+ event subscribe_clicked: Bool "The customer starts a subscription." phrase "the customer is subscribing"
13
+ event cancel_requested: Bool "The customer cancels the subscription." phrase "the customer is cancelling"
14
+ state signed_in: Bool "The customer is signed in." phrase "the customer is signed in"
15
+ state card_saved: Bool "A card is on file." phrase "a card is saved"
16
+ state stock_ok: Bool "The warehouse can fill the cart." phrase "stock is available"
17
+ output order_accepted: Bool "The order is accepted." phrase "accept the order" outcome "the order was accepted" / "the order was not accepted"
18
+ output order_rejected: Bool "The order is rejected." phrase "reject the order" outcome "the order was rejected" / "the order was not rejected"
19
+ output subscription_started: Bool "A subscription begins." phrase "the subscription is started" action "start the subscription" outcome "the subscription was started" / "the subscription was not started"
20
+ output receipt_sent: Bool "A receipt goes out." phrase "send a receipt" outcome "a receipt was sent" / "a receipt was not sent"
21
+ enum Lane { hold, fulfill }
22
+ output lane: Lane "Whether the warehouse holds or fulfills." phrase "the lane" as "route the order to the {value} lane" says "the order was routed to the {value} lane"
23
+ output billed_seats: Int 1..3 "How many seats are billed." phrase "the billed seats" as "bill {op} {value} seats" says "{value} seats were billed"
24
+
25
+ R1 When payment is submitted and the address is confirmed, the Checkout shall accept the order.
26
+ R2 When payment is submitted and the address is not confirmed, the Checkout shall not accept the order.
27
+ R3 When payment is not submitted, the Checkout shall not accept the order.
28
+ R4 When payment is submitted and the address is confirmed, the Checkout shall not accept the order.
29
+ R5 When payment is submitted and the address is confirmed, the Checkout shall send a receipt.
30
+ R6 If payment is declined, then the Checkout shall reject the order.
31
+ R7 If payment is not declined, then the Checkout shall not reject the order.
32
+ R8 When the customer is subscribing and the customer is not cancelling and the customer is signed in, the Checkout shall start the subscription.
33
+ R9 When the customer is not subscribing, the Checkout shall not start the subscription.
34
+ R10 When the customer is cancelling, the Checkout shall not start the subscription.
35
+ R11 When the customer is subscribing and the customer is not cancelling and the customer is not signed in, the Checkout shall not start the subscription.
36
+ R12 When the subscription is started, the Checkout shall route the order to the fulfill lane.
37
+ R13 When the subscription is not started, the Checkout shall route the order to the hold lane.
38
+ R14 When the customer is subscribing, the Checkout shall route the order to the hold lane.
39
+ R15 When checkout is started and stock is available, the Checkout shall bill at most 2 seats.
40
+ R16 When checkout is started and stock is available, the Checkout shall bill 3 seats.
41
+ R17 When checkout is not started, the Checkout shall bill 1 seat.
42
+ R18 When payment is submitted and the address is confirmed and the customer is signed in, the Checkout shall accept the order.
43
+ R19 When payment is submitted and the address is confirmed and a card is saved, the Checkout shall accept the order.
44
+ R20 When payment is submitted and the address is confirmed and stock is available, the Checkout shall accept the order.
45
+ R21 When payment is submitted and the address is confirmed and the cart is open, the Checkout shall accept the order.
46
+ R22 When payment is submitted and the address is confirmed and an item is added, the Checkout shall accept the order.
47
+ R23 When payment is submitted and the address is confirmed and checkout is started, the Checkout shall accept the order.
48
+ R24 When payment is submitted and the address is confirmed and the customer is subscribing, the Checkout shall accept the order.
49
+ R25 When payment is submitted and the address is not confirmed and the customer is signed in, the Checkout shall not accept the order.
50
+ R26 When payment is submitted and the address is not confirmed and the cart is open, the Checkout shall not accept the order.
51
+ R27 When payment is submitted and the address is not confirmed and an item is added, the Checkout shall not accept the order.
52
+ R28 When payment is submitted and the address is not confirmed and a card is saved, the Checkout shall not accept the order.
53
+ R29 When payment is not submitted and the cart is open, the Checkout shall not accept the order.
54
+ R30 When payment is not submitted and an item is added, the Checkout shall not accept the order.
55
+ R31 When payment is not submitted and checkout is started, the Checkout shall not accept the order.
56
+ R32 When payment is not submitted and the customer is signed in, the Checkout shall not accept the order.
57
+ R33 When payment is not submitted and a card is saved, the Checkout shall not accept the order.
58
+ R34 When payment is not submitted and stock is available, the Checkout shall not accept the order.
59
+ R35 When payment is not submitted and the customer is subscribing, the Checkout shall not accept the order.
60
+ R36 When the customer is subscribing and the customer is not cancelling and the customer is signed in and a card is saved, the Checkout shall start the subscription.
61
+ R37 When the customer is subscribing and the customer is not cancelling and the customer is signed in and the cart is open, the Checkout shall start the subscription.
62
+ R38 When the customer is subscribing and the customer is not cancelling and the customer is signed in and an item is added, the Checkout shall start the subscription.
63
+ R39 When the customer is subscribing and the customer is cancelling, the Checkout shall not start the subscription.
64
+ R40 When the customer is cancelling and the cart is open, the Checkout shall not start the subscription.
65
+ R41 When the customer is cancelling and an item is added, the Checkout shall not start the subscription.
66
+ R42 When the customer is not subscribing and the cart is open, the Checkout shall not start the subscription.
67
+ R43 When the customer is not subscribing and an item is added, the Checkout shall not start the subscription.
68
+ R44 When the customer is not subscribing and a card is saved, the Checkout shall not start the subscription.
69
+ R45 When the customer is subscribing and the customer is not cancelling and the customer is not signed in and the cart is open, the Checkout shall not start the subscription.
70
+ R46 When the subscription is not started and the cart is open, the Checkout shall route the order to the hold lane.
71
+ R47 When the subscription is not started and an item is added, the Checkout shall route the order to the hold lane.
72
+ R48 When the subscription is started and a card is saved, the Checkout shall route the order to the fulfill lane.
73
+ R49 When payment is submitted and the address is confirmed and the customer is signed in, the Checkout shall send a receipt.
74
+ R50 When payment is submitted and the address is confirmed and the cart is open, the Checkout shall send a receipt.
@@ -0,0 +1,3 @@
1
+ # Property: every scenario fulfills the order.
2
+
3
+ P1 The Order System shall fulfill the order.
@@ -0,0 +1,8 @@
1
+ # The response and the trigger are the old awkward shape. Lint names both.
2
+
3
+ state subscription_started: Bool "A subscription begins." phrase "the subscription is started" action "start the subscription"
4
+ enum Lane { hold, fulfill }
5
+ output lane: Lane "Whether the warehouse holds or fulfills." phrase "the lane" as "route the order to the {value} lane" says "the order was routed to the {value} lane"
6
+
7
+ R1 When start the subscription, the Checkout shall the lane is fulfill.
8
+ R2 When not start the subscription, the Checkout shall the lane is hold.
@@ -0,0 +1 @@
1
+ {
@@ -0,0 +1,9 @@
1
+ # Three requirements clash only as a set.
2
+
3
+ event order_submitted: Bool "The customer submits an order." phrase "an order is submitted"
4
+ output order_accepted: Bool "The order is accepted." phrase "the order is accepted" action "accept the order" outcome "the order was accepted" / "the order was not accepted"
5
+ output order_shipped: Bool "The order is shipped." phrase "ship the order" outcome "the order was shipped" / "the order was not shipped"
6
+
7
+ R1 When an order is submitted, the Order System shall accept the order.
8
+ R2 When the order is accepted, the Order System shall ship the order.
9
+ R3 When an order is submitted, the Order System shall not ship the order.
@@ -0,0 +1,7 @@
1
+ # The PM kept fulfillment of a submitted order and dropped the refusal.
2
+
3
+ event order_submitted: Bool "The customer submits an order." phrase "an order is submitted"
4
+ output order_fulfilled: Bool "The order is fulfilled." phrase "fulfill the order" outcome "the order was fulfilled" / "the order was not fulfilled"
5
+
6
+ R1 When an order is not submitted, the Order System shall not fulfill the order.
7
+ R2 When an order is submitted, the Order System shall fulfill the order.
@@ -0,0 +1,8 @@
1
+ # The same checkout lane, written so a reader can say it out loud.
2
+
3
+ state subscription_started: Bool "A subscription begins." phrase "the subscription is started" action "start the subscription"
4
+ enum Lane { hold, fulfill }
5
+ output lane: Lane "Whether the warehouse holds or fulfills." phrase "the lane" as "route the order to the {value} lane" says "the order was routed to the {value} lane"
6
+
7
+ R1 When the subscription is started, the Checkout shall route the order to the fulfill lane.
8
+ R2 When the subscription is not started, the Checkout shall route the order to the hold lane.
@@ -0,0 +1,10 @@
1
+ # Combined clauses
2
+
3
+ state inventory_available: Bool "Enough stock is on hand to fill the order." phrase "inventory is available"
4
+ event order_submitted: Bool "The customer submits an order." phrase "an order is submitted"
5
+ input quantity: Int 1..5 "How many units the customer asked for." phrase "the quantity"
6
+ enum Region { us, eu }
7
+ input region: Region "The region the order ships to." phrase "the region"
8
+ output shipped: Int 1..5 "How many units are shipped." phrase "the number shipped" as "ship {value}"
9
+
10
+ R1.2 While inventory is available, when an order is submitted, if the quantity is at least 1, where the region is us, then the Warehouse shall ship the quantity.
@@ -0,0 +1,8 @@
1
+ # Two requirements disagree about a submitted order.
2
+
3
+ event order_submitted: Bool "The customer submits an order." phrase "an order is submitted"
4
+ output order_fulfilled: Bool "The order is fulfilled." phrase "fulfill the order" outcome "the order was fulfilled" / "the order was not fulfilled"
5
+
6
+ R1 When an order is not submitted, the Order System shall not fulfill the order.
7
+ R2 When an order is submitted, the Order System shall fulfill the order.
8
+ R3 When an order is submitted, the Order System shall not fulfill the order.
@@ -0,0 +1,9 @@
1
+ # Every situation has a response.
2
+
3
+ event order_submitted: Bool "The customer submits an order." phrase "an order is submitted"
4
+ state inventory_available: Bool "Enough stock is on hand to fill the order." phrase "inventory is available"
5
+ output order_fulfilled: Bool "The order is fulfilled." phrase "fulfill the order" outcome "the order was fulfilled" / "the order was not fulfilled"
6
+
7
+ R1 When an order is submitted and inventory is available, the Order System shall fulfill the order.
8
+ R2 When an order is submitted and inventory is not available, the Order System shall not fulfill the order.
9
+ R3 When an order is not submitted, the Order System shall not fulfill the order.
@@ -0,0 +1,8 @@
1
+ # R2 states the same condition and the same response as R1.
2
+
3
+ event order_submitted: Bool "The customer submits an order." phrase "an order is submitted"
4
+ output order_fulfilled: Bool "The order is fulfilled." phrase "fulfill the order" outcome "the order was fulfilled" / "the order was not fulfilled"
5
+
6
+ R1 When an order is submitted, the Order System shall fulfill the order.
7
+ R2 When an order is submitted, the Order System shall fulfill the order.
8
+ R3 When an order is not submitted, the Order System shall not fulfill the order.
@@ -0,0 +1,5 @@
1
+ {
2
+ "order_submitted": true,
3
+ "inventory_available": true,
4
+ "order_fulfilled": false
5
+ }
@@ -0,0 +1,7 @@
1
+ # An unwanted event written with when.
2
+
3
+ unwanted event payment_declined: Bool "The payment was declined." phrase "payment fails"
4
+ output order_rejected: Bool "The order is rejected." phrase "reject the order" outcome "the order was rejected" / "the order was not rejected"
5
+
6
+ R4 When payment fails, the Order System shall reject the order.
7
+ R5 The Order System shall reject the order.
@@ -0,0 +1,7 @@
1
+ # The trigger is a bare negation. A positive event would name the person.
2
+
3
+ event borrow_requested: Bool "A member asks to take a copy home." phrase "a member asks to take a copy home"
4
+ output loan_allowed: Bool "Whether the loan was allowed." phrase "allow the loan" outcome "the loan was allowed" / "the loan was not allowed"
5
+
6
+ R1 When not a member asks to take a copy home, the Desk shall not allow the loan.
7
+ R2 When a member asks to take a copy home, the Desk shall allow the loan.
@@ -0,0 +1,5 @@
1
+ {
2
+ "order_submitted": true,
3
+ "inventory_available": true,
4
+ "order_fulfilled": true
5
+ }
@@ -0,0 +1,8 @@
1
+ # R4 caps the accepted quantity at 10. R5 accepts an order of 12.
2
+
3
+ event order_submitted: Bool "The customer submits an order." phrase "an order is submitted"
4
+ output quantity: Int 0..12 "How many units the order accepts." phrase "the quantity" as "accept {op} {value} units" says "{value} units were accepted"
5
+
6
+ R3 When an order is not submitted, the Order System shall accept 0 units.
7
+ R4 When an order is submitted, the Order System shall accept at most 10 units.
8
+ R5 When an order is submitted, the Order System shall accept 12 units.
@@ -0,0 +1,9 @@
1
+ # R2 repeats what R1 already requires.
2
+
3
+ event order_submitted: Bool "The customer submits an order." phrase "an order is submitted"
4
+ state inventory_available: Bool "Enough stock is on hand to fill the order." phrase "inventory is available"
5
+ output order_fulfilled: Bool "The order is fulfilled." phrase "fulfill the order" outcome "the order was fulfilled" / "the order was not fulfilled"
6
+
7
+ R1 When an order is submitted, the Order System shall fulfill the order.
8
+ R2 When an order is submitted and inventory is available, the Order System shall fulfill the order.
9
+ R3 When an order is not submitted, the Order System shall not fulfill the order.
@@ -0,0 +1,9 @@
1
+ # The copy cannot be lent and refused for the same request.
2
+
3
+ state copy_in_building: Bool "That copy is still in the building." phrase "the copy is in the building"
4
+ state copy_on_loan: Bool "That copy is already out on loan." phrase "the copy is out on loan"
5
+ event borrow_requested: Bool "A member asks to take a copy home." phrase "a member asks to take a copy home"
6
+ output loan_allowed: Bool "Whether the loan was allowed." phrase "allow the loan" outcome "the loan was allowed" / "the loan was not allowed"
7
+
8
+ R1 While the copy is in the building, when a member asks to take a copy home, the Desk shall allow the loan.
9
+ R2 While the copy is out on loan, when a member asks to take a copy home, the Desk shall not allow the loan.
@@ -0,0 +1,13 @@
1
+ # R5 repeats R4. R1 conflicts with R2, and that clash does not hide the repeat.
2
+
3
+ event order_submitted: Bool "The customer submits an order." phrase "an order is submitted"
4
+ state inventory_available: Bool "Enough stock is on hand to fill the order." phrase "inventory is available"
5
+ output order_fulfilled: Bool "The order is fulfilled." phrase "fulfill the order" outcome "the order was fulfilled" / "the order was not fulfilled"
6
+ output order_closed: Bool "The order is closed." phrase "close the order" outcome "the order was closed" / "the order was not closed"
7
+
8
+ R1 When an order is submitted, the Order System shall fulfill the order.
9
+ R2 When an order is submitted, the Order System shall not fulfill the order.
10
+ R3 When an order is not submitted, the Order System shall not fulfill the order.
11
+ R4 When an order is submitted, the Order System shall close the order.
12
+ R5 When an order is submitted and inventory is available, the Order System shall close the order.
13
+ R6 When an order is not submitted, the Order System shall not close the order.
@@ -0,0 +1,3 @@
1
+ # Property: the same rule as R1.
2
+
3
+ P1 WHEN an order is submitted AND inventory is available, THE Order System SHALL fulfill the order.
@@ -0,0 +1,7 @@
1
+ # The status would have to be shipped and cancelled at once.
2
+
3
+ enum Status { placed, shipped, cancelled }
4
+ output status: Status "Where the order stands." phrase "the status" as "mark the order {value}" says "the order was {value}"
5
+
6
+ R6 The Order System shall mark the order shipped.
7
+ R7 The Order System shall mark the order cancelled.
@@ -0,0 +1,8 @@
1
+ # An error condition with no stated response
2
+
3
+ unwanted event payment_declined: Bool "The payment was declined." phrase "payment fails"
4
+ event order_submitted: Bool "The customer submits an order." phrase "an order is submitted"
5
+ output order_fulfilled: Bool "The order is fulfilled." phrase "fulfill the order" outcome "the order was fulfilled" / "the order was not fulfilled"
6
+
7
+ R1 When an order is submitted, the Order System shall fulfill the order.
8
+ R2 When an order is not submitted, the Order System shall not fulfill the order.
@@ -0,0 +1,8 @@
1
+ # A declared event that no requirement handles
2
+
3
+ event order_submitted: Bool "The customer submits an order." phrase "an order is submitted"
4
+ event order_cancelled: Bool "The customer cancels an order." phrase "an order is cancelled"
5
+ output order_fulfilled: Bool "The order is fulfilled." phrase "fulfill the order" outcome "the order was fulfilled" / "the order was not fulfilled"
6
+
7
+ R1 When an order is submitted, the Order System shall fulfill the order.
8
+ R2 When an order is not submitted, the Order System shall not fulfill the order.
@@ -0,0 +1,10 @@
1
+ # "as needed" is free text. "respond quickly" is a declared phrase, so that wording is bound.
2
+
3
+ event order_submitted: Bool "The customer submits an order." phrase "an order is submitted"
4
+ output order_fulfilled: Bool "Handle it as needed." phrase "fulfill the order" outcome "the order was fulfilled" / "the order was not fulfilled"
5
+ output shipped: Bool "The order left the building." phrase "respond quickly" outcome "the order was shipped quickly" / "the order was not shipped quickly"
6
+
7
+ R1 When an order is submitted, the Order System shall fulfill the order.
8
+ R2 When an order is not submitted, the Order System shall not fulfill the order.
9
+ R3 When an order is submitted, the Order System shall respond quickly.
10
+ R4 When an order is not submitted, the Order System shall not respond quickly.
@@ -0,0 +1,13 @@
1
+ # loyalty and region are never mentioned. refund is never mentioned.
2
+ # A receipt is only read, never set.
3
+
4
+ state cart_open: Bool "The cart is open." phrase "the cart is open"
5
+ state loyalty: Bool "The customer is in the loyalty program." phrase "the customer is loyal"
6
+ input region: Int 1..3 "The region code." phrase "the region"
7
+ enum Lane { hold, fulfill, refund }
8
+ output lane: Lane "Whether the warehouse holds or fulfills." phrase "the lane" as "route the order to the {value} lane" says "the order was routed to the {value} lane"
9
+ output receipt_sent: Bool "A receipt goes out." phrase "a receipt is sent" action "send a receipt" outcome "a receipt was sent" / "a receipt was not sent"
10
+
11
+ R1 When the cart is open and a receipt is not sent, the Checkout shall route the order to the hold lane.
12
+ R2 When the cart is not open and a receipt is not sent, the Checkout shall route the order to the fulfill lane.
13
+ R3 When a receipt is sent, the Checkout shall route the order to the hold lane.
@@ -0,0 +1,9 @@
1
+ # R1 contradicts itself. R2 asks for a quantity the declared range does not contain.
2
+
3
+ state cart_open: Bool "The cart is open." phrase "the cart is open"
4
+ input quantity: Int 1..5 "How many units the customer asked for." phrase "the quantity"
5
+ output order_accepted: Bool "The order is accepted." phrase "accept the order" outcome "the order was accepted" / "the order was not accepted"
6
+
7
+ R1 When the cart is open and the cart is not open, the Checkout shall accept the order.
8
+ R2 When the quantity is at least 9, the Checkout shall accept the order.
9
+ R3 The Checkout shall accept the order.
@@ -0,0 +1,7 @@
1
+ # Vague words in an order requirement's description
2
+
3
+ event order_submitted: Bool "The customer submits an order." phrase "an order is submitted"
4
+ output order_fulfilled: Bool "The checkout is fast, appropriate, and user-friendly." phrase "fulfill the order" outcome "the order was fulfilled" / "the order was not fulfilled"
5
+
6
+ R1 When an order is submitted, the Order System shall fulfill the order.
7
+ R2 When an order is not submitted, the Order System shall not fulfill the order.
@@ -0,0 +1,5 @@
1
+ # A wide integer range the solver still decides.
2
+
3
+ output quantity: Int 0..250000 "A count with a wide range." phrase "the quantity" as "count {value}"
4
+
5
+ R1 The Counter shall count 0.
@@ -0,0 +1,8 @@
1
+ # Vague words and solution words.
2
+
3
+ event order_submitted: Bool "The customer submits an order." phrase "an order is submitted"
4
+ output order_fulfilled: Bool "The system responds quickly." phrase "fulfill the order" outcome "the order was fulfilled" / "the order was not fulfilled"
5
+ state saved: Bool "Store the order in the database." phrase "the order is saved"
6
+
7
+ R1 When an order is submitted, the Order System shall fulfill the order.
8
+ R2 When an order is not submitted, the Order System shall not fulfill the order.
@@ -0,0 +1,7 @@
1
+ # Order fulfillment
2
+
3
+ event order_submitted: Bool "The customer submits an order." phrase "an order is submitted"
4
+ state inventory_available: Bool "Enough stock is on hand to fill the order." phrase "inventory is available"
5
+ output order_fulfilled: Bool "The order is fulfilled." phrase "fulfill the order" outcome "the order was fulfilled" / "the order was not fulfilled"
6
+
7
+ R1 WHEN an order is submitted AND inventory is available, THE Order System SHALL fulfill the order.
@@ -0,0 +1,7 @@
1
+ # Unwanted events use if, then.
2
+
3
+ unwanted event payment_declined: Bool "The payment was declined." phrase "payment fails"
4
+ output order_rejected: Bool "The order is rejected." phrase "reject the order" outcome "the order was rejected" / "the order was not rejected"
5
+
6
+ R4 If payment fails, then the Order System shall reject the order.
7
+ R5 If not payment fails, then the Order System shall not reject the order.
@@ -0,0 +1,54 @@
1
+ # From a plan to a list of problems
2
+
3
+ A product plan goes in as plain English. A list of its logical problems comes out as plain English.
4
+
5
+ `generated.ears.md` is a recorded run from grok-4.7 on 2026-10-22. The model translated `plan.md`. The file is that reply, unedited.
6
+
7
+ ## Flow
8
+
9
+ 1. `plan.md` is the product plan, written the way a product manager talks. It has no EARS syntax. The problems planted in it are listed for a person in `expected-problems.md`. `planted.txt` is the same list in a small machine-readable form: two direct contradictions (`R2`/`R3` and `R5`/`R6`), one gap (`reminder_sent`), one condition that can never be true (`R9`), and one redundant requirement (`R11`).
10
+ 2. `generate.mjs` reads the agent skill and the plan and asks a model for an EARS spec. The user message tells the model it has no tools, must translate every rule faithfully, and must reply with only the spec in one fenced block. The request is streamed. It writes only the spec text to the output file. This step is live and needs a key. The offline test does not run it.
11
+ 3. `plan-verify lint --json` checks the spec and prints `lint.json`.
12
+ 4. `plan-verify report` turns that lint result into `problems.md`. Each logical problem is plain English, with requirement ids, line numbers, and the EARS sentences quoted. For a contradiction it asks which requirement should win and does not choose one. No model is involved.
13
+
14
+ Lint names redundancy on the requirements that are not part of any conflict. `R11` repeats `R10`, and neither requirement is in a conflict, so lint reports `R11` itself. The report still explains that `R11` only repeats `R10` in a stricter situation. It does not delete `R11`. A requirement whose condition can never be true stays vacuous only. `R9` is not also called redundant.
15
+
16
+ For each conflict the report asks which requirement should win, then lists candidate repairs and does not choose one. A repair drops one requirement in the conflicting set, or adds one Bool, enum equality, or integer bound to one requirement's condition. A suggestion is shown only after lint on the patched spec no longer has that conflict and has no new one. `R1` and `R2` can be separated by adding "and the copy is not out on loan" to `R1`, or "and the copy is not in the building" to `R2`. `R2` against `R3`, and `R5` against `R6`, share a condition, so the only candidates are to drop one of the two.
17
+
18
+ The recorded spec also has findings that were not planted. `R1` and `R2` contradict when the copy is both in the building and out on loan, because those two states are independent. `loan_allowed` is uncovered when a member asks and the copy is neither in the building nor out on loan. `renewal_allowed` is uncovered when nobody asks to renew. `R4` and `R12` negate a trigger with "When not …". Lint flags each one as wording kind `negation` and asks for a positive condition, such as "When a non-member asks", or a positive event.
19
+
20
+ ## Live command
21
+
22
+ `generate.mjs` has no packages. It runs on Node 18 or later with `fetch`. The key is read only from `XAI_API_KEY` and is never printed. `XAI_MODEL` overrides the model. The default is `grok-4.7`. The call goes to the OpenAI-compatible chat completions endpoint at `api.x.ai`. The skill text is the system prompt. The plan is wrapped in the user message.
23
+
24
+ From the repo root:
25
+
26
+ ```bash
27
+ XAI_API_KEY=... node examples/plan-to-problems/generate.mjs skills/plan-verify/SKILL.md examples/plan-to-problems/plan.md out.ears.md
28
+ ```
29
+
30
+ The script is self-contained. Copied next to `skill.md`, `plan.md`, and the output path, the same command is:
31
+
32
+ ```bash
33
+ node generate.mjs skill.md plan.md out.ears.md
34
+ ```
35
+
36
+ If the spec fails to parse, pass the parser error and the previous spec. The model is asked for a corrected spec:
37
+
38
+ ```bash
39
+ node generate.mjs skill.md plan.md out.ears.md repair-error.txt previous.ears.md
40
+ ```
41
+
42
+ Then print the problems. Either Node or the native binary works. They must print the same bytes.
43
+
44
+ ```bash
45
+ npm run build
46
+ ./build/plan-verify lint --json examples/plan-to-problems/generated.ears.md
47
+ ./build/plan-verify report examples/plan-to-problems/generated.ears.md
48
+ ```
49
+
50
+ `node --experimental-strip-types src/main.ts report examples/plan-to-problems/generated.ears.md` prints the same report.
51
+
52
+ ## Offline test
53
+
54
+ `npm test` runs an offline check that needs no key. It lints `generated.ears.md` and runs `report`, under Node and as the native binary. Stdout must match the committed `lint.json` and `problems.md`, stderr must be empty, and the exit status is 1 because problems were found. It then checks that every line in `planted.txt` is detected and is also named in `expected-problems.md`. Contradictions, the gap, and the impossible condition must show up in `lint.json`. The redundant requirement must show up in `lint.json` and in `problems.md`. The two bare negations must show up in `lint.json` as wording kind `negation`. The report must ask which requirement should win for every planted contradiction, and for any further contradiction the spec contains, and must not pick one.
@@ -0,0 +1,25 @@
1
+ # Planted problems
2
+
3
+ These are the logical problems written into `plan.md` on purpose. The recorded spec uses the ids below. Line numbers are the lines in `generated.ears.md`.
4
+
5
+ ## Two direct contradictions
6
+
7
+ The plan says not to lend a copy that is already out, and then it says to lend that same copy. Those are R2 line 17 and R3 line 18. Both apply when a member asks to take a copy home while the copy is out on loan, and they demand opposite answers.
8
+
9
+ The plan says a member who still owes a fine cannot renew, and it also says that member can renew. Those are R5 line 20 and R6 line 21. Both apply when a fine is owed and a member asks to renew, and they demand opposite answers.
10
+
11
+ ## One gap
12
+
13
+ The plan says to send a reminder after the due date. It never says what to do about a reminder when the loan is not overdue. No sentence covers that situation. The uncovered output is reminder_sent. R8 line 23 only speaks once the loan is overdue.
14
+
15
+ ## One condition that can never be true
16
+
17
+ Loans are only 1 to 21 days, and the plan also says to allow a loan of 40 days or longer. A loan length of at least 40 days cannot happen. That is R9 line 24.
18
+
19
+ ## One redundant requirement
20
+
21
+ Closing the loan when a copy comes back already requires the overdue case too. Saying it again adds nothing. R11 line 26 only repeats R10 line 25 in the stricter case where the loan is also overdue.
22
+
23
+ ## Negated triggers
24
+
25
+ R4 line 19 says "When not a member asks to take a copy home". R12 line 27 says "When not a member brings a copy back". Lint flags each sentence as wording kind `negation`. The suggested rewrite is a positive condition, such as "When a non-member asks", or a positive event.