@nforma.ai/nforma 0.2.1 → 0.29.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 (193) hide show
  1. package/README.md +2 -2
  2. package/agents/{qgsd-codebase-mapper.md → nf-codebase-mapper.md} +1 -1
  3. package/agents/{qgsd-debugger.md → nf-debugger.md} +3 -3
  4. package/agents/{qgsd-executor.md → nf-executor.md} +14 -14
  5. package/agents/{qgsd-integration-checker.md → nf-integration-checker.md} +1 -1
  6. package/agents/{qgsd-phase-researcher.md → nf-phase-researcher.md} +6 -6
  7. package/agents/{qgsd-plan-checker.md → nf-plan-checker.md} +9 -9
  8. package/agents/{qgsd-planner.md → nf-planner.md} +9 -9
  9. package/agents/{qgsd-project-researcher.md → nf-project-researcher.md} +2 -2
  10. package/agents/{qgsd-quorum-orchestrator.md → nf-quorum-orchestrator.md} +33 -33
  11. package/agents/{qgsd-quorum-slot-worker.md → nf-quorum-slot-worker.md} +3 -3
  12. package/agents/{qgsd-quorum-synthesizer.md → nf-quorum-synthesizer.md} +3 -3
  13. package/agents/{qgsd-quorum-test-worker.md → nf-quorum-test-worker.md} +1 -1
  14. package/agents/{qgsd-quorum-worker.md → nf-quorum-worker.md} +6 -6
  15. package/agents/{qgsd-research-synthesizer.md → nf-research-synthesizer.md} +5 -5
  16. package/agents/{qgsd-roadmapper.md → nf-roadmapper.md} +3 -3
  17. package/agents/{qgsd-verifier.md → nf-verifier.md} +8 -8
  18. package/bin/accept-debug-invariant.cjs +2 -2
  19. package/bin/account-manager.cjs +10 -10
  20. package/bin/aggregate-requirements.cjs +1 -1
  21. package/bin/analyze-assumptions.cjs +3 -3
  22. package/bin/analyze-state-space.cjs +14 -14
  23. package/bin/assumption-register.cjs +146 -0
  24. package/bin/attribute-trace-divergence.cjs +1 -1
  25. package/bin/auth-drivers/gh-cli.cjs +1 -1
  26. package/bin/auth-drivers/pool.cjs +1 -1
  27. package/bin/autoClosePtoF.cjs +3 -3
  28. package/bin/budget-tracker.cjs +77 -0
  29. package/bin/build-layer-manifest.cjs +153 -0
  30. package/bin/call-quorum-slot.cjs +3 -3
  31. package/bin/ccr-secure-config.cjs +5 -5
  32. package/bin/check-bundled-sdks.cjs +1 -1
  33. package/bin/check-mcp-health.cjs +1 -1
  34. package/bin/check-provider-health.cjs +6 -6
  35. package/bin/check-spec-sync.cjs +26 -26
  36. package/bin/check-trace-schema-drift.cjs +5 -5
  37. package/bin/conformance-schema.cjs +2 -2
  38. package/bin/cross-layer-dashboard.cjs +297 -0
  39. package/bin/design-impact.cjs +377 -0
  40. package/bin/detect-coverage-gaps.cjs +7 -7
  41. package/bin/failure-mode-catalog.cjs +227 -0
  42. package/bin/failure-taxonomy.cjs +177 -0
  43. package/bin/formal-scope-scan.cjs +179 -0
  44. package/bin/gate-a-grounding.cjs +334 -0
  45. package/bin/gate-b-abstraction.cjs +243 -0
  46. package/bin/gate-c-validation.cjs +166 -0
  47. package/bin/generate-formal-specs.cjs +17 -17
  48. package/bin/generate-petri-net.cjs +3 -3
  49. package/bin/generate-tla-cfg.cjs +5 -5
  50. package/bin/git-heatmap.cjs +571 -0
  51. package/bin/harness-diagnostic.cjs +326 -0
  52. package/bin/hazard-model.cjs +261 -0
  53. package/bin/install-formal-tools.cjs +1 -1
  54. package/bin/install.js +184 -139
  55. package/bin/instrumentation-map.cjs +178 -0
  56. package/bin/invariant-catalog.cjs +437 -0
  57. package/bin/issue-classifier.cjs +2 -2
  58. package/bin/load-baseline-requirements.cjs +4 -4
  59. package/bin/manage-agents-core.cjs +32 -32
  60. package/bin/migrate-to-slots.cjs +39 -39
  61. package/bin/mismatch-register.cjs +217 -0
  62. package/bin/nForma.cjs +176 -81
  63. package/bin/{qgsd-solve.cjs → nf-solve.cjs} +327 -14
  64. package/bin/observe-config.cjs +8 -0
  65. package/bin/observe-debt-writer.cjs +1 -1
  66. package/bin/observe-handler-deps.cjs +356 -0
  67. package/bin/observe-handler-grafana.cjs +2 -17
  68. package/bin/observe-handler-internal.cjs +5 -5
  69. package/bin/observe-handler-logstash.cjs +2 -17
  70. package/bin/observe-handler-prometheus.cjs +2 -17
  71. package/bin/observe-handler-upstream.cjs +251 -0
  72. package/bin/observe-handlers.cjs +12 -33
  73. package/bin/observe-render.cjs +68 -22
  74. package/bin/observe-utils.cjs +37 -0
  75. package/bin/observed-fsm.cjs +324 -0
  76. package/bin/planning-paths.cjs +6 -0
  77. package/bin/polyrepo.cjs +1 -1
  78. package/bin/probe-quorum-slots.cjs +1 -1
  79. package/bin/promote-gate-maturity.cjs +274 -0
  80. package/bin/promote-model.cjs +1 -1
  81. package/bin/propose-debug-invariants.cjs +1 -1
  82. package/bin/quorum-cache.cjs +144 -0
  83. package/bin/quorum-consensus-gate.cjs +1 -1
  84. package/bin/quorum-preflight.cjs +89 -0
  85. package/bin/quorum-slot-dispatch.cjs +6 -6
  86. package/bin/requirements-core.cjs +1 -1
  87. package/bin/review-mcp-logs.cjs +1 -1
  88. package/bin/risk-heatmap.cjs +151 -0
  89. package/bin/run-account-manager-tlc.cjs +4 -4
  90. package/bin/run-account-pool-alloy.cjs +2 -2
  91. package/bin/run-alloy.cjs +2 -2
  92. package/bin/run-audit-alloy.cjs +2 -2
  93. package/bin/run-breaker-tlc.cjs +3 -3
  94. package/bin/run-formal-check.cjs +9 -9
  95. package/bin/run-formal-verify.cjs +30 -9
  96. package/bin/run-installer-alloy.cjs +2 -2
  97. package/bin/run-oscillation-tlc.cjs +4 -4
  98. package/bin/run-phase-tlc.cjs +1 -1
  99. package/bin/run-protocol-tlc.cjs +4 -4
  100. package/bin/run-quorum-composition-alloy.cjs +2 -2
  101. package/bin/run-sensitivity-sweep.cjs +2 -2
  102. package/bin/run-stop-hook-tlc.cjs +3 -3
  103. package/bin/run-tlc.cjs +21 -21
  104. package/bin/run-transcript-alloy.cjs +2 -2
  105. package/bin/secrets.cjs +5 -5
  106. package/bin/security-sweep.cjs +238 -0
  107. package/bin/sensitivity-report.cjs +3 -3
  108. package/bin/set-secret.cjs +5 -5
  109. package/bin/setup-telemetry-cron.sh +3 -3
  110. package/bin/stall-detector.cjs +126 -0
  111. package/bin/state-candidates.cjs +206 -0
  112. package/bin/sync-baseline-requirements.cjs +1 -1
  113. package/bin/telemetry-collector.cjs +1 -1
  114. package/bin/test-changed.cjs +111 -0
  115. package/bin/test-recipe-gen.cjs +250 -0
  116. package/bin/trace-corpus-stats.cjs +211 -0
  117. package/bin/unified-mcp-server.mjs +3 -3
  118. package/bin/update-scoreboard.cjs +1 -1
  119. package/bin/validate-memory.cjs +2 -2
  120. package/bin/validate-traces.cjs +10 -10
  121. package/bin/verify-quorum-health.cjs +66 -5
  122. package/bin/xstate-to-tla.cjs +4 -4
  123. package/bin/xstate-trace-walker.cjs +3 -3
  124. package/commands/{qgsd → nf}/add-phase.md +3 -3
  125. package/commands/{qgsd → nf}/add-requirement.md +3 -3
  126. package/commands/{qgsd → nf}/add-todo.md +3 -3
  127. package/commands/{qgsd → nf}/audit-milestone.md +4 -4
  128. package/commands/{qgsd → nf}/check-todos.md +3 -3
  129. package/commands/{qgsd → nf}/cleanup.md +3 -3
  130. package/commands/{qgsd → nf}/close-formal-gaps.md +2 -2
  131. package/commands/{qgsd → nf}/complete-milestone.md +9 -9
  132. package/commands/{qgsd → nf}/debug.md +9 -9
  133. package/commands/{qgsd → nf}/discuss-phase.md +3 -3
  134. package/commands/{qgsd → nf}/execute-phase.md +15 -15
  135. package/commands/{qgsd → nf}/fix-tests.md +3 -3
  136. package/commands/{qgsd → nf}/formal-test-sync.md +1 -1
  137. package/commands/{qgsd → nf}/health.md +3 -3
  138. package/commands/{qgsd → nf}/help.md +3 -3
  139. package/commands/{qgsd → nf}/insert-phase.md +3 -3
  140. package/commands/nf/join-discord.md +18 -0
  141. package/commands/{qgsd → nf}/list-phase-assumptions.md +2 -2
  142. package/commands/{qgsd → nf}/map-codebase.md +7 -7
  143. package/commands/{qgsd → nf}/map-requirements.md +3 -3
  144. package/commands/{qgsd → nf}/mcp-restart.md +3 -3
  145. package/commands/{qgsd → nf}/mcp-set-model.md +8 -8
  146. package/commands/{qgsd → nf}/mcp-setup.md +63 -63
  147. package/commands/{qgsd → nf}/mcp-status.md +3 -3
  148. package/commands/{qgsd → nf}/mcp-update.md +7 -7
  149. package/commands/{qgsd → nf}/new-milestone.md +8 -8
  150. package/commands/{qgsd → nf}/new-project.md +8 -8
  151. package/commands/{qgsd → nf}/observe.md +49 -16
  152. package/commands/{qgsd → nf}/pause-work.md +3 -3
  153. package/commands/{qgsd → nf}/plan-milestone-gaps.md +5 -5
  154. package/commands/{qgsd → nf}/plan-phase.md +6 -6
  155. package/commands/{qgsd → nf}/polyrepo.md +2 -2
  156. package/commands/{qgsd → nf}/progress.md +3 -3
  157. package/commands/{qgsd → nf}/queue.md +2 -2
  158. package/commands/{qgsd → nf}/quick.md +8 -8
  159. package/commands/{qgsd → nf}/quorum-test.md +10 -10
  160. package/commands/{qgsd → nf}/quorum.md +36 -86
  161. package/commands/{qgsd → nf}/reapply-patches.md +2 -2
  162. package/commands/{qgsd → nf}/remove-phase.md +3 -3
  163. package/commands/{qgsd → nf}/research-phase.md +12 -12
  164. package/commands/{qgsd → nf}/resume-work.md +3 -3
  165. package/commands/nf/review-requirements.md +31 -0
  166. package/commands/{qgsd → nf}/set-profile.md +3 -3
  167. package/commands/{qgsd → nf}/settings.md +6 -6
  168. package/commands/{qgsd → nf}/solve.md +35 -35
  169. package/commands/{qgsd → nf}/sync-baselines.md +4 -4
  170. package/commands/{qgsd → nf}/triage.md +10 -10
  171. package/commands/{qgsd → nf}/update.md +3 -3
  172. package/commands/{qgsd → nf}/verify-work.md +5 -5
  173. package/hooks/dist/config-loader.js +188 -32
  174. package/hooks/dist/conformance-schema.cjs +2 -2
  175. package/hooks/dist/gsd-context-monitor.js +118 -13
  176. package/hooks/dist/{qgsd-check-update.js → nf-check-update.js} +5 -5
  177. package/hooks/dist/{qgsd-circuit-breaker.js → nf-circuit-breaker.js} +35 -24
  178. package/hooks/dist/{qgsd-precompact.js → nf-precompact.js} +13 -13
  179. package/hooks/dist/{qgsd-prompt.js → nf-prompt.js} +110 -33
  180. package/hooks/dist/nf-session-start.js +185 -0
  181. package/hooks/dist/{qgsd-slot-correlator.js → nf-slot-correlator.js} +13 -5
  182. package/hooks/dist/{qgsd-spec-regen.js → nf-spec-regen.js} +17 -8
  183. package/hooks/dist/{qgsd-statusline.js → nf-statusline.js} +12 -3
  184. package/hooks/dist/{qgsd-stop.js → nf-stop.js} +152 -18
  185. package/hooks/dist/{qgsd-token-collector.js → nf-token-collector.js} +12 -4
  186. package/hooks/dist/unified-mcp-server.mjs +2 -2
  187. package/package.json +6 -4
  188. package/scripts/build-hooks.js +13 -6
  189. package/scripts/secret-audit.sh +1 -1
  190. package/scripts/verify-hooks-sync.cjs +90 -0
  191. package/templates/{qgsd.json → nf.json} +4 -4
  192. package/commands/qgsd/join-discord.md +0 -18
  193. package/hooks/dist/qgsd-session-start.js +0 -122
