@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,194 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+ // bin/generate-proposed-changes.cjs
4
+ // PLAN-01: Reads must_haves: truths: from a single PLAN.md file and generates
5
+ // a proposed-changes.tla scratch spec with INVARIANT/PROPERTY stubs in {phaseDir}/.formal/.
6
+ // Also generates MCProposedChanges.cfg for TLC invocation.
7
+ //
8
+ // Reuses parsePlanFrontmatter and classifyTruth from generate-phase-spec.cjs.
9
+ //
10
+ // Usage:
11
+ // node bin/generate-proposed-changes.cjs <path-to-PLAN.md> [--dry-run]
12
+ //
13
+ // Output: {phaseDir}/.formal/proposed-changes.tla + MCProposedChanges.cfg
14
+
15
+ const fs = require('fs');
16
+ const path = require('path');
17
+
18
+ const { parsePlanFrontmatter, classifyTruth } = require('./generate-phase-spec.cjs');
19
+
20
+ /**
21
+ * Generate a TLA+ proposed-changes spec from a single PLAN.md file.
22
+ *
23
+ * @param {string} planFilePath - Absolute path to a PLAN.md file
24
+ * @returns {{ generated: boolean, reason?: string, specPath?: string, truthCount?: number, classifications?: Array<{truth: string, kind: string, propName: string}> }}
25
+ */
26
+ function generateProposedChanges(planFilePath) {
27
+ const content = fs.readFileSync(planFilePath, 'utf8');
28
+ const fm = parsePlanFrontmatter(content);
29
+ const phase = fm.phase || 'unknown';
30
+ const truths = fm.truths || [];
31
+
32
+ if (truths.length === 0) {
33
+ return { generated: false, reason: 'no truths' };
34
+ }
35
+
36
+ const classifications = [];
37
+ const timestamp = new Date().toISOString();
38
+
39
+ // Build TLA+ module
40
+ let spec = `---- MODULE ProposedChanges ----
41
+ \\* Source: must_haves: truths: from PLAN.md YAML frontmatter
42
+ \\* Generated by bin/generate-proposed-changes.cjs -- PLAN-01
43
+ \\* Phase: ${phase}
44
+ \\* Source file: ${planFilePath}
45
+ \\* Generated: ${timestamp}
46
+
47
+ EXTENDS Naturals, FiniteSets, TLC
48
+
49
+ `;
50
+
51
+ // ReqNN-to-Truth index mapping block
52
+ spec += `\\* === ReqNN-to-Truth Mapping ===\n`;
53
+ truths.forEach((truth, idx) => {
54
+ const propName = `Req${String(idx + 1).padStart(2, '0')}`;
55
+ spec += `\\* ${propName} -> Truth[${idx}]: "${truth}"\n`;
56
+ });
57
+ spec += `\\* ================================\n\n`;
58
+
59
+ // Minimal state machine
60
+ spec += `VARIABLES state
61
+
62
+ Init == state = "INIT"
63
+
64
+ Next == state' = state
65
+
66
+ Spec == Init /\\ [][Next]_<<state>>
67
+
68
+ `;
69
+
70
+ // Generate stubs for each truth
71
+ truths.forEach((truth, idx) => {
72
+ const kind = classifyTruth(truth);
73
+ const propName = `Req${String(idx + 1).padStart(2, '0')}`;
74
+ classifications.push({ truth, kind, propName });
75
+
76
+ // Escape for TLA+ comment
77
+ const label = truth.replace(/\\/g, '\\\\').replace(/"/g, '\\"');
78
+ spec += `${propName} == TRUE \\* "${label}"\n\n`;
79
+ });
80
+
81
+ spec += `====`;
82
+
83
+ // Determine output directory: phaseDir/.formal/
84
+ const phaseDir = path.dirname(planFilePath);
85
+ const formalDir = path.join(phaseDir, '.formal');
86
+ fs.mkdirSync(formalDir, { recursive: true });
87
+
88
+ const specPath = path.join(formalDir, 'ProposedChanges.tla');
89
+ fs.writeFileSync(specPath, spec, 'utf8');
90
+
91
+ return {
92
+ generated: true,
93
+ specPath,
94
+ truthCount: truths.length,
95
+ classifications,
96
+ };
97
+ }
98
+
99
+ /**
100
+ * Generate MCProposedChanges.cfg from a proposed-changes.tla file.
101
+ *
102
+ * @param {string} specPath - Absolute path to proposed-changes.tla
103
+ * @returns {{ cfgPath: string }}
104
+ */
105
+ function generateTlaCfg(specPath) {
106
+ const specContent = fs.readFileSync(specPath, 'utf8');
107
+ const lines = specContent.split('\n');
108
+
109
+ let cfg = 'SPECIFICATION Spec\n';
110
+
111
+ // Parse each ReqNN line to determine if it was classified as INVARIANT or PROPERTY
112
+ // We read the classifications from the mapping block comments
113
+ const mappingRegex = /^\\?\*\s+(Req\d{2})\s+->\s+Truth\[(\d+)\]:\s+"(.+)"/;
114
+ const propNames = [];
115
+
116
+ for (const line of lines) {
117
+ const match = line.match(mappingRegex);
118
+ if (match) {
119
+ propNames.push({ propName: match[1], truth: match[3] });
120
+ }
121
+ }
122
+
123
+ // Classify each truth to determine INVARIANT vs PROPERTY for the .cfg
124
+ for (const { propName, truth } of propNames) {
125
+ const kind = classifyTruth(truth);
126
+ if (kind === 'INVARIANT') {
127
+ cfg += `INVARIANT ${propName}\n`;
128
+ } else {
129
+ cfg += `PROPERTY ${propName}\n`;
130
+ }
131
+ }
132
+
133
+ const cfgPath = path.join(path.dirname(specPath), 'MCProposedChanges.cfg');
134
+ fs.writeFileSync(cfgPath, cfg, 'utf8');
135
+
136
+ return { cfgPath };
137
+ }
138
+
139
+ // ── CLI entrypoint ────────────────────────────────────────────────────────────
140
+ if (require.main === module) {
141
+ const args = process.argv.slice(2).filter(a => !a.startsWith('--'));
142
+ const flags = process.argv.slice(2).filter(a => a.startsWith('--'));
143
+ const isDryRun = flags.includes('--dry-run');
144
+
145
+ if (args.length === 0) {
146
+ process.stderr.write('[generate-proposed-changes] Usage: node bin/generate-proposed-changes.cjs <path-to-PLAN.md> [--dry-run]\n');
147
+ process.stderr.write('[generate-proposed-changes] Generates proposed-changes.tla + MCProposedChanges.cfg in {phaseDir}/.formal/\n');
148
+ process.exit(1);
149
+ }
150
+
151
+ const planFilePath = path.resolve(args[0]);
152
+ if (!fs.existsSync(planFilePath)) {
153
+ process.stderr.write('[generate-proposed-changes] Error: file not found: ' + planFilePath + '\n');
154
+ process.exit(1);
155
+ }
156
+
157
+ if (isDryRun) {
158
+ // Read and parse without writing
159
+ const content = fs.readFileSync(planFilePath, 'utf8');
160
+ const fm = parsePlanFrontmatter(content);
161
+ const truths = fm.truths || [];
162
+
163
+ if (truths.length === 0) {
164
+ process.stderr.write('[generate-proposed-changes] No truths found in ' + planFilePath + '\n');
165
+ process.exit(0);
166
+ }
167
+
168
+ process.stdout.write('[generate-proposed-changes] DRY-RUN\n');
169
+ process.stdout.write('[generate-proposed-changes] Plan: ' + planFilePath + '\n');
170
+ process.stdout.write('[generate-proposed-changes] Truths: ' + truths.length + '\n');
171
+ truths.forEach((t, i) => {
172
+ const kind = classifyTruth(t);
173
+ process.stdout.write(' Req' + String(i + 1).padStart(2, '0') + ' [' + kind + ']: ' + t + '\n');
174
+ });
175
+ } else {
176
+ const result = generateProposedChanges(planFilePath);
177
+
178
+ if (!result.generated) {
179
+ process.stderr.write('[generate-proposed-changes] ' + result.reason + ' in ' + planFilePath + '\n');
180
+ process.exit(0);
181
+ }
182
+
183
+ const { cfgPath } = generateTlaCfg(result.specPath);
184
+
185
+ process.stdout.write('[generate-proposed-changes] Spec: ' + result.specPath + '\n');
186
+ process.stdout.write('[generate-proposed-changes] Config: ' + cfgPath + '\n');
187
+ process.stdout.write('[generate-proposed-changes] Truths: ' + result.truthCount + '\n');
188
+ result.classifications.forEach(c => {
189
+ process.stdout.write(' ' + c.propName + ' [' + c.kind + ']: ' + c.truth + '\n');
190
+ });
191
+ }
192
+ }
193
+
194
+ module.exports = { generateProposedChanges, generateTlaCfg };
@@ -0,0 +1,122 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+ // bin/generate-tla-cfg.cjs
4
+ // Generates TLA+ model configuration files from the XState machine.
5
+ //
6
+ // The XState machine (src/machines/qgsd-workflow.machine.ts) is the SOURCE OF TRUTH.
7
+ // MCsafety.cfg and MCliveness.cfg are generated artifacts — do not edit them by hand.
8
+ // To change MaxDeliberation, update the XState machine context default instead.
9
+ //
10
+ // Usage:
11
+ // node bin/generate-tla-cfg.cjs # writes MCsafety.cfg + MCliveness.cfg
12
+ // node bin/generate-tla-cfg.cjs --dry # print output without writing
13
+ //
14
+ // Why agent counts are hardcoded here (not derived from code):
15
+ // SAFETY_AGENTS=5 and LIVENESS_AGENTS=3 are model-checking parameters, not
16
+ // runtime constants. 5 matches the quorum slot count; 3 keeps the liveness
17
+ // state space tractable (liveness + symmetry = incompatible in TLC).
18
+
19
+ const fs = require('fs');
20
+ const path = require('path');
21
+
22
+ const ROOT = path.join(__dirname, '..');
23
+ const DRY = process.argv.includes('--dry');
24
+
25
+ // ── Model-checking parameters (stable, not derived from code) ─────────────────
26
+ const SAFETY_AGENTS = 5; // N for MCsafety — matches QGSD quorum slot count
27
+ const LIVENESS_AGENTS = 3; // N for MCliveness — smaller for tractable liveness
28
+
29
+ // ── Extract MaxDeliberation from the XState machine ───────────────────────────
30
+ const machineFile = path.join(ROOT, 'src', 'machines', 'qgsd-workflow.machine.ts');
31
+ if (!fs.existsSync(machineFile)) {
32
+ process.stderr.write('[generate-tla-cfg] XState machine not found: ' + machineFile + '\n');
33
+ process.exit(1);
34
+ }
35
+ const machineSrc = fs.readFileSync(machineFile, 'utf8');
36
+
37
+ const maxDelibMatch = machineSrc.match(/maxDeliberation:\s*(\d+)/);
38
+ if (!maxDelibMatch) {
39
+ process.stderr.write(
40
+ '[generate-tla-cfg] Could not find maxDeliberation in XState context defaults.\n' +
41
+ '[generate-tla-cfg] Expected: maxDeliberation: <number>\n'
42
+ );
43
+ process.exit(1);
44
+ }
45
+ const maxDeliberation = parseInt(maxDelibMatch[1], 10);
46
+ process.stdout.write('[generate-tla-cfg] Extracted maxDeliberation=' + maxDeliberation + ' from XState machine\n');
47
+
48
+ // ── Generate agent declarations ───────────────────────────────────────────────
49
+ function agentDecls(n) {
50
+ const lines = [];
51
+ for (let i = 1; i <= n; i++) {
52
+ lines.push(' a' + i + ' = a' + i);
53
+ }
54
+ return lines.join('\n');
55
+ }
56
+
57
+ function agentsSet(n) {
58
+ const names = [];
59
+ for (let i = 1; i <= n; i++) names.push('a' + i);
60
+ return '{' + names.join(', ') + '}';
61
+ }
62
+
63
+ // ── Build MCsafety.cfg ────────────────────────────────────────────────────────
64
+ const safetyCfg = [
65
+ '\\* .planning/formal/tla/MCsafety.cfg',
66
+ '\\* GENERATED — do not edit by hand.',
67
+ '\\* Source of truth: src/machines/qgsd-workflow.machine.ts',
68
+ '\\* Regenerate: node bin/generate-tla-cfg.cjs',
69
+ '\\*',
70
+ '\\* TLC safety model: N=' + SAFETY_AGENTS + ' agents, symmetry reduction, no liveness check.',
71
+ '\\* Run: node bin/run-tlc.cjs MCsafety (requires Java >=17 and tla2tools.jar)',
72
+ '\\* NOTE: SYMMETRY requires model values — agents declared as model values (a1=a1 etc.)',
73
+ 'SPECIFICATION Spec',
74
+ 'CONSTANTS',
75
+ agentDecls(SAFETY_AGENTS),
76
+ ' Agents = ' + agentsSet(SAFETY_AGENTS),
77
+ ' MaxDeliberation = ' + maxDeliberation,
78
+ 'SYMMETRY AgentSymmetry',
79
+ 'INVARIANT TypeOK',
80
+ 'INVARIANT MinQuorumMet',
81
+ 'PROPERTY NoInvalidTransition',
82
+ 'CHECK_DEADLOCK FALSE',
83
+ '',
84
+ ].join('\n');
85
+
86
+ // ── Build MCliveness.cfg ──────────────────────────────────────────────────────
87
+ const livenessCfg = [
88
+ '\\* .planning/formal/tla/MCliveness.cfg',
89
+ '\\* GENERATED — do not edit by hand.',
90
+ '\\* Source of truth: src/machines/qgsd-workflow.machine.ts',
91
+ '\\* Regenerate: node bin/generate-tla-cfg.cjs',
92
+ '\\*',
93
+ '\\* TLC liveness model: N=' + LIVENESS_AGENTS + ' agents, NO symmetry (incompatible with liveness), PROPERTY only.',
94
+ '\\* Use -workers 1 for liveness (defensive against older TLC multi-worker liveness bugs).',
95
+ '\\* Run: node bin/run-tlc.cjs MCliveness (requires Java >=17 and tla2tools.jar)',
96
+ '\\* NOTE: Agents set to ' + LIVENESS_AGENTS + ' model values for tractable liveness checking.',
97
+ 'SPECIFICATION Spec',
98
+ 'CONSTANTS',
99
+ agentDecls(LIVENESS_AGENTS),
100
+ ' Agents = ' + agentsSet(LIVENESS_AGENTS),
101
+ ' MaxDeliberation = ' + maxDeliberation,
102
+ 'PROPERTY EventualConsensus',
103
+ 'CHECK_DEADLOCK FALSE',
104
+ '',
105
+ ].join('\n');
106
+
107
+ // ── Write or print ────────────────────────────────────────────────────────────
108
+ const safetyPath = path.join(ROOT, '.planning', 'formal', 'tla', 'MCsafety.cfg');
109
+ const livenessPath = path.join(ROOT, '.planning', 'formal', 'tla', 'MCliveness.cfg');
110
+
111
+ if (DRY) {
112
+ process.stdout.write('\n--- MCsafety.cfg ---\n' + safetyCfg);
113
+ process.stdout.write('\n--- MCliveness.cfg ---\n' + livenessCfg);
114
+ } else {
115
+ fs.writeFileSync(safetyPath, safetyCfg, 'utf8');
116
+ fs.writeFileSync(livenessPath, livenessCfg, 'utf8');
117
+ process.stdout.write('[generate-tla-cfg] Written: .planning/formal/tla/MCsafety.cfg\n');
118
+ process.stdout.write('[generate-tla-cfg] Written: .planning/formal/tla/MCliveness.cfg\n');
119
+ }
120
+
121
+ process.stdout.write('[generate-tla-cfg] MaxDeliberation=' + maxDeliberation +
122
+ ' SafetyAgents=' + SAFETY_AGENTS + ' LivenessAgents=' + LIVENESS_AGENTS + '\n');