@nforma.ai/nforma 0.2.1 → 0.28.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 (201) 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-slot-dispatch.cjs +6 -6
  85. package/bin/requirements-core.cjs +1 -1
  86. package/bin/review-mcp-logs.cjs +1 -1
  87. package/bin/risk-heatmap.cjs +151 -0
  88. package/bin/run-account-manager-tlc.cjs +4 -4
  89. package/bin/run-account-pool-alloy.cjs +2 -2
  90. package/bin/run-alloy.cjs +2 -2
  91. package/bin/run-audit-alloy.cjs +2 -2
  92. package/bin/run-breaker-tlc.cjs +3 -3
  93. package/bin/run-formal-check.cjs +9 -9
  94. package/bin/run-formal-verify.cjs +30 -9
  95. package/bin/run-installer-alloy.cjs +2 -2
  96. package/bin/run-oscillation-tlc.cjs +4 -4
  97. package/bin/run-phase-tlc.cjs +1 -1
  98. package/bin/run-protocol-tlc.cjs +4 -4
  99. package/bin/run-quorum-composition-alloy.cjs +2 -2
  100. package/bin/run-sensitivity-sweep.cjs +2 -2
  101. package/bin/run-stop-hook-tlc.cjs +3 -3
  102. package/bin/run-tlc.cjs +21 -21
  103. package/bin/run-transcript-alloy.cjs +2 -2
  104. package/bin/secrets.cjs +5 -5
  105. package/bin/security-sweep.cjs +238 -0
  106. package/bin/sensitivity-report.cjs +3 -3
  107. package/bin/set-secret.cjs +5 -5
  108. package/bin/setup-telemetry-cron.sh +3 -3
  109. package/bin/stall-detector.cjs +126 -0
  110. package/bin/state-candidates.cjs +206 -0
  111. package/bin/sync-baseline-requirements.cjs +1 -1
  112. package/bin/telemetry-collector.cjs +1 -1
  113. package/bin/test-changed.cjs +111 -0
  114. package/bin/test-recipe-gen.cjs +250 -0
  115. package/bin/trace-corpus-stats.cjs +211 -0
  116. package/bin/unified-mcp-server.mjs +3 -3
  117. package/bin/update-scoreboard.cjs +1 -1
  118. package/bin/validate-memory.cjs +2 -2
  119. package/bin/validate-traces.cjs +10 -10
  120. package/bin/verify-quorum-health.cjs +66 -5
  121. package/bin/xstate-to-tla.cjs +4 -4
  122. package/bin/xstate-trace-walker.cjs +3 -3
  123. package/commands/{qgsd → nf}/add-phase.md +3 -3
  124. package/commands/{qgsd → nf}/add-requirement.md +3 -3
  125. package/commands/{qgsd → nf}/add-todo.md +3 -3
  126. package/commands/{qgsd → nf}/audit-milestone.md +4 -4
  127. package/commands/{qgsd → nf}/check-todos.md +3 -3
  128. package/commands/{qgsd → nf}/cleanup.md +3 -3
  129. package/commands/{qgsd → nf}/close-formal-gaps.md +2 -2
  130. package/commands/{qgsd → nf}/complete-milestone.md +9 -9
  131. package/commands/{qgsd → nf}/debug.md +9 -9
  132. package/commands/{qgsd → nf}/discuss-phase.md +3 -3
  133. package/commands/{qgsd → nf}/execute-phase.md +15 -15
  134. package/commands/{qgsd → nf}/fix-tests.md +3 -3
  135. package/commands/{qgsd → nf}/formal-test-sync.md +1 -1
  136. package/commands/{qgsd → nf}/health.md +3 -3
  137. package/commands/{qgsd → nf}/help.md +3 -3
  138. package/commands/{qgsd → nf}/insert-phase.md +3 -3
  139. package/commands/nf/join-discord.md +18 -0
  140. package/commands/{qgsd → nf}/list-phase-assumptions.md +2 -2
  141. package/commands/{qgsd → nf}/map-codebase.md +7 -7
  142. package/commands/{qgsd → nf}/map-requirements.md +3 -3
  143. package/commands/{qgsd → nf}/mcp-restart.md +3 -3
  144. package/commands/{qgsd → nf}/mcp-set-model.md +8 -8
  145. package/commands/{qgsd → nf}/mcp-setup.md +63 -63
  146. package/commands/{qgsd → nf}/mcp-status.md +3 -3
  147. package/commands/{qgsd → nf}/mcp-update.md +7 -7
  148. package/commands/{qgsd → nf}/new-milestone.md +8 -8
  149. package/commands/{qgsd → nf}/new-project.md +8 -8
  150. package/commands/{qgsd → nf}/observe.md +49 -16
  151. package/commands/{qgsd → nf}/pause-work.md +3 -3
  152. package/commands/{qgsd → nf}/plan-milestone-gaps.md +5 -5
  153. package/commands/{qgsd → nf}/plan-phase.md +6 -6
  154. package/commands/{qgsd → nf}/polyrepo.md +2 -2
  155. package/commands/{qgsd → nf}/progress.md +3 -3
  156. package/commands/{qgsd → nf}/queue.md +2 -2
  157. package/commands/{qgsd → nf}/quick.md +8 -8
  158. package/commands/{qgsd → nf}/quorum-test.md +10 -10
  159. package/commands/{qgsd → nf}/quorum.md +40 -40
  160. package/commands/{qgsd → nf}/reapply-patches.md +2 -2
  161. package/commands/{qgsd → nf}/remove-phase.md +3 -3
  162. package/commands/{qgsd → nf}/research-phase.md +12 -12
  163. package/commands/{qgsd → nf}/resume-work.md +3 -3
  164. package/commands/nf/review-requirements.md +31 -0
  165. package/commands/{qgsd → nf}/set-profile.md +3 -3
  166. package/commands/{qgsd → nf}/settings.md +6 -6
  167. package/commands/{qgsd → nf}/solve.md +35 -35
  168. package/commands/{qgsd → nf}/sync-baselines.md +4 -4
  169. package/commands/{qgsd → nf}/triage.md +10 -10
  170. package/commands/{qgsd → nf}/update.md +3 -3
  171. package/commands/{qgsd → nf}/verify-work.md +5 -5
  172. package/hooks/dist/config-loader.js +188 -32
  173. package/hooks/dist/conformance-schema.cjs +2 -2
  174. package/hooks/dist/gsd-context-monitor.js +118 -13
  175. package/hooks/dist/{qgsd-check-update.js → nf-check-update.js} +5 -5
  176. package/hooks/dist/{qgsd-circuit-breaker.js → nf-circuit-breaker.js} +35 -24
  177. package/hooks/dist/nf-circuit-breaker.test.js +1002 -0
  178. package/hooks/dist/{qgsd-precompact.js → nf-precompact.js} +13 -13
  179. package/hooks/dist/nf-precompact.test.js +227 -0
  180. package/hooks/dist/{qgsd-prompt.js → nf-prompt.js} +110 -33
  181. package/hooks/dist/nf-prompt.test.js +698 -0
  182. package/hooks/dist/nf-session-start.js +185 -0
  183. package/hooks/dist/nf-session-start.test.js +354 -0
  184. package/hooks/dist/{qgsd-slot-correlator.js → nf-slot-correlator.js} +13 -5
  185. package/hooks/dist/nf-slot-correlator.test.js +85 -0
  186. package/hooks/dist/{qgsd-spec-regen.js → nf-spec-regen.js} +17 -8
  187. package/hooks/dist/nf-spec-regen.test.js +73 -0
  188. package/hooks/dist/{qgsd-statusline.js → nf-statusline.js} +12 -3
  189. package/hooks/dist/nf-statusline.test.js +157 -0
  190. package/hooks/dist/{qgsd-stop.js → nf-stop.js} +152 -18
  191. package/hooks/dist/nf-stop.test.js +1388 -0
  192. package/hooks/dist/{qgsd-token-collector.js → nf-token-collector.js} +12 -4
  193. package/hooks/dist/nf-token-collector.test.js +262 -0
  194. package/hooks/dist/unified-mcp-server.mjs +2 -2
  195. package/package.json +4 -4
  196. package/scripts/build-hooks.js +13 -6
  197. package/scripts/secret-audit.sh +1 -1
  198. package/scripts/verify-hooks-sync.cjs +90 -0
  199. package/templates/{qgsd.json → nf.json} +4 -4
  200. package/commands/qgsd/join-discord.md +0 -18
  201. package/hooks/dist/qgsd-session-start.js +0 -122
