@nforma.ai/nforma 0.2.1

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 (215) hide show
  1. package/LICENSE +22 -0
  2. package/README.md +1024 -0
  3. package/agents/qgsd-codebase-mapper.md +764 -0
  4. package/agents/qgsd-debugger.md +1201 -0
  5. package/agents/qgsd-executor.md +472 -0
  6. package/agents/qgsd-integration-checker.md +443 -0
  7. package/agents/qgsd-phase-researcher.md +502 -0
  8. package/agents/qgsd-plan-checker.md +643 -0
  9. package/agents/qgsd-planner.md +1182 -0
  10. package/agents/qgsd-project-researcher.md +621 -0
  11. package/agents/qgsd-quorum-orchestrator.md +628 -0
  12. package/agents/qgsd-quorum-slot-worker.md +41 -0
  13. package/agents/qgsd-quorum-synthesizer.md +133 -0
  14. package/agents/qgsd-quorum-test-worker.md +37 -0
  15. package/agents/qgsd-quorum-worker.md +161 -0
  16. package/agents/qgsd-research-synthesizer.md +239 -0
  17. package/agents/qgsd-roadmapper.md +660 -0
  18. package/agents/qgsd-verifier.md +628 -0
  19. package/bin/accept-debug-invariant.cjs +165 -0
  20. package/bin/account-manager.cjs +719 -0
  21. package/bin/aggregate-requirements.cjs +466 -0
  22. package/bin/analyze-assumptions.cjs +757 -0
  23. package/bin/analyze-state-space.cjs +921 -0
  24. package/bin/attribute-trace-divergence.cjs +150 -0
  25. package/bin/auth-drivers/gh-cli.cjs +93 -0
  26. package/bin/auth-drivers/index.cjs +46 -0
  27. package/bin/auth-drivers/pool.cjs +67 -0
  28. package/bin/auth-drivers/simple.cjs +95 -0
  29. package/bin/autoClosePtoF.cjs +110 -0
  30. package/bin/blessed-terminal.cjs +350 -0
  31. package/bin/build-phase-index.cjs +472 -0
  32. package/bin/call-quorum-slot.cjs +541 -0
  33. package/bin/ccr-secure-config.cjs +99 -0
  34. package/bin/ccr-secure-start.cjs +83 -0
  35. package/bin/check-bundled-sdks.cjs +177 -0
  36. package/bin/check-coverage-guard.cjs +112 -0
  37. package/bin/check-liveness-fairness.cjs +95 -0
  38. package/bin/check-mcp-health.cjs +123 -0
  39. package/bin/check-provider-health.cjs +395 -0
  40. package/bin/check-results-exit.cjs +24 -0
  41. package/bin/check-spec-sync.cjs +360 -0
  42. package/bin/check-trace-redaction.cjs +271 -0
  43. package/bin/check-trace-schema-drift.cjs +99 -0
  44. package/bin/compareDrift.cjs +21 -0
  45. package/bin/conformance-schema.cjs +12 -0
  46. package/bin/count-scenarios.cjs +420 -0
  47. package/bin/debt-dedup.cjs +144 -0
  48. package/bin/debt-ledger.cjs +61 -0
  49. package/bin/debt-retention.cjs +76 -0
  50. package/bin/debt-state-machine.cjs +80 -0
  51. package/bin/detect-coverage-gaps.cjs +204 -0
  52. package/bin/detect-project-intent.cjs +362 -0
  53. package/bin/export-prism-constants.cjs +164 -0
  54. package/bin/extract-annotations.cjs +633 -0
  55. package/bin/extractFormalExpected.cjs +104 -0
  56. package/bin/fingerprint-drift.cjs +24 -0
  57. package/bin/fingerprint-issue.cjs +46 -0
  58. package/bin/formal-core.cjs +519 -0
  59. package/bin/formal-ref-linker.cjs +141 -0
  60. package/bin/formal-test-sync.cjs +788 -0
  61. package/bin/generate-formal-specs.cjs +588 -0
  62. package/bin/generate-petri-net.cjs +397 -0
  63. package/bin/generate-phase-spec.cjs +249 -0
  64. package/bin/generate-proposed-changes.cjs +194 -0
  65. package/bin/generate-tla-cfg.cjs +122 -0
  66. package/bin/generate-traceability-matrix.cjs +701 -0
  67. package/bin/generate-triage-bundle.cjs +300 -0
  68. package/bin/gh-account-rotate.cjs +34 -0
  69. package/bin/initialize-model-registry.cjs +105 -0
  70. package/bin/install-formal-tools.cjs +382 -0
  71. package/bin/install.js +2424 -0
  72. package/bin/isNumericThreshold.cjs +34 -0
  73. package/bin/issue-classifier.cjs +151 -0
  74. package/bin/levenshtein.cjs +74 -0
  75. package/bin/lint-formal-models.cjs +580 -0
  76. package/bin/load-baseline-requirements.cjs +275 -0
  77. package/bin/manage-agents-core.cjs +815 -0
  78. package/bin/migrate-formal-dir.cjs +172 -0
  79. package/bin/migrate-planning.cjs +206 -0
  80. package/bin/migrate-to-slots.cjs +255 -0
  81. package/bin/nForma.cjs +2726 -0
  82. package/bin/observe-config.cjs +353 -0
  83. package/bin/observe-debt-writer.cjs +140 -0
  84. package/bin/observe-handler-grafana.cjs +128 -0
  85. package/bin/observe-handler-internal.cjs +301 -0
  86. package/bin/observe-handler-logstash.cjs +153 -0
  87. package/bin/observe-handler-prometheus.cjs +185 -0
  88. package/bin/observe-handlers.cjs +436 -0
  89. package/bin/observe-registry.cjs +131 -0
  90. package/bin/observe-render.cjs +168 -0
  91. package/bin/planning-paths.cjs +167 -0
  92. package/bin/polyrepo.cjs +560 -0
  93. package/bin/prism-priority.cjs +153 -0
  94. package/bin/probe-quorum-slots.cjs +167 -0
  95. package/bin/promote-model.cjs +225 -0
  96. package/bin/propose-debug-invariants.cjs +165 -0
  97. package/bin/providers.json +392 -0
  98. package/bin/pty-proxy.py +129 -0
  99. package/bin/qgsd-solve.cjs +2477 -0
  100. package/bin/quorum-consensus-gate.cjs +238 -0
  101. package/bin/quorum-formal-context.cjs +183 -0
  102. package/bin/quorum-slot-dispatch.cjs +934 -0
  103. package/bin/read-policy.cjs +60 -0
  104. package/bin/requirement-map.cjs +63 -0
  105. package/bin/requirements-core.cjs +247 -0
  106. package/bin/resolve-cli.cjs +101 -0
  107. package/bin/review-mcp-logs.cjs +294 -0
  108. package/bin/run-account-manager-tlc.cjs +188 -0
  109. package/bin/run-account-pool-alloy.cjs +158 -0
  110. package/bin/run-alloy.cjs +153 -0
  111. package/bin/run-audit-alloy.cjs +187 -0
  112. package/bin/run-breaker-tlc.cjs +181 -0
  113. package/bin/run-formal-check.cjs +395 -0
  114. package/bin/run-formal-verify.cjs +701 -0
  115. package/bin/run-installer-alloy.cjs +188 -0
  116. package/bin/run-oauth-rotation-prism.cjs +132 -0
  117. package/bin/run-oscillation-tlc.cjs +202 -0
  118. package/bin/run-phase-tlc.cjs +228 -0
  119. package/bin/run-prism.cjs +446 -0
  120. package/bin/run-protocol-tlc.cjs +201 -0
  121. package/bin/run-quorum-composition-alloy.cjs +155 -0
  122. package/bin/run-sensitivity-sweep.cjs +231 -0
  123. package/bin/run-stop-hook-tlc.cjs +188 -0
  124. package/bin/run-tlc.cjs +467 -0
  125. package/bin/run-transcript-alloy.cjs +173 -0
  126. package/bin/run-uppaal.cjs +264 -0
  127. package/bin/secrets.cjs +134 -0
  128. package/bin/sensitivity-report.cjs +219 -0
  129. package/bin/sensitivity-sweep-feedback.cjs +194 -0
  130. package/bin/set-secret.cjs +29 -0
  131. package/bin/setup-telemetry-cron.sh +36 -0
  132. package/bin/sweepPtoF.cjs +63 -0
  133. package/bin/sync-baseline-requirements.cjs +290 -0
  134. package/bin/task-envelope.cjs +360 -0
  135. package/bin/telemetry-collector.cjs +229 -0
  136. package/bin/unified-mcp-server.mjs +735 -0
  137. package/bin/update-agents.cjs +369 -0
  138. package/bin/update-scoreboard.cjs +1134 -0
  139. package/bin/validate-debt-entry.cjs +207 -0
  140. package/bin/validate-invariant.cjs +419 -0
  141. package/bin/validate-memory.cjs +389 -0
  142. package/bin/validate-requirements-haiku.cjs +435 -0
  143. package/bin/validate-traces.cjs +438 -0
  144. package/bin/verify-formal-results.cjs +124 -0
  145. package/bin/verify-quorum-health.cjs +273 -0
  146. package/bin/write-check-result.cjs +106 -0
  147. package/bin/xstate-to-tla.cjs +483 -0
  148. package/bin/xstate-trace-walker.cjs +205 -0
  149. package/commands/qgsd/add-phase.md +43 -0
  150. package/commands/qgsd/add-requirement.md +24 -0
  151. package/commands/qgsd/add-todo.md +47 -0
  152. package/commands/qgsd/audit-milestone.md +37 -0
  153. package/commands/qgsd/check-todos.md +45 -0
  154. package/commands/qgsd/cleanup.md +18 -0
  155. package/commands/qgsd/close-formal-gaps.md +33 -0
  156. package/commands/qgsd/complete-milestone.md +136 -0
  157. package/commands/qgsd/debug.md +166 -0
  158. package/commands/qgsd/discuss-phase.md +83 -0
  159. package/commands/qgsd/execute-phase.md +117 -0
  160. package/commands/qgsd/fix-tests.md +27 -0
  161. package/commands/qgsd/formal-test-sync.md +32 -0
  162. package/commands/qgsd/health.md +22 -0
  163. package/commands/qgsd/help.md +22 -0
  164. package/commands/qgsd/insert-phase.md +32 -0
  165. package/commands/qgsd/join-discord.md +18 -0
  166. package/commands/qgsd/list-phase-assumptions.md +46 -0
  167. package/commands/qgsd/map-codebase.md +71 -0
  168. package/commands/qgsd/map-requirements.md +20 -0
  169. package/commands/qgsd/mcp-restart.md +176 -0
  170. package/commands/qgsd/mcp-set-model.md +134 -0
  171. package/commands/qgsd/mcp-setup.md +1371 -0
  172. package/commands/qgsd/mcp-status.md +274 -0
  173. package/commands/qgsd/mcp-update.md +238 -0
  174. package/commands/qgsd/new-milestone.md +44 -0
  175. package/commands/qgsd/new-project.md +42 -0
  176. package/commands/qgsd/observe.md +260 -0
  177. package/commands/qgsd/pause-work.md +38 -0
  178. package/commands/qgsd/plan-milestone-gaps.md +34 -0
  179. package/commands/qgsd/plan-phase.md +44 -0
  180. package/commands/qgsd/polyrepo.md +50 -0
  181. package/commands/qgsd/progress.md +24 -0
  182. package/commands/qgsd/queue.md +54 -0
  183. package/commands/qgsd/quick.md +133 -0
  184. package/commands/qgsd/quorum-test.md +275 -0
  185. package/commands/qgsd/quorum.md +707 -0
  186. package/commands/qgsd/reapply-patches.md +110 -0
  187. package/commands/qgsd/remove-phase.md +31 -0
  188. package/commands/qgsd/research-phase.md +189 -0
  189. package/commands/qgsd/resume-work.md +40 -0
  190. package/commands/qgsd/set-profile.md +34 -0
  191. package/commands/qgsd/settings.md +39 -0
  192. package/commands/qgsd/solve.md +565 -0
  193. package/commands/qgsd/sync-baselines.md +119 -0
  194. package/commands/qgsd/triage.md +233 -0
  195. package/commands/qgsd/update.md +37 -0
  196. package/commands/qgsd/verify-work.md +38 -0
  197. package/hooks/dist/config-loader.js +297 -0
  198. package/hooks/dist/conformance-schema.cjs +12 -0
  199. package/hooks/dist/gsd-context-monitor.js +64 -0
  200. package/hooks/dist/qgsd-check-update.js +62 -0
  201. package/hooks/dist/qgsd-circuit-breaker.js +682 -0
  202. package/hooks/dist/qgsd-precompact.js +156 -0
  203. package/hooks/dist/qgsd-prompt.js +653 -0
  204. package/hooks/dist/qgsd-session-start.js +122 -0
  205. package/hooks/dist/qgsd-slot-correlator.js +58 -0
  206. package/hooks/dist/qgsd-spec-regen.js +86 -0
  207. package/hooks/dist/qgsd-statusline.js +91 -0
  208. package/hooks/dist/qgsd-stop.js +553 -0
  209. package/hooks/dist/qgsd-token-collector.js +133 -0
  210. package/hooks/dist/unified-mcp-server.mjs +669 -0
  211. package/package.json +95 -0
  212. package/scripts/build-hooks.js +46 -0
  213. package/scripts/postinstall.js +48 -0
  214. package/scripts/secret-audit.sh +45 -0
  215. package/templates/qgsd.json +49 -0
