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
@@ -0,0 +1,228 @@
1
+ /**
2
+ * A small JSON reader for flat scenario objects.
3
+ * Values are booleans, integers, or strings. Nested values are rejected.
4
+ * This stays in the static ScriptC tier, which does not accept JSON.parse.
5
+ */
6
+
7
+ export type JsonValue =
8
+ | { kind: "bool"; value: boolean }
9
+ | { kind: "int"; value: number }
10
+ | { kind: "string"; value: string };
11
+
12
+ export interface JsonObject {
13
+ keys: string[];
14
+ values: JsonValue[];
15
+ }
16
+
17
+ export type JsonResult = { ok: true; object: JsonObject } | { ok: false; line: number; message: string };
18
+
19
+ class Cur {
20
+ text: string;
21
+ i: number;
22
+ line: number;
23
+
24
+ constructor(text: string) {
25
+ this.text = text;
26
+ this.i = 0;
27
+ this.line = 1;
28
+ }
29
+ }
30
+
31
+ export function parseScenarioJson(text: string): JsonResult {
32
+ const source = text.charCodeAt(0) === 0xfeff ? text.slice(1) : text;
33
+ const cur = new Cur(source);
34
+ skip(cur);
35
+ if (cur.i >= cur.text.length) return fail(cur, "expected an object");
36
+ if (cur.text.charCodeAt(cur.i) !== 123) return fail(cur, "expected an object");
37
+ cur.i++;
38
+ const keys: string[] = [];
39
+ const values: JsonValue[] = [];
40
+ skip(cur);
41
+ if (cur.i < cur.text.length && cur.text.charCodeAt(cur.i) === 125) {
42
+ cur.i++;
43
+ skip(cur);
44
+ if (cur.i < cur.text.length) return fail(cur, "unexpected trailing text");
45
+ return { ok: true, object: { keys: keys, values: values } };
46
+ }
47
+ while (cur.i < cur.text.length) {
48
+ skip(cur);
49
+ const key = parseString(cur);
50
+ if (key === null) return fail(cur, "expected a string key");
51
+ for (let i = 0; i < keys.length; i++) {
52
+ if (keys[i] === key) return fail(cur, "duplicate key " + quote(key));
53
+ }
54
+ skip(cur);
55
+ if (cur.i >= cur.text.length || cur.text.charCodeAt(cur.i) !== 58) return fail(cur, "expected ':'");
56
+ cur.i++;
57
+ skip(cur);
58
+ const value = parseValue(cur);
59
+ if (typeof value === "string") return fail(cur, value);
60
+ keys.push(key);
61
+ values.push(value);
62
+ skip(cur);
63
+ if (cur.i >= cur.text.length) return fail(cur, "expected '}'");
64
+ const mark = cur.text.charCodeAt(cur.i);
65
+ if (mark === 125) {
66
+ cur.i++;
67
+ skip(cur);
68
+ if (cur.i < cur.text.length) return fail(cur, "unexpected trailing text");
69
+ return { ok: true, object: { keys: keys, values: values } };
70
+ }
71
+ if (mark !== 44) return fail(cur, "expected ',' or '}'");
72
+ cur.i++;
73
+ skip(cur);
74
+ if (cur.i < cur.text.length && cur.text.charCodeAt(cur.i) === 125) return fail(cur, "trailing comma");
75
+ }
76
+ return fail(cur, "expected '}'");
77
+ }
78
+
79
+ function parseValue(cur: Cur): JsonValue | string {
80
+ if (cur.i >= cur.text.length) return "expected a value";
81
+ const code = cur.text.charCodeAt(cur.i);
82
+ if (code === 116 && cur.text.slice(cur.i, cur.i + 4) === "true") {
83
+ const after = cur.i + 4;
84
+ if (after < cur.text.length && isIdent(cur.text.charCodeAt(after))) return "expected a value";
85
+ cur.i = after;
86
+ return { kind: "bool", value: true };
87
+ }
88
+ if (code === 102 && cur.text.slice(cur.i, cur.i + 5) === "false") {
89
+ const after = cur.i + 5;
90
+ if (after < cur.text.length && isIdent(cur.text.charCodeAt(after))) return "expected a value";
91
+ cur.i = after;
92
+ return { kind: "bool", value: false };
93
+ }
94
+ if (code === 110 && cur.text.slice(cur.i, cur.i + 4) === "null") return "null is not allowed";
95
+ if (code === 34) {
96
+ const text = parseString(cur);
97
+ if (text === null) return "unterminated string";
98
+ return { kind: "string", value: text };
99
+ }
100
+ if (code === 45 || isDigit(code)) {
101
+ const parsed = parseIntLiteral(cur);
102
+ if (parsed === null) return "expected an integer";
103
+ return { kind: "int", value: parsed };
104
+ }
105
+ if (code === 123 || code === 91) return "expected a boolean, integer, or string";
106
+ return "expected a value";
107
+ }
108
+
109
+ function parseIntLiteral(cur: Cur): number | null {
110
+ const start = cur.i;
111
+ let negative = false;
112
+ if (cur.text.charCodeAt(cur.i) === 45) {
113
+ negative = true;
114
+ cur.i++;
115
+ if (cur.i >= cur.text.length || !isDigit(cur.text.charCodeAt(cur.i))) {
116
+ cur.i = start;
117
+ return null;
118
+ }
119
+ }
120
+ if (cur.text.charCodeAt(cur.i) === 48) {
121
+ cur.i++;
122
+ if (cur.i < cur.text.length && isDigit(cur.text.charCodeAt(cur.i))) {
123
+ cur.i = start;
124
+ return null;
125
+ }
126
+ } else {
127
+ if (!isDigit(cur.text.charCodeAt(cur.i))) {
128
+ cur.i = start;
129
+ return null;
130
+ }
131
+ while (cur.i < cur.text.length && isDigit(cur.text.charCodeAt(cur.i))) cur.i++;
132
+ }
133
+ if (cur.i < cur.text.length) {
134
+ const code = cur.text.charCodeAt(cur.i);
135
+ if (code === 46 || code === 101 || code === 69) {
136
+ cur.i = start;
137
+ return null;
138
+ }
139
+ }
140
+ const digits = cur.text.slice(negative ? start + 1 : start, cur.i);
141
+ if (digits.length > 9) {
142
+ cur.i = start;
143
+ return null;
144
+ }
145
+ let value = 0;
146
+ for (let i = 0; i < digits.length; i++) value = value * 10 + (digits.charCodeAt(i) - 48);
147
+ return negative ? -value : value;
148
+ }
149
+
150
+ function parseString(cur: Cur): string | null {
151
+ if (cur.i >= cur.text.length || cur.text.charCodeAt(cur.i) !== 34) return null;
152
+ cur.i++;
153
+ let out = "";
154
+ while (cur.i < cur.text.length) {
155
+ const code = cur.text.charCodeAt(cur.i);
156
+ if (code === 34) {
157
+ cur.i++;
158
+ return out;
159
+ }
160
+ if (code === 10 || code === 13) return null;
161
+ if (code === 92) {
162
+ cur.i++;
163
+ if (cur.i >= cur.text.length) return null;
164
+ const esc = cur.text.charCodeAt(cur.i);
165
+ if (esc === 34 || esc === 92 || esc === 47) out += cur.text.slice(cur.i, cur.i + 1);
166
+ else if (esc === 110) out += "\n";
167
+ else if (esc === 114) out += "\r";
168
+ else if (esc === 116) out += "\t";
169
+ else if (esc === 117) {
170
+ if (cur.i + 4 >= cur.text.length) return null;
171
+ let hex = 0;
172
+ for (let h = 1; h <= 4; h++) {
173
+ const digit = hexValue(cur.text.charCodeAt(cur.i + h));
174
+ if (digit < 0) return null;
175
+ hex = hex * 16 + digit;
176
+ }
177
+ out += String.fromCharCode(hex);
178
+ cur.i += 4;
179
+ } else {
180
+ return null;
181
+ }
182
+ cur.i++;
183
+ continue;
184
+ }
185
+ out += cur.text.slice(cur.i, cur.i + 1);
186
+ cur.i++;
187
+ }
188
+ return null;
189
+ }
190
+
191
+ function skip(cur: Cur): void {
192
+ while (cur.i < cur.text.length) {
193
+ const code = cur.text.charCodeAt(cur.i);
194
+ if (code === 10) {
195
+ cur.line++;
196
+ cur.i++;
197
+ continue;
198
+ }
199
+ if (code === 32 || code === 9 || code === 13) {
200
+ cur.i++;
201
+ continue;
202
+ }
203
+ break;
204
+ }
205
+ }
206
+
207
+ function fail(cur: Cur, message: string): JsonResult {
208
+ return { ok: false, line: cur.line, message: message };
209
+ }
210
+
211
+ function quote(value: string): string {
212
+ return "\"" + value + "\"";
213
+ }
214
+
215
+ function isDigit(code: number): boolean {
216
+ return code >= 48 && code <= 57;
217
+ }
218
+
219
+ function isIdent(code: number): boolean {
220
+ return isDigit(code) || (code >= 65 && code <= 90) || (code >= 97 && code <= 122) || code === 95;
221
+ }
222
+
223
+ function hexValue(code: number): number {
224
+ if (code >= 48 && code <= 57) return code - 48;
225
+ if (code >= 97 && code <= 102) return code - 87;
226
+ if (code >= 65 && code <= 70) return code - 55;
227
+ return -1;
228
+ }