@@ -4,7 +4,7 @@
4
4
  * Pure functions and shared helpers for manage-agents CLI and blessed TUI.
5
5
  * No interactive dependencies (inquirer, blessed). This module provides:
6
6
  * - File I/O helpers: readClaudeJson, writeClaudeJson, readProvidersJson, writeProvidersJson
7
- * - Configuration parsing: readQgsdJson, readCcrConfigSafe, getGlobalMcpServers
7
+ * - Configuration parsing: readNfJson, readCcrConfigSafe, getGlobalMcpServers
8
8
  * - Provider management: fetchProviderModels, probeProviderUrl, probeAllSlots, liveDashboard
9
9
  * - Pure transformation functions exported in _pure namespace
10
10
  *
@@ -23,9 +23,9 @@ const { updateAgents, getUpdateStatuses } = require('./update-agents.cjs');
23
23
  // File paths
24
24
  const CLAUDE_JSON_PATH = path.join(os.homedir(), '.claude.json');
25
25
  const CLAUDE_JSON_TMP = CLAUDE_JSON_PATH + '.tmp';
26
- const QGSD_JSON_PATH = path.join(os.homedir(), '.claude', 'qgsd.json');
26
+ const NF_JSON_PATH = path.join(os.homedir(), '.claude', 'nf.json');
27
27
  const CCR_CONFIG_PATH = path.join(os.homedir(), '.claude-code-router', 'config.json');
28
- const UPDATE_LOG_PATH = path.join(os.homedir(), '.claude', 'qgsd-update.log');
28
+ const UPDATE_LOG_PATH = path.join(os.homedir(), '.claude', 'nf-update.log');
29
29
  const PROVIDERS_JSON_PATH = path.join(__dirname, 'providers.json');