@@ -0,0 +1,144 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * quorum-cache.cjs — Cache infrastructure for quorum results.
5
+ *
6
+ * Provides deterministic cache key computation (SHA-256), file-based read/write
7
+ * with TTL validation, and invalidation logic based on git HEAD and quorum
8
+ * composition changes.
9
+ *
10
+ * Cache files are stored in .planning/.quorum-cache/ (gitignored).
11
+ * All operations fail-open: errors return null/undefined, never throw.
12
+ */
13
+
14
+ const crypto = require('node:crypto');
15
+ const fs = require('node:fs');
16
+ const path = require('node:path');
17
+ const { spawnSync } = require('node:child_process');
18
+
19
+ /**
20
+ * Compute a deterministic SHA-256 cache key from quorum inputs.
21
+ *
22
+ * @param {string} prompt - The user prompt string
23
+ * @param {string} context - Additional context string
24
+ * @param {Array<{slot: string}>} slots - Slot objects with .slot property
25
+ * @param {Array} configQuorumActive - quorum_active array from config
26
+ * @param {string} gitHead - Current git HEAD hash
27
+ * @returns {string} Full hex SHA-256 digest
28
+ */
29
+ function computeCacheKey(prompt, context, slots, configQuorumActive, gitHead) {
30
+ const sortedSlotNames = (slots || [])
31
+ .map(s => s.slot)
32
+ .sort()
33
+ .join(',');
34
+
35
+ const configHash = crypto
36
+ .createHash('sha256')
37
+ .update(JSON.stringify(configQuorumActive || []))
38
+ .digest('hex')
39
+ .slice(0, 16);
40
+
41
+ const payload = [
42
+ String(prompt || ''),
43
+ String(context || ''),
44
+ sortedSlotNames,
45
+ configHash,
46
+ String(gitHead || ''),
47
+ ].join('\x00');
48
+
49
+ return crypto.createHash('sha256').update(payload).digest('hex');
50
+ }
51
+
52
+ /**
53
+ * Get current git HEAD hash.
54
+ * Runs `git rev-parse HEAD` with a 3s timeout. Fail-open.
55
+ *
56
+ * @returns {string} Trimmed HEAD hash or empty string on failure
57
+ */
58
+ function getGitHead() {
59
+ try {
60
+ const result = spawnSync('git', ['rev-parse', 'HEAD'], {
61
+ timeout: 3000,
62
+ encoding: 'utf8',
63
+ stdio: ['ignore', 'pipe', 'ignore'],
64
+ });
65
+ return (result.stdout || '').trim();
66
+ } catch (_) {
67
+ return '';
68
+ }
69
+ }
70
+
71
+ /**
72
+ * Read a cache entry from disk.
73
+ * Returns parsed entry if valid (version 1, not expired, has completed field).
74
+ * Fail-open: returns null on any error or invalid state.
75
+ *
76
+ * @param {string} cacheKey - The cache key (hex digest)
77
+ * @param {string} cacheDir - Directory containing cache files
78
+ * @returns {object|null} Parsed cache entry or null
79
+ */
80
+ function readCache(cacheKey, cacheDir) {
81
+ try {
82
+ const filePath = path.join(cacheDir, `${cacheKey}.json`);
83
+ const raw = fs.readFileSync(filePath, 'utf8');
84
+ const entry = JSON.parse(raw);
85
+
86
+ // Version check
87
+ if (entry.version !== 1) return null;
88
+
89
+ // Must have completed field (pending entries are cache misses)
90
+ if (!entry.completed) return null;
91
+
92
+ // TTL check
93
+ const age = Date.now() - new Date(entry.created).getTime();
94
+ if (age > entry.ttl_ms) return null;
95
+
96
+ return entry;
97
+ } catch (_) {
98
+ return null;
99
+ }
100
+ }
101
+
102
+ /**
103
+ * Write a cache entry to disk. Creates cacheDir if needed. Fail-open.
104
+ *
105
+ * @param {string} cacheKey - The cache key (hex digest)
106
+ * @param {object} entry - Cache entry object
107
+ * @param {string} cacheDir - Directory to write cache files
108
+ */
109
+ function writeCache(cacheKey, entry, cacheDir) {
110
+ try {
111
+ fs.mkdirSync(cacheDir, { recursive: true });
112
+ const filePath = path.join(cacheDir, `${cacheKey}.json`);
113
+ fs.writeFileSync(filePath, JSON.stringify(entry, null, 2), 'utf8');
114
+ } catch (_) {
115
+ // Fail-open: swallow errors
116
+ }
117
+ }
118
+
119
+ /**
120
+ * Validate a cache entry against current state.
121
+ * Returns false if git HEAD changed, quorum composition changed, or TTL expired.
122
+ *
123
+ * @param {object} entry - Cache entry to validate
124
+ * @param {string} currentGitHead - Current git HEAD hash
125
+ * @param {Array} currentQuorumActive - Current quorum_active config
126
+ * @returns {boolean} True if cache entry is still valid
127
+ */
128
+ function isCacheValid(entry, currentGitHead, currentQuorumActive) {
129
+ if (!entry) return false;
130
+
131
+ // Git HEAD must match
132
+ if (entry.git_head !== currentGitHead) return false;
133
+
134
+ // Quorum composition must match
135
+ if (JSON.stringify(entry.quorum_active) !== JSON.stringify(currentQuorumActive)) return false;
136
+
137
+ // TTL must not be expired
138
+ const age = Date.now() - new Date(entry.created).getTime();
139
+ if (age > entry.ttl_ms) return false;
140
+
141
+ return true;
142
+ }
143
+
144
+ module.exports = { computeCacheKey, readCache, writeCache, getGitHead, isCacheValid };
@@ -154,7 +154,7 @@ function readEarlyEscalationThreshold(configPaths) {
154
154
  const DEFAULT = 0.10;
155
155
  const paths = configPaths || [
156
156
  path.join(process.cwd(), '.planning', 'config.json'),
157
- path.join(process.cwd(), '.planning', 'qgsd.json'),
157
+ path.join(process.cwd(), '.planning', 'nf.json'),
158
158
  ];
159
159
  for (const cfgPath of paths) {
160
160
  try {
@@ -0,0 +1,89 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+
4
+ /**
5
+ * quorum-preflight.cjs — extract quorum config and team identity from nf.json + providers.json
6
+ *
7
+ * Replaces three inline `node -e` snippets in quorum.md that caused shell-escaping
8
+ * failures (LLMs escaping `!` as `\!` inside node -e strings).
9
+ *
10
+ * Usage:
11
+ * node quorum-preflight.cjs --quorum-active # → JSON array of active slot names
12
+ * node quorum-preflight.cjs --max-quorum-size # → integer (default 3)
13
+ * node quorum-preflight.cjs --team # → JSON { slotName: { model } }
14
+ * node quorum-preflight.cjs --all # → JSON { quorum_active, max_quorum_size, team }
15
+ *
16
+ * All modes read from ~/.claude/nf.json (global) merged with $CWD/.claude/nf.json (project).
17
+ * --team and --all also read providers.json (same search logic as call-quorum-slot.cjs).
18
+ *
19
+ * Exit code: always 0. Output: JSON to stdout.
20
+ */
21
+
22
+ const fs = require('fs');
23
+ const path = require('path');
24
+ const os = require('os');
25
+
26
+ // ─── Read merged nf.json config ─────────────────────────────────────────────
27
+ function readConfig() {
28
+ const globalCfg = path.join(os.homedir(), '.claude', 'nf.json');
29
+ const projCfg = path.join(process.cwd(), '.claude', 'nf.json');
30
+ let cfg = {};
31
+ for (const f of [globalCfg, projCfg]) {
32
+ try { Object.assign(cfg, JSON.parse(fs.readFileSync(f, 'utf8'))); } catch (_) {}
33
+ }
34
+ return cfg;
35
+ }
36
+
37
+ // ─── Find providers.json (mirrors call-quorum-slot.cjs / probe-quorum-slots.cjs) ──
38
+ function findProviders() {
39
+ const searchPaths = [
40
+ path.join(__dirname, 'providers.json'),
41
+ path.join(os.homedir(), '.claude', 'nf-bin', 'providers.json'),
42
+ ];
43
+ try {
44
+ const claudeJson = JSON.parse(fs.readFileSync(path.join(os.homedir(), '.claude.json'), 'utf8'));
45
+ const u1args = claudeJson?.mcpServers?.['unified-1']?.args ?? [];
46
+ const serverScript = u1args.find(a => typeof a === 'string' && a.endsWith('unified-mcp-server.mjs'));
47
+ if (serverScript) searchPaths.unshift(path.join(path.dirname(serverScript), 'providers.json'));
48
+ } catch (_) {}
49
+ for (const p of searchPaths) {
50
+ try {
51
+ if (fs.existsSync(p)) return JSON.parse(fs.readFileSync(p, 'utf8')).providers;
52
+ } catch (_) {}
53
+ }
54
+ return [];
55
+ }
56
+
57
+ // ─── Build team JSON from providers + config ────────────────────────────────
58
+ function buildTeam(providers, active) {
59
+ const team = {};
60
+ for (const p of providers) {
61
+ if (active.length > 0 && !active.includes(p.name)) continue;
62
+ team[p.name] = { model: p.model };
63
+ }
64
+ return team;
65
+ }
66
+
67
+ // ─── Main ───────────────────────────────────────────────────────────────────
68
+ const mode = process.argv[2] || '--all';
69
+ const cfg = readConfig();
70
+
71
+ if (mode === '--quorum-active') {
72
+ console.log(JSON.stringify(cfg.quorum_active || []));
73
+ } else if (mode === '--max-quorum-size') {
74
+ console.log(cfg.max_quorum_size ?? 3);
75
+ } else if (mode === '--team') {
76
+ const providers = findProviders();
77
+ const active = cfg.quorum_active || [];
78
+ console.log(JSON.stringify(buildTeam(providers, active)));
79
+ } else if (mode === '--all') {
80
+ const providers = findProviders();
81
+ const active = cfg.quorum_active || [];
82
+ const team = buildTeam(providers, active);
83
+ const maxSize = cfg.max_quorum_size ?? 3;
84
+ console.log(JSON.stringify({ quorum_active: active, max_quorum_size: maxSize, team }));
85
+ } else {
86
+ console.error(`Unknown mode: ${mode}`);
87
+ console.error('Usage: node quorum-preflight.cjs [--quorum-active|--max-quorum-size|--team|--all]');
88
+ process.exit(1);
89
+ }
@@ -19,7 +19,7 @@
19
19
  * [--cwd <dir>]
20
20
  *
21
21
  * Builds the Mode A or Mode B prompt from deterministic JS templates matching
22
- * agents/qgsd-quorum-slot-worker.md Step 2, pipes it to call-quorum-slot.cjs via
22
+ * agents/nf-quorum-slot-worker.md Step 2, pipes it to call-quorum-slot.cjs via
23
23
  * child_process.spawn, parses the output, and emits a structured YAML result block.
24
24
  *
25
25
  * Exported pure functions (testable without subprocess):
@@ -118,7 +118,7 @@ const PATH_CATEGORY_MAP = new Map([
118
118
  *
119
119
  * @param {Array} requirements — full requirements array
120
120
  * @param {string} question — the question text
121
- * @param {string|null} artifactPath — optional artifact path (e.g. "hooks/qgsd-stop.js")
121
+ * @param {string|null} artifactPath — optional artifact path (e.g. "hooks/nf-stop.js")
122
122
  * @returns {Array} — filtered requirements (max 20), sorted by score descending
123
123
  */
124
124
  function matchRequirementsByKeywords(requirements, question, artifactPath) {
@@ -279,7 +279,7 @@ function formatRequirementsSection(requirements) {
279
279
  /**
280
280
  * buildModeAPrompt — constructs the Mode A question prompt.
281
281
  *
282
- * Matches the EXACT template from agents/qgsd-quorum-slot-worker.md Step 2 Mode A.
282
+ * Matches the EXACT template from agents/nf-quorum-slot-worker.md Step 2 Mode A.
283
283
  *
284
284
  * @param {object} opts
285
285
  * @param {number} opts.round
@@ -297,7 +297,7 @@ function buildModeAPrompt({ round, repoDir, question, artifactPath, artifactCont
297
297
  const lines = [];
298
298
 
299
299
  // Header
300
- lines.push(`QGSD Quorum — Round ${round}`);
300
+ lines.push(`nForma Quorum — Round ${round}`);
301
301
  lines.push('');
302
302
 
303
303
  // Repository + question
@@ -422,7 +422,7 @@ function buildModeAPrompt({ round, repoDir, question, artifactPath, artifactCont
422
422
  /**
423
423
  * buildModeBPrompt — constructs the Mode B execution review prompt.
424
424
  *
425
- * Matches the EXACT template from agents/qgsd-quorum-slot-worker.md Step 2 Mode B.
425
+ * Matches the EXACT template from agents/nf-quorum-slot-worker.md Step 2 Mode B.
426
426
  *
427
427
  * @param {object} opts
428
428
  * @param {number} opts.round
@@ -440,7 +440,7 @@ function buildModeBPrompt({ round, repoDir, question, traces, artifactPath, arti
440
440
  const lines = [];
441
441
 
442
442
  // Header
443
- lines.push(`QGSD Quorum — Execution Review (Round ${round})`);
443
+ lines.push(`nForma Quorum — Execution Review (Round ${round})`);
444
444
  lines.push('');
445
445
 
446
446
  // Repository + question
@@ -3,7 +3,7 @@
3
3
  /**
4
4
  * Pure data functions for requirements management.
5
5
  * No blessed dependency — all functions are testable in isolation.
6
- * Consumers: bin/qgsd.cjs (blessed TUI)
6
+ * Consumers: bin/nf.cjs (blessed TUI)
7
7
  *
8
8
  * Data sources (project-relative paths via process.cwd()):
9
9
  * .planning/formal/requirements.json — 210 requirements in frozen envelope
@@ -192,7 +192,7 @@ const fmtMs = (ms) => {
192
192
  };
193
193
 
194
194
  console.log(`\n${bold('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━')}`);
195
- console.log(bold(' QGSD ► MCP LOG REVIEW'));
195
+ console.log(bold(' nForma ► MCP LOG REVIEW'));
196
196
  console.log(bold('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━'));
197
197
  console.log(dim(` Scanned: ${files.length} debug files | Last ${MAX_DAYS} days | ${DEBUG_DIR}`));
198
198
  console.log();
@@ -0,0 +1,151 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+
4
+ /**
5
+ * risk-heatmap.cjs — Ranked risk transition list for Layer 3 (Reasoning).
6
+ *
7
+ * Combines FMEA RPN scores with L1/L2 coverage gap data to produce a
8
+ * prioritized list of highest-risk transitions. Transitions not modeled
9
+ * in XState receive a 50% risk penalty.
10
+ *
11
+ * Requirements: RSN-03
12
+ *
13
+ * Usage:
14
+ * node bin/risk-heatmap.cjs # print summary to stdout
15
+ * node bin/risk-heatmap.cjs --json # print full results JSON to stdout
16
+ */
17
+
18
+ const fs = require('fs');
19
+ const path = require('path');
20
+
21
+ const ROOT = process.env.PROJECT_ROOT || path.join(__dirname, '..');
22
+ const FORMAL = path.join(ROOT, '.planning', 'formal');
23
+ const REASONING_DIR = path.join(FORMAL, 'reasoning');
24
+ const OUT_FILE = path.join(REASONING_DIR, 'risk-heatmap.json');
25
+
26
+ const JSON_FLAG = process.argv.includes('--json');
27
+
28
+ // ── Risk tier classification ────────────────────────────────────────────────
29
+
30
+ function classifyRiskTier(riskScore) {
31
+ if (riskScore >= 200) return 'critical';
32
+ if (riskScore >= 100) return 'high';
33
+ if (riskScore >= 40) return 'medium';
34
+ return 'low';
35
+ }
36
+
37
+ // ── Core computation ────────────────────────────────────────────────────────
38
+
39
+ /**
40
+ * Compute composite risk score for a transition.
41
+ * risk_score = RPN * (1 + coverage_gap_penalty)
42
+ * coverage_gap_penalty = 0.5 if transition is in missing_in_model, else 0.0
43
+ */
44
+ function computeRiskScore(rpn, hasCoverageGap) {
45
+ const penalty = hasCoverageGap ? 0.5 : 0.0;
46
+ return rpn * (1 + penalty);
47
+ }
48
+
49
+ function generateRiskHeatmap(hazardModel, observedFsm) {
50
+ // Build set of missing_in_model transitions
51
+ const missingInModel = new Set(
52
+ (observedFsm.model_comparison?.missing_in_model || [])
53
+ .map(m => `${m.from}-${m.event}`)
54
+ );
55
+
56
+ const transitions = [];
57
+
58
+ for (const hazard of (hazardModel?.hazards || [])) {
59
+ const key = `${hazard.state}-${hazard.event}`;
60
+ const hasCoverageGap = missingInModel.has(key);
61
+ const coverageGapPenalty = hasCoverageGap ? 0.5 : 0.0;
62
+ const riskScore = computeRiskScore(hazard.rpn, hasCoverageGap);
63
+
64
+ transitions.push({
65
+ state: hazard.state,
66
+ event: hazard.event,
67
+ to_state: hazard.to_state,
68
+ rpn: hazard.rpn,
69
+ coverage_gap: hasCoverageGap,
70
+ coverage_gap_penalty: coverageGapPenalty,
71
+ risk_score: riskScore,
72
+ risk_tier: classifyRiskTier(riskScore),
73
+ derived_from: [
74
+ { layer: 'L3', artifact: 'reasoning/hazard-model.json', ref: `hazards[id=${hazard.id}]` },
75
+ { layer: 'L2', artifact: 'semantics/observed-fsm.json', ref: `observed_transitions.${hazard.state}.${hazard.event}` },
76
+ ],
77
+ });
78
+ }
79
+
80
+ // Sort by risk_score descending
81
+ transitions.sort((a, b) => b.risk_score - a.risk_score);
82
+
83
+ // Summary stats
84
+ const byRiskTier = { critical: 0, high: 0, medium: 0, low: 0 };
85
+ let coverageGapCount = 0;
86
+ for (const t of transitions) {
87
+ byRiskTier[t.risk_tier] = (byRiskTier[t.risk_tier] || 0) + 1;
88
+ if (t.coverage_gap) coverageGapCount++;
89
+ }
90
+
91
+ return {
92
+ schema_version: '1',
93
+ generated: new Date().toISOString(),
94
+ formula: 'risk_score = RPN * (1 + coverage_gap_penalty); coverage_gap_penalty = 0.5 if missing_in_model, else 0.0',
95
+ risk_tiers: {
96
+ critical: 'risk_score >= 200',
97
+ high: '100 <= risk_score < 200',
98
+ medium: '40 <= risk_score < 100',
99
+ low: 'risk_score < 40',
100
+ },
101
+ transitions,
102
+ summary: {
103
+ total: transitions.length,
104
+ by_risk_tier: byRiskTier,
105
+ coverage_gap_count: coverageGapCount,
106
+ },
107
+ };
108
+ }
109
+
110
+ // ── Entry point ─────────────────────────────────────────────────────────────
111
+
112
+ function main() {
113
+ // Load L3 hazard model
114
+ const hazardPath = path.join(REASONING_DIR, 'hazard-model.json');
115
+ if (!fs.existsSync(hazardPath)) {
116
+ console.error('ERROR: hazard-model.json not found at', hazardPath);
117
+ console.error('Run bin/hazard-model.cjs first.');
118
+ process.exit(1);
119
+ }
120
+ const hazardModel = JSON.parse(fs.readFileSync(hazardPath, 'utf8'));
121
+
122
+ // Load L2 observed FSM
123
+ const fsmPath = path.join(FORMAL, 'semantics', 'observed-fsm.json');
124
+ if (!fs.existsSync(fsmPath)) {
125
+ console.error('ERROR: observed-fsm.json not found at', fsmPath);
126
+ process.exit(1);
127
+ }
128
+ const observedFsm = JSON.parse(fs.readFileSync(fsmPath, 'utf8'));
129
+
130
+ const output = generateRiskHeatmap(hazardModel, observedFsm);
131
+
132
+ // Write output
133
+ fs.mkdirSync(REASONING_DIR, { recursive: true });
134
+ fs.writeFileSync(OUT_FILE, JSON.stringify(output, null, 2) + '\n');
135
+
136
+ if (JSON_FLAG) {
137
+ process.stdout.write(JSON.stringify(output));
138
+ } else {
139
+ console.log(`Risk Heatmap`);
140
+ console.log(` Total transitions: ${output.summary.total}`);
141
+ console.log(` By risk tier: ${JSON.stringify(output.summary.by_risk_tier)}`);
142
+ console.log(` Coverage gaps: ${output.summary.coverage_gap_count}`);
143
+ console.log(` Output: ${OUT_FILE}`);
144
+ }
145
+
146
+ process.exit(0);
147
+ }
148
+
149
+ if (require.main === module) main();
150
+
151
+ module.exports = { computeRiskScore, classifyRiskTier, generateRiskHeatmap, main };
@@ -1,8 +1,8 @@
1
1
  #!/usr/bin/env node
2
2
  'use strict';
3
3
  // bin/run-account-manager-tlc.cjs
4
- // Invokes TLC model checker for the QGSD account manager TLA+ specification.
5
- // Source spec: .planning/formal/tla/QGSDAccountManager.tla
4
+ // Invokes TLC model checker for the nForma account manager TLA+ specification.
5
+ // Source spec: .planning/formal/tla/NFAccountManager.tla
6
6
  // Source impl: bin/account-manager.cjs
7
7
  //
8
8
  // Checks:
@@ -23,7 +23,7 @@
23
23
  // - .planning/formal/tla/tla2tools.jar (see .planning/formal/tla/README.md for download command)
24
24
 
25
25
  const { spawnSync } = require('child_process');
26
- const JAVA_HEAP_MAX = process.env.QGSD_JAVA_HEAP_MAX || '512m';
26
+ const JAVA_HEAP_MAX = process.env.NF_JAVA_HEAP_MAX || '512m';
27
27
  const fs = require('fs');
28
28
  const path = require('path');
29
29
  const { writeCheckResult } = require('./write-check-result.cjs');
@@ -125,7 +125,7 @@ if (!fs.existsSync(jarPath)) {
125
125
  }
126
126
 
127
127
  // ── 4. Invoke TLC ────────────────────────────────────────────────────────────
128
- const specPath = path.join(ROOT, '.planning', 'formal', 'tla', 'QGSDAccountManager.tla');
128
+ const specPath = path.join(ROOT, '.planning', 'formal', 'tla', 'NFAccountManager.tla');
129
129
  const cfgPath = path.join(ROOT, '.planning', 'formal', 'tla', configName + '.cfg');
130
130
  // Use workers=1 for liveness (IdleReachable) — avoids multi-worker liveness bugs in TLC
131
131
  const workers = '1';
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  'use strict';
3
3
  // bin/run-account-pool-alloy.cjs
4
- // Invokes Alloy 6 JAR headless for the QGSD account pool structure spec.
4
+ // Invokes Alloy 6 JAR headless for the nForma account pool structure spec.
5
5
  // Requirements: ALY-AM-01
6
6
  //
7
7
  // Usage:
@@ -19,7 +19,7 @@
19
19
  // - .planning/formal/alloy/org.alloytools.alloy.dist.jar (see VERIFICATION_TOOLS.md for download)
20
20
 
21
21
  const { spawnSync } = require('child_process');
22
- const JAVA_HEAP_MAX = process.env.QGSD_JAVA_HEAP_MAX || '512m';
22
+ const JAVA_HEAP_MAX = process.env.NF_JAVA_HEAP_MAX || '512m';
23
23
  const fs = require('fs');
24
24
  const path = require('path');
25
25
  const { writeCheckResult } = require('./write-check-result.cjs');
package/bin/run-alloy.cjs CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  'use strict';
3
3
  // bin/run-alloy.cjs
4
- // Invokes Alloy 6 JAR headless for the QGSD vote-counting model.
4
+ // Invokes Alloy 6 JAR headless for the nForma vote-counting model.
5
5
  // Requirements: ALY-02
6
6
  //
7
7
  // Usage:
@@ -12,7 +12,7 @@
12
12
  // - .planning/formal/alloy/org.alloytools.alloy.dist.jar (see VERIFICATION_TOOLS.md for download)
13
13
 
14
14
  const { spawnSync } = require('child_process');
15
- const JAVA_HEAP_MAX = process.env.QGSD_JAVA_HEAP_MAX || '512m';
15
+ const JAVA_HEAP_MAX = process.env.NF_JAVA_HEAP_MAX || '512m';
16
16
  const fs = require('fs');
17
17
  const path = require('path');
18
18
  const { writeCheckResult } = require('./write-check-result.cjs');
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  'use strict';
3
3
  // bin/run-audit-alloy.cjs
4
- // Invokes Alloy 6 JAR headless for QGSD audit trail specs (GAP-3, GAP-9).
4
+ // Invokes Alloy 6 JAR headless for nForma audit trail specs (GAP-3, GAP-9).
5
5
  // Requirements: GAP-3, GAP-9
6
6
  //
7
7
  // Usage:
@@ -16,7 +16,7 @@
16
16
  // - .planning/formal/alloy/org.alloytools.alloy.dist.jar (see VERIFICATION_TOOLS.md for download)
17
17
 
18
18
  const { spawnSync } = require('child_process');
19
- const JAVA_HEAP_MAX = process.env.QGSD_JAVA_HEAP_MAX || '512m';
19
+ const JAVA_HEAP_MAX = process.env.NF_JAVA_HEAP_MAX || '512m';
20
20
  const fs = require('fs');
21
21
  const path = require('path');
22
22
  const { writeCheckResult } = require('./write-check-result.cjs');
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  'use strict';
3
3
  // bin/run-breaker-tlc.cjs
4
- // Invokes TLC model checker for the QGSD circuit breaker TLA+ specification.
4
+ // Invokes TLC model checker for the nForma circuit breaker TLA+ specification.
5
5
  // Requirements: QT-105
6
6
  //
7
7
  // Usage:
@@ -13,7 +13,7 @@
13
13
  // - .planning/formal/tla/tla2tools.jar (see .planning/formal/tla/README.md for download command)
14
14
 
15
15
  const { spawnSync } = require('child_process');
16
- const JAVA_HEAP_MAX = process.env.QGSD_JAVA_HEAP_MAX || '512m';
16
+ const JAVA_HEAP_MAX = process.env.NF_JAVA_HEAP_MAX || '512m';
17
17
  const fs = require('fs');
18
18
  const path = require('path');
19
19
  const { writeCheckResult } = require('./write-check-result.cjs');
@@ -117,7 +117,7 @@ if (!fs.existsSync(jarPath)) {
117
117
  }
118
118
 
119
119
  // ── 4. Invoke TLC ────────────────────────────────────────────────────────────
120
- const specPath = path.join(ROOT, '.planning', 'formal', 'tla', 'QGSDCircuitBreaker.tla');
120
+ const specPath = path.join(ROOT, '.planning', 'formal', 'tla', 'NFCircuitBreaker.tla');
121
121
  const cfgPath = path.join(ROOT, '.planning', 'formal', 'tla', configName + '.cfg');
122
122
  // Always use 'auto' workers — MCbreaker has a small state space and liveness
123
123
  // can safely run with multiple workers (no known multi-worker liveness bugs at this scale).
@@ -31,7 +31,7 @@ const MODULE_CHECKS = {
31
31
  cmd: [
32
32
  'java', '-cp', '.planning/formal/tla/tla2tools.jar', 'tlc2.TLC',
33
33
  '-config', '.planning/formal/tla/MCliveness.cfg',
34
- '.planning/formal/tla/QGSDQuorum.tla',
34
+ '.planning/formal/tla/NFQuorum.tla',
35
35
  '-workers', '1'
36
36
  ]
37
37
  },
@@ -67,7 +67,7 @@ const MODULE_CHECKS = {
67
67
  cmd: [
68
68
  'java', '-cp', '.planning/formal/tla/tla2tools.jar', 'tlc2.TLC',
69
69
  '-config', '.planning/formal/tla/MCbreaker.cfg',
70
- '.planning/formal/tla/QGSDCircuitBreaker.tla',
70
+ '.planning/formal/tla/NFCircuitBreaker.tla',
71
71
  '-workers', '1'
72
72
  ]
73
73
  }
@@ -78,7 +78,7 @@ const MODULE_CHECKS = {
78
78
  cmd: [
79
79
  'java', '-cp', '.planning/formal/tla/tla2tools.jar', 'tlc2.TLC',
80
80
  '-config', '.planning/formal/tla/MCdeliberation.cfg',
81
- '.planning/formal/tla/QGSDDeliberation.tla',
81
+ '.planning/formal/tla/NFDeliberation.tla',
82
82
  '-workers', '1'
83
83
  ]
84
84
  }
@@ -89,7 +89,7 @@ const MODULE_CHECKS = {
89
89
  cmd: [
90
90
  'java', '-cp', '.planning/formal/tla/tla2tools.jar', 'tlc2.TLC',
91
91
  '-config', '.planning/formal/tla/MCoscillation.cfg',
92
- '.planning/formal/tla/QGSDOscillation.tla',
92
+ '.planning/formal/tla/NFOscillation.tla',
93
93
  '-workers', '1'
94
94
  ]
95
95
  }
@@ -100,7 +100,7 @@ const MODULE_CHECKS = {
100
100
  cmd: [
101
101
  'java', '-cp', '.planning/formal/tla/tla2tools.jar', 'tlc2.TLC',
102
102
  '-config', '.planning/formal/tla/MCconvergence.cfg',
103
- '.planning/formal/tla/QGSDConvergence.tla',
103
+ '.planning/formal/tla/NFConvergence.tla',
104
104
  '-workers', '1'
105
105
  ]
106
106
  }
@@ -111,7 +111,7 @@ const MODULE_CHECKS = {
111
111
  cmd: [
112
112
  'java', '-cp', '.planning/formal/tla/tla2tools.jar', 'tlc2.TLC',
113
113
  '-config', '.planning/formal/tla/MCprefilter.cfg',
114
- '.planning/formal/tla/QGSDPreFilter.tla',
114
+ '.planning/formal/tla/NFPreFilter.tla',
115
115
  '-workers', '1'
116
116
  ]
117
117
  }
@@ -122,7 +122,7 @@ const MODULE_CHECKS = {
122
122
  cmd: [
123
123
  'java', '-cp', '.planning/formal/tla/tla2tools.jar', 'tlc2.TLC',
124
124
  '-config', '.planning/formal/tla/MCrecruiting-safety.cfg',
125
- '.planning/formal/tla/QGSDRecruiting.tla',
125
+ '.planning/formal/tla/NFRecruiting.tla',
126
126
  '-workers', '1'
127
127
  ]
128
128
  }
@@ -133,7 +133,7 @@ const MODULE_CHECKS = {
133
133
  cmd: [
134
134
  'java', '-cp', '.planning/formal/tla/tla2tools.jar', 'tlc2.TLC',
135
135
  '-config', '.planning/formal/tla/MCaccount-manager.cfg',
136
- '.planning/formal/tla/QGSDAccountManager.tla',
136
+ '.planning/formal/tla/NFAccountManager.tla',
137
137
  '-workers', '1'
138
138
  ]
139
139
  }
@@ -144,7 +144,7 @@ const MODULE_CHECKS = {
144
144
  cmd: [
145
145
  'java', '-cp', '.planning/formal/tla/tla2tools.jar', 'tlc2.TLC',
146
146
  '-config', '.planning/formal/tla/MCMCPEnv.cfg',
147
- '.planning/formal/tla/QGSDMCPEnv.tla',
147
+ '.planning/formal/tla/NFMCPEnv.tla',
148
148
  '-workers', '1'
149
149
  ]
150
150
  }