@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,300 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+ // bin/generate-triage-bundle.cjs
4
+ // Reads .planning/formal/check-results.ndjson, writes .planning/formal/diff-report.md and .planning/formal/suspects.md.
5
+ // Requirements: TRIAGE-01
6
+
7
+ const fs = require('fs');
8
+ const path = require('path');
9
+ const { parseNDJSON, groupByFormalism } = require('./verify-formal-results.cjs');
10
+
11
+ /**
12
+ * Parse .planning/formal/check-results.ndjson relative to cwd.
13
+ * Uses parseNDJSON from verify-formal-results.cjs (fail-open, skips malformed lines).
14
+ * @returns {object[]} — parsed result records (may be empty)
15
+ */
16
+ function parseCurrentNDJSON() {
17
+ const ndjsonPath = path.join(process.cwd(), '.planning', 'formal', 'check-results.ndjson');
18
+ return parseNDJSON(ndjsonPath);
19
+ }
20
+
21
+ /**
22
+ * Filter and sort suspects from current NDJSON records.
23
+ * Suspects = result=fail OR triage_tags.length > 0.
24
+ * Sort: fail > warn+tags > inconclusive+tags > everything else.
25
+ * @param {object[]} results — parsed NDJSON records
26
+ * @returns {object[]} — suspect records, sorted by priority
27
+ */
28
+ function generateSuspects(results) {
29
+ const suspects = results.filter(r =>
30
+ r.result === 'fail' ||
31
+ (Array.isArray(r.triage_tags) && r.triage_tags.length > 0)
32
+ );
33
+
34
+ const priority = (r) => {
35
+ if (r.result === 'fail') return 0;
36
+ if (r.result === 'warn' && r.triage_tags && r.triage_tags.length > 0) return 1;
37
+ return 2;
38
+ };
39
+
40
+ suspects.sort((a, b) => priority(a) - priority(b));
41
+ return suspects;
42
+ }
43
+
44
+ /**
45
+ * Load previous run's check_id->result snapshot from embedded JSON block in diff-report.md.
46
+ * Returns {} if no previous report exists or no JSON block found (fail-open).
47
+ * @returns {Object.<string, string>} — map of check_id to result string
48
+ */
49
+ function loadPreviousSnapshot() {
50
+ const diffPath = path.join(process.cwd(), '.planning', 'formal', 'diff-report.md');
51
+ let content;
52
+ try {
53
+ content = fs.readFileSync(diffPath, 'utf8');
54
+ } catch (_) {
55
+ return {}; // no previous report — first run
56
+ }
57
+ // Extract JSON block from "## Previous Run (for next comparison)" section
58
+ const match = content.match(/```json\s*\n([\s\S]*?)```/);
59
+ if (!match) return {};
60
+ try {
61
+ const parsed = JSON.parse(match[1]);
62
+ if (typeof parsed === 'object' && parsed !== null && !Array.isArray(parsed)) {
63
+ return parsed;
64
+ }
65
+ } catch (_) { /* malformed JSON block — treat as first run */ }
66
+ return {};
67
+ }
68
+
69
+ /**
70
+ * Compute per-check delta between current and previous run.
71
+ * @param {object[]} currentResults — parsed NDJSON records
72
+ * @param {Object.<string, string>} previousSnapshot — check_id->result from last run
73
+ * @returns {{ transitioned: object[], newChecks: object[], removedChecks: string[], unchanged: number }}
74
+ */
75
+ function computeDeltas(currentResults, previousSnapshot) {
76
+ const currentMap = {};
77
+ for (const r of currentResults) {
78
+ if (r.check_id) currentMap[r.check_id] = r;
79
+ }
80
+
81
+ const transitioned = [];
82
+ const newChecks = [];
83
+ let unchanged = 0;
84
+
85
+ for (const r of currentResults) {
86
+ if (!r.check_id) continue;
87
+ if (!(r.check_id in previousSnapshot)) {
88
+ newChecks.push(r);
89
+ } else if (previousSnapshot[r.check_id] !== r.result) {
90
+ transitioned.push({ ...r, previousResult: previousSnapshot[r.check_id] });
91
+ } else {
92
+ unchanged++;
93
+ }
94
+ }
95
+
96
+ const removedChecks = Object.keys(previousSnapshot).filter(id => !(id in currentMap));
97
+
98
+ return { transitioned, newChecks, removedChecks, unchanged };
99
+ }
100
+
101
+ /**
102
+ * Build check_id->result map from current results (for embedding in diff-report.md).
103
+ * @param {object[]} currentResults
104
+ * @returns {Object.<string, string>}
105
+ */
106
+ function buildCurrentSnapshot(currentResults) {
107
+ const snap = {};
108
+ for (const r of currentResults) {
109
+ if (r.check_id) snap[r.check_id] = r.result;
110
+ }
111
+ return snap;
112
+ }
113
+
114
+ /**
115
+ * Generate diff-report.md content.
116
+ * @param {object[]} currentResults
117
+ * @param {Object} deltas — from computeDeltas
118
+ * @param {boolean} isFirstRun
119
+ * @returns {string} — full markdown content
120
+ */
121
+ function formatDiffReport(currentResults, deltas, isFirstRun) {
122
+ const timestamp = new Date().toISOString();
123
+ const grouped = groupByFormalism(currentResults);
124
+
125
+ // Overall status: fail > inconclusive > pass
126
+ let overallStatus = 'pass';
127
+ for (const counts of Object.values(grouped)) {
128
+ if (counts.fail > 0) { overallStatus = 'fail'; break; }
129
+ if (counts.inconclusive > 0 && overallStatus !== 'fail') overallStatus = 'inconclusive';
130
+ }
131
+
132
+ const pass = currentResults.filter(r => r.result === 'pass').length;
133
+ const fail = currentResults.filter(r => r.result === 'fail').length;
134
+ const other = currentResults.length - pass - fail;
135
+
136
+ const lines = [
137
+ '# Formal Verification Diff Report',
138
+ '',
139
+ `**Generated:** ${timestamp}`,
140
+ `**Current Run:** ${pass} pass, ${fail} fail` + (other > 0 ? `, ${other} warn/inconclusive` : ''),
141
+ `**Previous Run:** ${isFirstRun ? 'first run — no previous snapshot' : `${deltas.transitioned.length} transitions, ${deltas.newChecks.length} new, ${deltas.removedChecks.length} removed`}`,
142
+ `**Overall Status:** ${overallStatus}`,
143
+ '',
144
+ ];
145
+
146
+ if (isFirstRun) {
147
+ lines.push('**Status:** First run — no previous snapshot to compare against.');
148
+ lines.push('');
149
+ lines.push('All checks are new. Run again after a second verification to see deltas.');
150
+ lines.push('');
151
+ } else {
152
+ // Transitioned checks
153
+ if (deltas.transitioned.length > 0) {
154
+ lines.push('## Transitioned Checks');
155
+ lines.push('');
156
+ lines.push('| Check | Previous | Current | Summary |');
157
+ lines.push('|-------|----------|---------|---------|');
158
+ for (const r of deltas.transitioned) {
159
+ const summary = (r.summary || '').substring(0, 60);
160
+ lines.push(`| ${r.check_id} | ${r.previousResult} | ${r.result} | ${summary} |`);
161
+ }
162
+ lines.push('');
163
+ }
164
+
165
+ // New checks
166
+ if (deltas.newChecks.length > 0) {
167
+ lines.push('## New Checks');
168
+ lines.push('');
169
+ lines.push('| Check | Result | Summary |');
170
+ lines.push('|-------|--------|---------|');
171
+ for (const r of deltas.newChecks) {
172
+ const summary = (r.summary || '').substring(0, 60);
173
+ lines.push(`| ${r.check_id} | ${r.result} | ${summary} |`);
174
+ }
175
+ lines.push('');
176
+ }
177
+
178
+ // Removed checks
179
+ if (deltas.removedChecks.length > 0) {
180
+ lines.push('## Removed Checks');
181
+ lines.push('');
182
+ for (const id of deltas.removedChecks) {
183
+ lines.push(`- ${id}: no longer run`);
184
+ }
185
+ lines.push('');
186
+ }
187
+
188
+ // Unchanged — summary only
189
+ if (deltas.unchanged > 0) {
190
+ lines.push('## Unchanged Checks');
191
+ lines.push('');
192
+ lines.push(`${deltas.unchanged} check(s) unchanged from previous run — no action needed.`);
193
+ lines.push('');
194
+ }
195
+ }
196
+
197
+ // Embedded snapshot — REQUIRED for next run delta computation
198
+ const snapshot = buildCurrentSnapshot(currentResults);
199
+ lines.push('## Previous Run (for next comparison)');
200
+ lines.push('');
201
+ lines.push('```json');
202
+ lines.push(JSON.stringify(snapshot));
203
+ lines.push('```');
204
+
205
+ return lines.join('\n');
206
+ }
207
+
208
+ /**
209
+ * Generate suspects.md content.
210
+ * @param {object[]} suspects — from generateSuspects (pre-sorted)
211
+ * @returns {string} — full markdown content
212
+ */
213
+ function formatSuspectsReport(suspects) {
214
+ const timestamp = new Date().toISOString();
215
+ const lines = [
216
+ '# Formal Verification Suspects',
217
+ '',
218
+ `**Generated:** ${timestamp}`,
219
+ `**Total Suspects:** ${suspects.length}`,
220
+ '',
221
+ ];
222
+
223
+ if (suspects.length === 0) {
224
+ lines.push('No suspects found — all checks passed without triage tags.');
225
+ return lines.join('\n');
226
+ }
227
+
228
+ const critical = suspects.filter(r => r.result === 'fail');
229
+ const warnings = suspects.filter(r => r.result === 'warn' && Array.isArray(r.triage_tags) && r.triage_tags.length > 0);
230
+ const inconclusive = suspects.filter(r => r.result === 'inconclusive' && Array.isArray(r.triage_tags) && r.triage_tags.length > 0);
231
+ const other = suspects.filter(r =>
232
+ !['fail'].includes(r.result) &&
233
+ !(r.result === 'warn' && Array.isArray(r.triage_tags) && r.triage_tags.length > 0) &&
234
+ !(r.result === 'inconclusive' && Array.isArray(r.triage_tags) && r.triage_tags.length > 0)
235
+ );
236
+
237
+ function renderGroup(title, group) {
238
+ if (group.length === 0) return;
239
+ lines.push(`## ${title}`);
240
+ lines.push('');
241
+ for (const r of group) {
242
+ lines.push(`### ${r.check_id}`);
243
+ lines.push(`- **Property:** ${r.property || 'N/A'}`);
244
+ lines.push(`- **Summary:** ${r.summary || 'N/A'}`);
245
+ lines.push(`- **Runtime:** ${r.runtime_ms != null ? r.runtime_ms + 'ms' : 'N/A'}`);
246
+ lines.push(`- **Tags:** ${Array.isArray(r.triage_tags) && r.triage_tags.length > 0 ? r.triage_tags.join(', ') : 'none'}`);
247
+ lines.push('');
248
+ }
249
+ }
250
+
251
+ renderGroup('Critical Failures (result=fail)', critical);
252
+ renderGroup('Warnings with Tags (result=warn)', warnings);
253
+ renderGroup('Inconclusive with Tags (result=inconclusive)', inconclusive);
254
+ renderGroup('Other Suspects', other);
255
+
256
+ return lines.join('\n');
257
+ }
258
+
259
+ function writeFileSafe(filePath, content) {
260
+ try {
261
+ fs.mkdirSync(path.dirname(filePath), { recursive: true });
262
+ fs.writeFileSync(filePath, content, 'utf8');
263
+ process.stderr.write('[generate-triage-bundle] Wrote: ' + filePath + '\n');
264
+ } catch (err) {
265
+ process.stderr.write('[generate-triage-bundle] Warning: could not write ' + filePath + ': ' + err.message + '\n');
266
+ }
267
+ }
268
+
269
+ /**
270
+ * Main CLI entry point.
271
+ * Reads .planning/formal/check-results.ndjson relative to process.cwd().
272
+ * Writes .planning/formal/diff-report.md and .planning/formal/suspects.md.
273
+ */
274
+ function main() {
275
+ const currentResults = parseCurrentNDJSON();
276
+ const previousSnapshot = loadPreviousSnapshot();
277
+ const isFirstRun = Object.keys(previousSnapshot).length === 0;
278
+ const deltas = computeDeltas(currentResults, previousSnapshot);
279
+ const suspects = generateSuspects(currentResults);
280
+
281
+ const formalDir = path.join(process.cwd(), '.planning', 'formal');
282
+ writeFileSafe(path.join(formalDir, 'diff-report.md'), formatDiffReport(currentResults, deltas, isFirstRun));
283
+ writeFileSafe(path.join(formalDir, 'suspects.md'), formatSuspectsReport(suspects));
284
+ }
285
+
286
+ // ── Exports ───────────────────────────────────────────────────────────────────
287
+ // Exported for testing without CLI execution side effects.
288
+ if (require.main === module) {
289
+ main();
290
+ }
291
+
292
+ module.exports = {
293
+ parseCurrentNDJSON,
294
+ generateSuspects,
295
+ computeDeltas,
296
+ loadPreviousSnapshot,
297
+ buildCurrentSnapshot,
298
+ formatDiffReport,
299
+ formatSuspectsReport,
300
+ };
@@ -0,0 +1,34 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+
4
+ /**
5
+ * gh-account-rotate.cjs — rotate to the next gh auth account
6
+ *
7
+ * Called by call-quorum-slot.cjs as oauth_rotation.rotate_cmd for copilot-1.
8
+ * Delegates all gh auth status parsing to the gh-cli auth driver (single source of truth).
9
+ *
10
+ * Usage:
11
+ * node bin/gh-account-rotate.cjs
12
+ */
13
+
14
+ const { spawnSync } = require('child_process');
15
+ const { parseGhStatus } = require('./auth-drivers/gh-cli.cjs');
16
+
17
+ const { accounts, active } = parseGhStatus();
18
+
19
+ if (accounts.length < 2) {
20
+ process.stderr.write('[gh-rotate] Only one gh account — nothing to rotate\n');
21
+ process.exit(0);
22
+ }
23
+
24
+ const idx = active ? accounts.indexOf(active) : 0;
25
+ const next = accounts[(idx + 1) % accounts.length];
26
+
27
+ process.stderr.write(`[gh-rotate] ${active ?? '?'} → ${next}\n`);
28
+
29
+ const r = spawnSync('gh', ['auth', 'switch', '--user', next, '--hostname', 'github.com'], {
30
+ stdio: 'inherit',
31
+ encoding: 'utf8',
32
+ });
33
+
34
+ process.exit(r.status ?? 0);
@@ -0,0 +1,105 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+ // bin/initialize-model-registry.cjs
4
+ // One-time idempotent initialization of .planning/formal/model-registry.json.
5
+ //
6
+ // Scans .planning/formal/tla/, .planning/formal/alloy/, and .planning/formal/prism/ for canonical model files
7
+ // and creates model-registry.json with provenance metadata for each.
8
+ //
9
+ // Usage:
10
+ // node bin/initialize-model-registry.cjs
11
+ //
12
+ // Idempotent: if .planning/formal/model-registry.json already exists, exits 0 silently.
13
+ // Run this once after cloning the repo, before any generate/promote/debug operations.
14
+
15
+ const fs = require('fs');
16
+ const path = require('path');
17
+
18
+ const ROOT = path.join(__dirname, '..');
19
+ const REGISTRY_PATH = path.join(ROOT, '.planning', 'formal', 'model-registry.json');
20
+
21
+ // ── Idempotent guard ──────────────────────────────────────────────────────────
22
+ if (fs.existsSync(REGISTRY_PATH)) {
23
+ process.exit(0);
24
+ }
25
+
26
+ // ── Scan .planning/formal/ subdirectories ───────────────────────────────────────────────
27
+ const SCAN_DIRS = [
28
+ { dir: path.join(ROOT, '.planning', 'formal', 'tla'), exts: ['.tla'] },
29
+ { dir: path.join(ROOT, '.planning', 'formal', 'alloy'), exts: ['.als'] },
30
+ { dir: path.join(ROOT, '.planning', 'formal', 'prism'), exts: ['.pm'] },
31
+ ];
32
+
33
+ // Files to skip — not canonical specs
34
+ const SKIP_PATTERNS = [
35
+ /_TTrace_/, // TLC error trace artifacts
36
+ ];
37
+
38
+ const models = {};
39
+
40
+ for (const { dir, exts } of SCAN_DIRS) {
41
+ if (!fs.existsSync(dir)) continue;
42
+
43
+ let entries;
44
+ try {
45
+ entries = fs.readdirSync(dir);
46
+ } catch (err) {
47
+ process.stderr.write('[initialize-model-registry] Cannot read dir ' + dir + ': ' + err.message + '\n');
48
+ continue;
49
+ }
50
+
51
+ for (const filename of entries) {
52
+ const ext = path.extname(filename);
53
+ if (!exts.includes(ext)) continue;
54
+
55
+ // Skip non-canonical files
56
+ const skip = SKIP_PATTERNS.some(pattern => pattern.test(filename));
57
+ if (skip) continue;
58
+
59
+ const absPath = path.join(dir, filename);
60
+
61
+ // Compute registry key — relative from ROOT, no leading './' or '/'
62
+ let key = path.relative(ROOT, absPath).replace(/\\/g, '/');
63
+ if (key.startsWith('./')) key = key.slice(2);
64
+ if (key.startsWith('/')) key = key.slice(1);
65
+
66
+ // Validate key format
67
+ if (key.startsWith('/') || key.startsWith('./')) {
68
+ process.stderr.write('[initialize-model-registry] WARNING: key has invalid prefix: ' + key + '\n');
69
+ continue;
70
+ }
71
+
72
+ const mtime = fs.statSync(absPath).mtime.toISOString();
73
+
74
+ // Detect generated files (xstate-derived)
75
+ const isGenerated = filename.includes('xstate');
76
+
77
+ models[key] = {
78
+ version: 1,
79
+ last_updated: mtime,
80
+ update_source: isGenerated ? 'generate' : 'manual',
81
+ source_id: isGenerated ? 'generate:tla-from-xstate' : null,
82
+ session_id: null,
83
+ description: ''
84
+ };
85
+ }
86
+ }
87
+
88
+ // ── Build registry with sorted keys ──────────────────────────────────────────
89
+ const sortedModels = {};
90
+ for (const key of Object.keys(models).sort()) {
91
+ sortedModels[key] = models[key];
92
+ }
93
+
94
+ const registry = {
95
+ version: '1.0',
96
+ last_sync: new Date().toISOString(),
97
+ models: sortedModels
98
+ };
99
+
100
+ // ── Write registry ─────────────────────────────────────────────────────────
101
+ fs.mkdirSync(path.dirname(REGISTRY_PATH), { recursive: true });
102
+ fs.writeFileSync(REGISTRY_PATH, JSON.stringify(registry, null, 2), 'utf8');
103
+
104
+ const count = Object.keys(sortedModels).length;
105
+ process.stdout.write('[initialize-model-registry] Created .planning/formal/model-registry.json with ' + count + ' entries\n');