30
30
  const PROVIDERS_JSON_TMP = PROVIDERS_JSON_PATH + '.tmp';
31
31
 
@@ -145,7 +145,7 @@ function probeProviderUrl(baseUrl, apiKey) {
145
145
  headers: {
146
146
  'Authorization': apiKey ? `Bearer ${apiKey}` : '',
147
147
  'Accept': 'application/json',
148
- 'User-Agent': 'qgsd-manage-agents/1.0',
148
+ 'User-Agent': 'nf-manage-agents/1.0',
149
149
  },
150
150
  timeout: 7000,
151
151
  },
@@ -185,17 +185,17 @@ function classifyProbeResult(probeResult) {
185
185
  }
186
186
 
187
187
  /**
188
- * Write key validity status to qgsd.json under agent_config[slotName].key_status.
188
+ * Write key validity status to nf.json under agent_config[slotName].key_status.
189
189
  */
190
190
  function writeKeyStatus(slotName, status, filePath) {
191
- const qgsd = readQgsdJson(filePath);
192
- if (!qgsd.agent_config) qgsd.agent_config = {};
193
- if (!qgsd.agent_config[slotName]) qgsd.agent_config[slotName] = {};
194
- qgsd.agent_config[slotName].key_status = {
191
+ const nfCfg = readNfJson(filePath);
192
+ if (!nf.agent_config) nf.agent_config = {};
193
+ if (!nf.agent_config[slotName]) nf.agent_config[slotName] = {};
194
+ nf.agent_config[slotName].key_status = {
195
195
  status,
196
196
  checkedAt: new Date().toISOString(),
197
197
  };
198
- writeQgsdJson(qgsd, filePath);
198
+ writeNfJson(nfCfg, filePath);
199
199
  }
200
200
 
201
201
  /**
@@ -214,7 +214,7 @@ function formatTimestamp(ts) {
214
214
  */
215
215
  function buildDashboardLines(slots, mcpServers, healthMap, lastUpdated) {
216
216
  const lines = [];
217
- lines.push(' QGSD Live Health Dashboard');
217
+ lines.push(' nForma Live Health Dashboard');
218
218
  lines.push(' ' + '\u2500'.repeat(60));
219
219
  lines.push('');
220
220
 
@@ -324,11 +324,11 @@ function writeProvidersJson(data) {
324
324
  }
325
325
 
326
326
  // ---------------------------------------------------------------------------
327
- // QGSD JSON helpers
327
+ // nForma JSON helpers
328
328
  // ---------------------------------------------------------------------------
329
329
 
330
- function readQgsdJson(filePath) {
331
- const p = filePath || QGSD_JSON_PATH;
330
+ function readNfJson(filePath) {
331
+ const p = filePath || NF_JSON_PATH;
332
332
  if (!fs.existsSync(p)) return {};
333
333
  try {
334
334
  return JSON.parse(fs.readFileSync(p, 'utf8'));
@@ -337,19 +337,19 @@ function readQgsdJson(filePath) {
337
337
  }
338
338
  }
339
339
 
340
- function writeQgsdJson(data, filePath) {
341
- const p = filePath || QGSD_JSON_PATH;
340
+ function writeNfJson(data, filePath) {
341
+ const p = filePath || NF_JSON_PATH;
342
342
  const tmp = p + '.tmp';
343
343
  fs.writeFileSync(tmp, JSON.stringify(data, null, 2), 'utf8');
344
344
  fs.renameSync(tmp, p);
345
345
  }
346
346
 
347
347
  function writeUpdatePolicy(slotName, policy, filePath) {
348
- const qgsd = readQgsdJson(filePath);
349
- if (!qgsd.agent_config) qgsd.agent_config = {};
350
- if (!qgsd.agent_config[slotName]) qgsd.agent_config[slotName] = {};
351
- qgsd.agent_config[slotName].update_policy = policy;
352
- writeQgsdJson(qgsd, filePath);
348
+ const nfCfg = readNfJson(filePath);
349
+ if (!nf.agent_config) nf.agent_config = {};
350
+ if (!nf.agent_config[slotName]) nf.agent_config[slotName] = {};
351
+ nf.agent_config[slotName].update_policy = policy;
352
+ writeNfJson(nfCfg, filePath);
353
353
  }
354
354
 
355
355
  // ---------------------------------------------------------------------------
@@ -392,11 +392,11 @@ function applyKeyUpdate(updates, keytarAccount, newEnv, secretsLib) {
392
392
  if (!('apiKey' in updates)) return newEnv;
393
393
  if (updates.apiKey === '__REMOVE__') {
394
394
  delete newEnv.ANTHROPIC_API_KEY;
395
- if (secretsLib) secretsLib.delete('qgsd', keytarAccount);
395
+ if (secretsLib) secretsLib.delete('nforma', keytarAccount);
396
396
  } else {
397
397
  delete newEnv.ANTHROPIC_API_KEY;
398
398
  if (secretsLib) {
399
- secretsLib.set('qgsd', keytarAccount, updates.apiKey);
399
+ secretsLib.set('nforma', keytarAccount, updates.apiKey);
400
400
  } else {
401
401
  newEnv.ANTHROPIC_API_KEY = updates.apiKey;
402
402
  }
@@ -406,11 +406,11 @@ function applyKeyUpdate(updates, keytarAccount, newEnv, secretsLib) {
406
406
 
407
407
  async function applyCcrProviderUpdate(subAction, selectedKey, keyValue, secretsLib) {
408
408
  if (subAction === 'set') {
409
- await secretsLib.set('qgsd', selectedKey, keyValue);
409
+ await secretsLib.set('nforma', selectedKey, keyValue);
410
410
  return { action: 'set', key: selectedKey };
411
411
  }
412
412
  if (subAction === 'remove') {
413
- await secretsLib.delete('qgsd', selectedKey);
413
+ await secretsLib.delete('nforma', selectedKey);
414
414
  return { action: 'remove', key: selectedKey };
415
415
  }
416
416
  return null;
@@ -611,7 +611,7 @@ function validateImportSchema(parsed) {
611
611
  // ---------------------------------------------------------------------------
612
612
 
613
613
  /**
614
- * Probe a single slot and persist key_status to qgsd.json if the provider responded.
614
+ * Probe a single slot and persist key_status to nf.json if the provider responded.
615
615
  * Does NOT persist for timeout/network errors (statusCode is null) -- this is
616
616
  * a provider/network issue, not a key validity issue.
617
617
  * @param {string} slotName - MCP server slot name (e.g. 'claude-1')
@@ -642,7 +642,7 @@ async function probeAllSlots(mcpServers, slots, secretsLib) {
642
642
  let apiKey = env.ANTHROPIC_API_KEY || '';
643
643
  if (secretsLib) {
644
644
  try {
645
- const k = await secretsLib.get('qgsd', account);
645
+ const k = await secretsLib.get('nforma', account);
646
646
  if (k) apiKey = k;
647
647
  } catch (_) {}
648
648
  }
@@ -654,9 +654,9 @@ async function probeAllSlots(mcpServers, slots, secretsLib) {
654
654
 
655
655
  async function runAutoUpdateCheck(getStatusesFn = getUpdateStatuses) {
656
656
  const check = async () => {
657
- let qgsd;
658
- try { qgsd = readQgsdJson(); } catch { return; }
659
- const agentConfig = qgsd.agent_config || {};
657
+ let nfCfg;
658
+ try { nfCfg = readNfJson(); } catch { return; }
659
+ const agentConfig = nf.agent_config || {};
660
660
  const autoSlots = Object.keys(agentConfig).filter(
661
661
  (s) => agentConfig[s] && agentConfig[s].update_policy === 'auto'
662
662
  );
@@ -777,8 +777,8 @@ module.exports._pure = {
777
777
  buildAgentChoiceLabel,
778
778
  applyKeyUpdate,
779
779
  applyCcrProviderUpdate,
780
- readQgsdJson,
781
- writeQgsdJson,
780
+ readNfJson,
781
+ writeNfJson,
782
782
  slotToFamily,
783
783
  getWlDisplay,
784
784
  readCcrConfigSafe,
@@ -62,8 +62,8 @@ function migrateClaudeJson(claudeJsonPath, dryRun = false) {
62
62
  return { changed, renamed };
63
63
  }
64
64
 
65
- // tool_prefix migration map for qgsd.json
66
- const QGSD_PREFIX_MAP = {
65
+ // tool_prefix migration map for nf.json
66
+ const NF_PREFIX_MAP = {
67
67
  'mcp__codex-cli__': 'mcp__codex-cli-1__',
68
68
  'mcp__gemini-cli__': 'mcp__gemini-cli-1__',
69
69
  'mcp__opencode__': 'mcp__opencode-1__',
@@ -71,21 +71,21 @@ const QGSD_PREFIX_MAP = {
71
71
  };
72
72
 
73
73
  /**
74
- * Migrate ~/.claude/qgsd.json required_models tool_prefix values to slot-based prefixes.
75
- * @param {string} qgsdJsonPath - Absolute path to ~/.claude/qgsd.json
74
+ * Migrate ~/.claude/nf.json required_models tool_prefix values to slot-based prefixes.
75
+ * @param {string} nfJsonPath - Absolute path to ~/.claude/nf.json
76
76
  * @param {boolean} dryRun - If true, do not write changes
77
77
  * @returns {{ changed: number, patched: Array<{key: string, from: string, to: string}> }}
78
78
  */
79
- function migrateQgsdJson(qgsdJsonPath, dryRun = false) {
80
- if (!fs.existsSync(qgsdJsonPath)) {
79
+ function migrateNfJson(nfJsonPath, dryRun = false) {
80
+ if (!fs.existsSync(nfJsonPath)) {
81
81
  return { changed: 0, patched: [] };
82
82
  }
83
83
 
84
84
  let raw;
85
85
  try {
86
- raw = JSON.parse(fs.readFileSync(qgsdJsonPath, 'utf8'));
86
+ raw = JSON.parse(fs.readFileSync(nfJsonPath, 'utf8'));
87
87
  } catch (e) {
88
- throw new Error(`Failed to read ${qgsdJsonPath}: ${e.message}`);
88
+ throw new Error(`Failed to read ${nfJsonPath}: ${e.message}`);
89
89
  }
90
90
 
91
91
  const requiredModels = raw.required_models;
@@ -98,7 +98,7 @@ function migrateQgsdJson(qgsdJsonPath, dryRun = false) {
98
98
 
99
99
  for (const [modelKey, modelDef] of Object.entries(requiredModels)) {
100
100
  if (modelDef && typeof modelDef.tool_prefix === 'string') {
101
- const newPrefix = QGSD_PREFIX_MAP[modelDef.tool_prefix];
101
+ const newPrefix = NF_PREFIX_MAP[modelDef.tool_prefix];
102
102
  if (newPrefix) {
103
103
  patched.push({ key: modelKey, from: modelDef.tool_prefix, to: newPrefix });
104
104
  modelDef.tool_prefix = newPrefix;
@@ -108,21 +108,21 @@ function migrateQgsdJson(qgsdJsonPath, dryRun = false) {
108
108
  }
109
109
 
110
110
  if (changed > 0 && !dryRun) {
111
- fs.writeFileSync(qgsdJsonPath, JSON.stringify(raw, null, 2) + '\n', 'utf8');
111
+ fs.writeFileSync(nfJsonPath, JSON.stringify(raw, null, 2) + '\n', 'utf8');
112
112
  }
113
113
 
114
114
  return { changed, patched };
115
115
  }
116
116
 
117
117
  /**
118
- * Populate quorum_active in ~/.claude/qgsd.json from current mcpServers in ~/.claude.json.
118
+ * Populate quorum_active in ~/.claude/nf.json from current mcpServers in ~/.claude.json.
119
119
  * Idempotent: skips if quorum_active already present and non-empty.
120
- * @param {string} qgsdJsonPath - Absolute path to ~/.claude/qgsd.json
120
+ * @param {string} nfJsonPath - Absolute path to ~/.claude/nf.json
121
121
  * @param {string} claudeJsonPath - Absolute path to ~/.claude.json
122
122
  * @param {boolean} dryRun - If true, do not write changes
123
123
  * @returns {{ skipped: boolean, slots: string[] }}
124
124
  */
125
- function populateActiveSlots(qgsdJsonPath, claudeJsonPath, dryRun = false) {
125
+ function populateActiveSlots(nfJsonPath, claudeJsonPath, dryRun = false) {
126
126
  // Read current slot names from ~/.claude.json
127
127
  let slotNames = [];
128
128
  try {
@@ -134,25 +134,25 @@ function populateActiveSlots(qgsdJsonPath, claudeJsonPath, dryRun = false) {
134
134
  return { skipped: true, slots: [] };
135
135
  }
136
136
 
137
- // Read or create qgsd.json
138
- let qgsdConfig = {};
137
+ // Read or create nf.json
138
+ let nfConfig = {};
139
139
  try {
140
- qgsdConfig = JSON.parse(fs.readFileSync(qgsdJsonPath, 'utf8'));
140
+ nfConfig = JSON.parse(fs.readFileSync(nfJsonPath, 'utf8'));
141
141
  } catch (e) {
142
- if (e.code !== 'ENOENT') throw new Error(`Failed to read ${qgsdJsonPath}: ${e.message}`);
143
- // qgsd.json absent — create minimal object
142
+ if (e.code !== 'ENOENT') throw new Error(`Failed to read ${nfJsonPath}: ${e.message}`);
143
+ // nf.json absent — create minimal object
144
144
  }
145
145
 
146
146
  // Idempotent: skip if already set and non-empty
147
- if (Array.isArray(qgsdConfig.quorum_active) && qgsdConfig.quorum_active.length > 0) {
148
- return { skipped: true, slots: qgsdConfig.quorum_active };
147
+ if (Array.isArray(nfConfig.quorum_active) && nfConfig.quorum_active.length > 0) {
148
+ return { skipped: true, slots: nfConfig.quorum_active };
149
149
  }
150
150
 
151
151
  // Populate and write
152
- qgsdConfig.quorum_active = slotNames;
152
+ nfConfig.quorum_active = slotNames;
153
153
  if (!dryRun) {
154
- fs.mkdirSync(path.dirname(qgsdJsonPath), { recursive: true });
155
- fs.writeFileSync(qgsdJsonPath, JSON.stringify(qgsdConfig, null, 2) + '\n', 'utf8');
154
+ fs.mkdirSync(path.dirname(nfJsonPath), { recursive: true });
155
+ fs.writeFileSync(nfJsonPath, JSON.stringify(nfConfig, null, 2) + '\n', 'utf8');
156
156
  }
157
157
  return { skipped: false, slots: slotNames };
158
158
  }
@@ -161,7 +161,7 @@ function populateActiveSlots(qgsdJsonPath, claudeJsonPath, dryRun = false) {
161
161
  if (require.main === module) {
162
162
  const dryRun = process.argv.includes('--dry-run');
163
163
  const claudeJsonPath = path.join(os.homedir(), '.claude.json');
164
- const qgsdJsonPath = path.join(os.homedir(), '.claude', 'qgsd.json');
164
+ const nfJsonPath = path.join(os.homedir(), '.claude', 'nf.json');
165
165
 
166
166
  let r1, r2;
167
167
  try {
@@ -172,15 +172,15 @@ if (require.main === module) {
172
172
  }
173
173
 
174
174
  try {
175
- r2 = migrateQgsdJson(qgsdJsonPath, dryRun);
175
+ r2 = migrateNfJson(nfJsonPath, dryRun);
176
176
  } catch (e) {
177
- console.error(`Error migrating ~/.claude/qgsd.json: ${e.message}`);
177
+ console.error(`Error migrating ~/.claude/nf.json: ${e.message}`);
178
178
  process.exit(1);
179
179
  }
180
180
 
181
181
  let r3;
182
182
  try {
183
- r3 = populateActiveSlots(qgsdJsonPath, claudeJsonPath, dryRun);
183
+ r3 = populateActiveSlots(nfJsonPath, claudeJsonPath, dryRun);
184
184
  if (!r3.skipped) {
185
185
  console.log(`[migrate-to-slots] quorum_active populated: ${r3.slots.join(', ')}`);
186
186
  } else {
@@ -200,7 +200,7 @@ if (require.main === module) {
200
200
  console.log(` mcpServers: ${from} → ${to}`);
201
201
  }
202
202
  for (const { key, from, to } of r2.patched) {
203
- console.log(` qgsd.json required_models.${key}.tool_prefix: ${from} → ${to}`);
203
+ console.log(` nf.json required_models.${key}.tool_prefix: ${from} → ${to}`);
204
204
  }
205
205
  } else {
206
206
  if (r1.changed > 0) {
@@ -210,7 +210,7 @@ if (require.main === module) {
210
210
  }
211
211
  }
212
212
  if (r2.changed > 0) {
213
- console.log(`Patched ${r2.changed} qgsd.json tool_prefix values`);
213
+ console.log(`Patched ${r2.changed} nf.json tool_prefix values`);
214
214
  for (const { key, from, to } of r2.patched) {
215
215
  console.log(` required_models.${key}.tool_prefix: ${from} → ${to}`);
216
216
  }
@@ -221,24 +221,24 @@ if (require.main === module) {
221
221
  }
222
222
 
223
223
  /**
224
- * Append a single slot name to quorum_active in qgsd.json if not already present.
224
+ * Append a single slot name to quorum_active in nf.json if not already present.
225
225
  * Idempotent: no-op if slot is already in the array.
226
226
  * @param {string} slotName - e.g. "copilot-2"
227
- * @param {string} qgsdJsonPath - path to ~/.claude/qgsd.json
227
+ * @param {string} nfJsonPath - path to ~/.claude/nf.json
228
228
  * @param {boolean} dryRun - if true, report but do not write
229
229
  * @returns {{ added: boolean, slot: string, skipped: boolean }}
230
230
  */
231
- function addSlotToQuorumActive(slotName, qgsdJsonPath, dryRun = false) {
232
- let qgsdConfig = {};
231
+ function addSlotToQuorumActive(slotName, nfJsonPath, dryRun = false) {
232
+ let nfConfig = {};
233
233
  try {
234
- if (fs.existsSync(qgsdJsonPath)) {
235
- qgsdConfig = JSON.parse(fs.readFileSync(qgsdJsonPath, 'utf8'));
234
+ if (fs.existsSync(nfJsonPath)) {
235
+ nfConfig = JSON.parse(fs.readFileSync(nfJsonPath, 'utf8'));
236
236
  }
237
237
  } catch (e) {
238
238
  return { added: false, slot: slotName, skipped: true, error: e.message };
239
239
  }
240
240
 
241
- const active = Array.isArray(qgsdConfig.quorum_active) ? qgsdConfig.quorum_active : [];
241
+ const active = Array.isArray(nfConfig.quorum_active) ? nfConfig.quorum_active : [];
242
242
  if (active.includes(slotName)) {
243
243
  return { added: false, slot: slotName, skipped: true, reason: 'already present' };
244
244
  }
@@ -247,9 +247,9 @@ function addSlotToQuorumActive(slotName, qgsdJsonPath, dryRun = false) {
247
247
  return { added: true, slot: slotName, skipped: false, dryRun: true };
248
248
  }
249
249
 
250
- qgsdConfig.quorum_active = [...active, slotName];
251
- fs.writeFileSync(qgsdJsonPath, JSON.stringify(qgsdConfig, null, 2) + '\n');
250
+ nfConfig.quorum_active = [...active, slotName];
251
+ fs.writeFileSync(nfJsonPath, JSON.stringify(nfConfig, null, 2) + '\n');
252
252
  return { added: true, slot: slotName, skipped: false };
253
253
  }
254
254
 
255
- module.exports = { migrateClaudeJson, migrateQgsdJson, populateActiveSlots, addSlotToQuorumActive, SLOT_MIGRATION_MAP };
255
+ module.exports = { migrateClaudeJson, migrateNfJson, populateActiveSlots, addSlotToQuorumActive, SLOT_MIGRATION_MAP };
@@ -0,0 +1,217 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+ // bin/mismatch-register.cjs
4
+ // Builds a JSONL mismatch register tracking L2-model vs L1-trace disagreements.
5
+ // Each line is a standalone JSON object with resolution tracking.
6
+ // Requirements: SEM-02
7
+
8
+ const fs = require('fs');
9
+ const path = require('path');
10
+
11
+ const PROJECT_ROOT = path.resolve(__dirname, '..');
12
+
13
+ // ── Helpers ──────────────────────────────────────────────────────────────────
14
+
15
+ /**
16
+ * Classify a conformance event as a methodology skip.
17
+ * H1 methodology: mid-session events (phase !== 'IDLE' AND action !== 'quorum_start')
18
+ * cannot be validated with a fresh actor from IDLE, so they are methodology skips,
19
+ * NOT mismatches. Fresh actors always start in IDLE, so mid-session events would
20
+ * produce false mismatches without this compensation.
21
+ */
22
+ function isMethodologySkip(event) {
23
+ if (!event) return false;
24
+ // quorum_start always starts from IDLE -- valid for fresh-actor replay
25
+ if (event.action === 'quorum_start') return false;
26
+ // Events with non-IDLE phase are mid-session
27
+ if (event.phase && event.phase !== 'IDLE') return true;
28
+ return false;
29
+ }
30
+
31
+ /**
32
+ * Generate a mismatch entry from a conformance event comparison.
33
+ */
34
+ function buildMismatchEntry(id, event, eventIndex, expectedState, actualState, divergenceType, resolution, notes, classification) {
35
+ return {
36
+ id: 'MISMATCH-' + String(id).padStart(3, '0'),
37
+ timestamp: event.ts || new Date().toISOString(),
38
+ l2_source: 'nf-workflow.machine.ts',
39
+ l1_trace_ref: {
40
+ event_index: eventIndex,
41
+ session: event.session_id || event.round_id || 'standalone',
42
+ },
43
+ expected_state: expectedState,
44
+ actual_state: actualState,
45
+ divergence_type: divergenceType,
46
+ resolution: resolution || 'open',
47
+ resolution_notes: notes || null,
48
+ classification: classification || null,
49
+ };
50
+ }
51
+
52
+ /**
53
+ * Process conformance events and build mismatch entries.
54
+ * Returns { entries, summary }.
55
+ */
56
+ function buildMismatchRegister(conformanceEvents, vocabulary, divergences) {
57
+ // Lazy-load validate-traces for mapToXStateEvent
58
+ const { mapToXStateEvent } = require(path.join(__dirname, 'validate-traces.cjs'));
59
+
60
+ // Machine path resolution (same pattern as validate-traces.cjs)
61
+ const machinePath = (() => {
62
+ const repoDist = path.join(__dirname, '..', 'dist', 'machines', 'nf-workflow.machine.js');
63
+ const installDist = path.join(__dirname, 'dist', 'machines', 'nf-workflow.machine.js');
64
+ return fs.existsSync(repoDist) ? repoDist : installDist;
65
+ })();
66
+ const { createActor, nfWorkflowMachine } = require(machinePath);
67
+
68
+ const entries = [];
69
+ let mismatchCounter = 0;
70
+ const stats = { total_events: 0, mapped: 0, unmapped: 0, methodology_skip: 0, state_mismatch: 0 };
71
+
72
+ // Process conformance events
73
+ for (let i = 0; i < conformanceEvents.length; i++) {
74
+ const event = conformanceEvents[i];
75
+ stats.total_events++;
76
+
77
+ // Map to XState event
78
+ const xstateEvent = mapToXStateEvent(event);
79
+ if (!xstateEvent) {
80
+ stats.unmapped++;
81
+ continue; // instrumentation gap, not a mismatch
82
+ }
83
+ stats.mapped++;
84
+
85
+ // H1 methodology skip: mid-session events cannot be validated with fresh actor
86
+ if (isMethodologySkip(event)) {
87
+ stats.methodology_skip++;
88
+ continue;
89
+ }
90
+
91
+ // Fresh actor per event for isolation
92
+ const actor = createActor(nfWorkflowMachine);
93
+ actor.start();
94
+ actor.send(xstateEvent);
95
+ const snapshot = actor.getSnapshot();
96
+ actor.stop();
97
+
98
+ // Determine expected state based on event type
99
+ let expectedState = null;
100
+ if (event.action === 'quorum_start') expectedState = 'COLLECTING_VOTES';
101
+ else if (event.action === 'circuit_break') expectedState = 'IDLE';
102
+ else if (event.action === 'deliberation_round') expectedState = 'DELIBERATING';
103
+ else if (event.outcome === 'APPROVE' || event.outcome === 'BLOCK') expectedState = 'DECIDED';
104
+
105
+ if (expectedState === null) continue; // cannot determine -- skip
106
+
107
+ // Get actual state as string
108
+ const actualStateValue = typeof snapshot.value === 'string'
109
+ ? snapshot.value
110
+ : JSON.stringify(snapshot.value);
111
+
112
+ // Compare states
113
+ if (!snapshot.matches(expectedState)) {
114
+ mismatchCounter++;
115
+ stats.state_mismatch++;
116
+ entries.push(buildMismatchEntry(
117
+ mismatchCounter, event, i,
118
+ expectedState, actualStateValue,
119
+ 'state_mismatch', 'open', null, null
120
+ ));
121
+ }
122
+ }
123
+
124
+ // Incorporate existing .divergences.json entries
125
+ if (Array.isArray(divergences)) {
126
+ for (const div of divergences) {
127
+ mismatchCounter++;
128
+ const divEvent = div.event || {};
129
+
130
+ // Check if this divergence matches the H1 methodology skip pattern
131
+ const isH1 = isMethodologySkip(divEvent);
132
+ const resolution = isH1 ? 'explained' : 'open';
133
+ const notes = isH1 ? 'H1 methodology limitation: mid-session event evaluated with fresh actor from IDLE' : null;
134
+
135
+ entries.push(buildMismatchEntry(
136
+ mismatchCounter, divEvent, divEvent._lineIndex || 0,
137
+ div.expectedState || 'unknown',
138
+ typeof div.actualState === 'string' ? div.actualState : JSON.stringify(div.actualState || 'unknown'),
139
+ div.divergenceType || 'state_mismatch',
140
+ resolution, notes,
141
+ isH1 ? 'methodology_limitation' : null
142
+ ));
143
+ }
144
+ }
145
+
146
+ const byResolution = { open: 0, explained: 0, bug: 0 };
147
+ const byDivergenceType = {};
148
+ for (const e of entries) {
149
+ byResolution[e.resolution] = (byResolution[e.resolution] || 0) + 1;
150
+ byDivergenceType[e.divergence_type] = (byDivergenceType[e.divergence_type] || 0) + 1;
151
+ }
152
+
153
+ return {
154
+ entries,
155
+ summary: {
156
+ total_mismatches: entries.length,
157
+ by_resolution: byResolution,
158
+ by_divergence_type: byDivergenceType,
159
+ stats,
160
+ },
161
+ };
162
+ }
163
+
164
+ // ── CLI ──────────────────────────────────────────────────────────────────────
165
+
166
+ if (require.main === module) {
167
+ const jsonFlag = process.argv.includes('--json');
168
+
169
+ // Read conformance events
170
+ const pp = require(path.join(__dirname, 'planning-paths.cjs'));
171
+ const logPath = pp.resolveWithFallback(PROJECT_ROOT, 'conformance-events');
172
+ let conformanceEvents = [];
173
+ if (fs.existsSync(logPath)) {
174
+ const raw = fs.readFileSync(logPath, 'utf8');
175
+ const lines = raw.split('\n').filter(l => l.trim().length > 0);
176
+ for (const line of lines) {
177
+ try { conformanceEvents.push(JSON.parse(line)); } catch (_) { /* skip malformed lines */ }
178
+ }
179
+ }
180
+
181
+ // Read event vocabulary
182
+ const vocabPath = path.join(PROJECT_ROOT, '.planning', 'formal', 'evidence', 'event-vocabulary.json');
183
+ let vocabulary = {};
184
+ if (fs.existsSync(vocabPath)) {
185
+ try { vocabulary = JSON.parse(fs.readFileSync(vocabPath, 'utf8')); } catch (_) { /* fail-open */ }
186
+ }
187
+
188
+ // Read existing divergences
189
+ const divergencesPath = path.join(PROJECT_ROOT, '.planning', 'formal', '.divergences.json');
190
+ let divergences = [];
191
+ if (fs.existsSync(divergencesPath)) {
192
+ try { divergences = JSON.parse(fs.readFileSync(divergencesPath, 'utf8')); } catch (_) { /* fail-open */ }
193
+ }
194
+
195
+ const result = buildMismatchRegister(conformanceEvents, vocabulary, divergences);
196
+
197
+ // Write JSONL output (one JSON object per line)
198
+ const outputPath = path.join(PROJECT_ROOT, '.planning', 'formal', 'semantics', 'mismatch-register.jsonl');
199
+ fs.mkdirSync(path.dirname(outputPath), { recursive: true });
200
+ const jsonlContent = result.entries.map(e => JSON.stringify(e)).join('\n');
201
+ fs.writeFileSync(outputPath, jsonlContent + (jsonlContent.length > 0 ? '\n' : ''), 'utf8');
202
+
203
+ // Output summary
204
+ if (jsonFlag) {
205
+ process.stdout.write(JSON.stringify(result.summary, null, 2) + '\n');
206
+ } else {
207
+ process.stdout.write('[mismatch-register] Total mismatches: ' + result.summary.total_mismatches + '\n');
208
+ process.stdout.write('[mismatch-register] By resolution: ' + JSON.stringify(result.summary.by_resolution) + '\n');
209
+ process.stdout.write('[mismatch-register] By type: ' + JSON.stringify(result.summary.by_divergence_type) + '\n');
210
+ process.stdout.write('[mismatch-register] Stats: ' + JSON.stringify(result.summary.stats) + '\n');
211
+ process.stdout.write('[mismatch-register] Output: ' + outputPath + '\n');
212
+ }
213
+
214
+ process.exit(0);
215
+ }
216
+
217
+ module.exports = { buildMismatchRegister, buildMismatchEntry, isMethodologySkip };