@@ -0,0 +1,395 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+ // bin/run-formal-check.cjs
4
+ // Lightweight per-module formal checker for TLC, Alloy, PRISM.
5
+ // Invoked by Step 6.3 in quick.md to run model checkers after execution.
6
+ // Requirements: quick-130
7
+ //
8
+ // Usage:
9
+ // node bin/run-formal-check.cjs --modules=quorum
10
+ // node bin/run-formal-check.cjs --modules=quorum,tui-nav,breaker
11
+ //
12
+ // Exit codes:
13
+ // 0 if all checks passed or skipped (no counterexample)
14
+ // 1 if any check failed (counterexample or TLC error)
15
+ //
16
+ // Prerequisites:
17
+ // - Java >=17 (for TLC and Alloy)
18
+ // - .planning/formal/tla/tla2tools.jar
19
+ // - .planning/formal/alloy/org.alloytools.alloy.dist.jar
20
+ // - PRISM_BIN env var (optional; skipped if not set)
21
+
22
+ const { spawnSync } = require('child_process');
23
+ const fs = require('fs');
24
+ const path = require('path');
25
+
26
+ // ── Module to check mapping (hardcoded) ──────────────────────────────────
27
+ const MODULE_CHECKS = {
28
+ quorum: [
29
+ {
30
+ tool: 'tlc',
31
+ cmd: [
32
+ 'java', '-cp', '.planning/formal/tla/tla2tools.jar', 'tlc2.TLC',
33
+ '-config', '.planning/formal/tla/MCliveness.cfg',
34
+ '.planning/formal/tla/QGSDQuorum.tla',
35
+ '-workers', '1'
36
+ ]
37
+ },
38
+ {
39
+ tool: 'alloy',
40
+ cmd: [
41
+ 'java', '-jar', '.planning/formal/alloy/org.alloytools.alloy.dist.jar', 'exec',
42
+ '--output', '-', '--type', 'text', '--quiet',
43
+ '.planning/formal/alloy/quorum-votes.als'
44
+ ]
45
+ },
46
+ {
47
+ tool: 'prism',
48
+ cmd: null, // Set dynamically if PRISM_BIN is set
49
+ prismModel: '.planning/formal/prism/quorum.pm',
50
+ prismProps: '.planning/formal/prism/quorum.props'
51
+ }
52
+ ],
53
+ 'tui-nav': [
54
+ {
55
+ tool: 'tlc',
56
+ cmd: [
57
+ 'java', '-cp', '.planning/formal/tla/tla2tools.jar', 'tlc2.TLC',
58
+ '-config', '.planning/formal/tla/MCTUINavigation.cfg',
59
+ '.planning/formal/tla/TUINavigation.tla',
60
+ '-workers', '1'
61
+ ]
62
+ }
63
+ ],
64
+ breaker: [
65
+ {
66
+ tool: 'tlc',
67
+ cmd: [
68
+ 'java', '-cp', '.planning/formal/tla/tla2tools.jar', 'tlc2.TLC',
69
+ '-config', '.planning/formal/tla/MCbreaker.cfg',
70
+ '.planning/formal/tla/QGSDCircuitBreaker.tla',
71
+ '-workers', '1'
72
+ ]
73
+ }
74
+ ],
75
+ deliberation: [
76
+ {
77
+ tool: 'tlc',
78
+ cmd: [
79
+ 'java', '-cp', '.planning/formal/tla/tla2tools.jar', 'tlc2.TLC',
80
+ '-config', '.planning/formal/tla/MCdeliberation.cfg',
81
+ '.planning/formal/tla/QGSDDeliberation.tla',
82
+ '-workers', '1'
83
+ ]
84
+ }
85
+ ],
86
+ oscillation: [
87
+ {
88
+ tool: 'tlc',
89
+ cmd: [
90
+ 'java', '-cp', '.planning/formal/tla/tla2tools.jar', 'tlc2.TLC',
91
+ '-config', '.planning/formal/tla/MCoscillation.cfg',
92
+ '.planning/formal/tla/QGSDOscillation.tla',
93
+ '-workers', '1'
94
+ ]
95
+ }
96
+ ],
97
+ convergence: [
98
+ {
99
+ tool: 'tlc',
100
+ cmd: [
101
+ 'java', '-cp', '.planning/formal/tla/tla2tools.jar', 'tlc2.TLC',
102
+ '-config', '.planning/formal/tla/MCconvergence.cfg',
103
+ '.planning/formal/tla/QGSDConvergence.tla',
104
+ '-workers', '1'
105
+ ]
106
+ }
107
+ ],
108
+ prefilter: [
109
+ {
110
+ tool: 'tlc',
111
+ cmd: [
112
+ 'java', '-cp', '.planning/formal/tla/tla2tools.jar', 'tlc2.TLC',
113
+ '-config', '.planning/formal/tla/MCprefilter.cfg',
114
+ '.planning/formal/tla/QGSDPreFilter.tla',
115
+ '-workers', '1'
116
+ ]
117
+ }
118
+ ],
119
+ recruiting: [
120
+ {
121
+ tool: 'tlc',
122
+ cmd: [
123
+ 'java', '-cp', '.planning/formal/tla/tla2tools.jar', 'tlc2.TLC',
124
+ '-config', '.planning/formal/tla/MCrecruiting-safety.cfg',
125
+ '.planning/formal/tla/QGSDRecruiting.tla',
126
+ '-workers', '1'
127
+ ]
128
+ }
129
+ ],
130
+ 'account-manager': [
131
+ {
132
+ tool: 'tlc',
133
+ cmd: [
134
+ 'java', '-cp', '.planning/formal/tla/tla2tools.jar', 'tlc2.TLC',
135
+ '-config', '.planning/formal/tla/MCaccount-manager.cfg',
136
+ '.planning/formal/tla/QGSDAccountManager.tla',
137
+ '-workers', '1'
138
+ ]
139
+ }
140
+ ],
141
+ 'mcp-calls': [
142
+ {
143
+ tool: 'tlc',
144
+ cmd: [
145
+ 'java', '-cp', '.planning/formal/tla/tla2tools.jar', 'tlc2.TLC',
146
+ '-config', '.planning/formal/tla/MCMCPEnv.cfg',
147
+ '.planning/formal/tla/QGSDMCPEnv.tla',
148
+ '-workers', '1'
149
+ ]
150
+ }
151
+ ]
152
+ };
153
+
154
+ // ── Helper: Detect Java ──────────────────────────────────────────────────
155
+ function detectJava() {
156
+ const JAVA_HOME = process.env.JAVA_HOME;
157
+ let javaExe;
158
+
159
+ if (JAVA_HOME) {
160
+ javaExe = path.join(JAVA_HOME, 'bin', 'java');
161
+ if (fs.existsSync(javaExe)) {
162
+ return javaExe;
163
+ }
164
+ }
165
+
166
+ const probe = spawnSync('java', ['--version'], { encoding: 'utf8' });
167
+ if (!probe.error && probe.status === 0) {
168
+ return 'java';
169
+ }
170
+
171
+ return null;
172
+ }
173
+
174
+ // ── Helper: Check if file exists (fail-open on missing jar) ───────────────
175
+ function checkJarExists(jarPath) {
176
+ return fs.existsSync(jarPath);
177
+ }
178
+
179
+ // ── Helper: Run a single check ──────────────────────────────────────────────
180
+ function runCheck(module, checkDef, javaExe, cwd) {
181
+ const tool = checkDef.tool;
182
+ const startMs = Date.now();
183
+
184
+ if (tool === 'tlc') {
185
+ // TLC check
186
+ const cmd = checkDef.cmd[0];
187
+ const args = checkDef.cmd.slice(1);
188
+
189
+ const result = spawnSync(cmd, args, {
190
+ cwd,
191
+ stdio: 'pipe',
192
+ encoding: 'utf8',
193
+ timeout: 180000
194
+ });
195
+
196
+ const runtimeMs = Date.now() - startMs;
197
+ let status = 'pass';
198
+ let detail = '';
199
+
200
+ if (result.error) {
201
+ status = 'skipped';
202
+ detail = result.error.message;
203
+ } else if (result.status !== 0) {
204
+ status = 'fail';
205
+ detail = `Exit code ${result.status}`;
206
+ // Scan stderr for error indicators
207
+ if (result.stderr && result.stderr.includes('Error:')) {
208
+ detail += '; error in stderr';
209
+ }
210
+ }
211
+
212
+ return { module, tool, status, detail, runtimeMs };
213
+ } else if (tool === 'alloy') {
214
+ // Alloy check
215
+ const cmd = checkDef.cmd[0];
216
+ const args = checkDef.cmd.slice(1);
217
+
218
+ const result = spawnSync(cmd, args, {
219
+ cwd,
220
+ stdio: 'pipe',
221
+ encoding: 'utf8',
222
+ timeout: 180000
223
+ });
224
+
225
+ const runtimeMs = Date.now() - startMs;
226
+ let status = 'pass';
227
+ let detail = '';
228
+
229
+ if (result.error) {
230
+ status = 'skipped';
231
+ detail = result.error.message;
232
+ } else if (result.status !== 0) {
233
+ status = 'fail';
234
+ detail = `Exit code ${result.status}`;
235
+ } else if (result.stdout && result.stdout.includes('Counterexample')) {
236
+ status = 'fail';
237
+ detail = 'Counterexample found';
238
+ }
239
+
240
+ return { module, tool, status, detail, runtimeMs };
241
+ } else if (tool === 'prism') {
242
+ // PRISM check
243
+ const prismBin = process.env.PRISM_BIN;
244
+ if (!prismBin || !fs.existsSync(prismBin)) {
245
+ return {
246
+ module,
247
+ tool: 'prism',
248
+ status: 'skipped',
249
+ detail: 'PRISM_BIN not set or binary not found',
250
+ runtimeMs: 0
251
+ };
252
+ }
253
+
254
+ const result = spawnSync(prismBin, [checkDef.prismModel], {
255
+ cwd,
256
+ stdio: 'pipe',
257
+ encoding: 'utf8',
258
+ timeout: 180000
259
+ });
260
+
261
+ const runtimeMs = Date.now() - startMs;
262
+ let status = 'pass';
263
+ let detail = '';
264
+
265
+ if (result.error) {
266
+ status = 'fail';
267
+ detail = result.error.message;
268
+ } else if (result.status !== 0) {
269
+ status = 'fail';
270
+ detail = `Exit code ${result.status}`;
271
+ }
272
+
273
+ return { module, tool, status, detail, runtimeMs };
274
+ }
275
+
276
+ return { module, tool, status: 'skipped', detail: 'Unknown tool', runtimeMs: 0 };
277
+ }
278
+
279
+ // ── Main execution ──────────────────────────────────────────────────────────
280
+ if (require.main === module) {
281
+ // Parse --modules argument
282
+ const args = process.argv.slice(2);
283
+ let modules = [];
284
+
285
+ const modulesArg = args.find(a => a.startsWith('--modules='));
286
+ if (modulesArg) {
287
+ modules = modulesArg.split('=')[1].split(',').map(m => m.trim());
288
+ }
289
+
290
+ if (modules.length === 0) {
291
+ process.stderr.write('[run-formal-check] Error: --modules argument required\n');
292
+ process.stderr.write('[run-formal-check] Usage: node bin/run-formal-check.cjs --modules=quorum,tui-nav\n');
293
+ process.exit(1);
294
+ }
295
+
296
+ const cwd = process.cwd();
297
+
298
+ // Detect Java (fail-open)
299
+ const javaExe = detectJava();
300
+ if (!javaExe) {
301
+ process.stderr.write('[run-formal-check] WARNING: java not found — skipping all TLC/Alloy checks\n');
302
+ // All checks become skipped
303
+ const allResults = [];
304
+ for (const module of modules) {
305
+ if (!MODULE_CHECKS[module]) {
306
+ process.stderr.write(`[run-formal-check] WARNING: unknown module "${module}" — skipping\n`);
307
+ continue;
308
+ }
309
+ for (const checkDef of MODULE_CHECKS[module]) {
310
+ allResults.push({
311
+ module,
312
+ tool: checkDef.tool,
313
+ status: 'skipped',
314
+ detail: 'java not found',
315
+ runtimeMs: 0
316
+ });
317
+ }
318
+ }
319
+
320
+ const skipped = allResults.length;
321
+ const passed = 0;
322
+ const failed = 0;
323
+
324
+ process.stdout.write(`[run-formal-check] Results: ${allResults.length} checks, ${passed} passed, ${failed} failed, ${skipped} skipped\n`);
325
+ process.stdout.write(`FORMAL_CHECK_RESULT=${JSON.stringify({ passed, failed, skipped, counterexamples: [] })}\n`);
326
+ process.exit(0);
327
+ }
328
+
329
+ // Check jar files (fail-open on missing)
330
+ const tlcJarPath = path.join(cwd, '.planning', 'formal', 'tla', 'tla2tools.jar');
331
+ const alloyJarPath = path.join(cwd, '.planning', 'formal', 'alloy', 'org.alloytools.alloy.dist.jar');
332
+
333
+ const tlcJarExists = checkJarExists(tlcJarPath);
334
+ const alloyJarExists = checkJarExists(alloyJarPath);
335
+
336
+ // Run all checks
337
+ const allResults = [];
338
+
339
+ for (const module of modules) {
340
+ if (!MODULE_CHECKS[module]) {
341
+ process.stderr.write(`[run-formal-check] WARNING: unknown module "${module}" — skipping\n`);
342
+ continue;
343
+ }
344
+
345
+ for (const checkDef of MODULE_CHECKS[module]) {
346
+ if (checkDef.tool === 'tlc' && !tlcJarExists) {
347
+ process.stderr.write(`[run-formal-check] WARNING: tla2tools.jar not found — skipping ${module} TLC check\n`);
348
+ allResults.push({
349
+ module,
350
+ tool: 'tlc',
351
+ status: 'skipped',
352
+ detail: 'tla2tools.jar not found',
353
+ runtimeMs: 0
354
+ });
355
+ } else if (checkDef.tool === 'alloy' && !alloyJarExists) {
356
+ process.stderr.write(`[run-formal-check] WARNING: org.alloytools.alloy.dist.jar not found — skipping ${module} Alloy check\n`);
357
+ allResults.push({
358
+ module,
359
+ tool: 'alloy',
360
+ status: 'skipped',
361
+ detail: 'org.alloytools.alloy.dist.jar not found',
362
+ runtimeMs: 0
363
+ });
364
+ } else {
365
+ const result = runCheck(module, checkDef, javaExe, cwd);
366
+ allResults.push(result);
367
+ }
368
+ }
369
+ }
370
+
371
+ // Count results
372
+ const passed = allResults.filter(r => r.status === 'pass').length;
373
+ const failed = allResults.filter(r => r.status === 'fail').length;
374
+ const skipped = allResults.filter(r => r.status === 'skipped').length;
375
+ const counterexamples = allResults
376
+ .filter(r => r.status === 'fail')
377
+ .map(r => `${r.module}:${r.tool}`);
378
+
379
+ // Output summary
380
+ process.stdout.write(`[run-formal-check] Results: ${allResults.length} checks, ${passed} passed, ${failed} failed, ${skipped} skipped\n`);
381
+
382
+ // Machine-readable result line
383
+ process.stdout.write(`FORMAL_CHECK_RESULT=${JSON.stringify({ passed, failed, skipped, counterexamples })}\n`);
384
+
385
+ // Exit code: 0 if no failures, 1 if any failed
386
+ const exitCode = failed > 0 ? 1 : 0;
387
+ process.exit(exitCode);
388
+ }
389
+
390
+ module.exports = {
391
+ detectJava,
392
+ checkJarExists,
393
+ runCheck,
394
+ MODULE_CHECKS
395
+ };