@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.
- package/README.md +2 -2
- package/agents/{qgsd-codebase-mapper.md → nf-codebase-mapper.md} +1 -1
- package/agents/{qgsd-debugger.md → nf-debugger.md} +3 -3
- package/agents/{qgsd-executor.md → nf-executor.md} +14 -14
- package/agents/{qgsd-integration-checker.md → nf-integration-checker.md} +1 -1
- package/agents/{qgsd-phase-researcher.md → nf-phase-researcher.md} +6 -6
- package/agents/{qgsd-plan-checker.md → nf-plan-checker.md} +9 -9
- package/agents/{qgsd-planner.md → nf-planner.md} +9 -9
- package/agents/{qgsd-project-researcher.md → nf-project-researcher.md} +2 -2
- package/agents/{qgsd-quorum-orchestrator.md → nf-quorum-orchestrator.md} +33 -33
- package/agents/{qgsd-quorum-slot-worker.md → nf-quorum-slot-worker.md} +3 -3
- package/agents/{qgsd-quorum-synthesizer.md → nf-quorum-synthesizer.md} +3 -3
- package/agents/{qgsd-quorum-test-worker.md → nf-quorum-test-worker.md} +1 -1
- package/agents/{qgsd-quorum-worker.md → nf-quorum-worker.md} +6 -6
- package/agents/{qgsd-research-synthesizer.md → nf-research-synthesizer.md} +5 -5
- package/agents/{qgsd-roadmapper.md → nf-roadmapper.md} +3 -3
- package/agents/{qgsd-verifier.md → nf-verifier.md} +8 -8
- package/bin/accept-debug-invariant.cjs +2 -2
- package/bin/account-manager.cjs +10 -10
- package/bin/aggregate-requirements.cjs +1 -1
- package/bin/analyze-assumptions.cjs +3 -3
- package/bin/analyze-state-space.cjs +14 -14
- package/bin/assumption-register.cjs +146 -0
- package/bin/attribute-trace-divergence.cjs +1 -1
- package/bin/auth-drivers/gh-cli.cjs +1 -1
- package/bin/auth-drivers/pool.cjs +1 -1
- package/bin/autoClosePtoF.cjs +3 -3
- package/bin/budget-tracker.cjs +77 -0
- package/bin/build-layer-manifest.cjs +153 -0
- package/bin/call-quorum-slot.cjs +3 -3
- package/bin/ccr-secure-config.cjs +5 -5
- package/bin/check-bundled-sdks.cjs +1 -1
- package/bin/check-mcp-health.cjs +1 -1
- package/bin/check-provider-health.cjs +6 -6
- package/bin/check-spec-sync.cjs +26 -26
- package/bin/check-trace-schema-drift.cjs +5 -5
- package/bin/conformance-schema.cjs +2 -2
- package/bin/cross-layer-dashboard.cjs +297 -0
- package/bin/design-impact.cjs +377 -0
- package/bin/detect-coverage-gaps.cjs +7 -7
- package/bin/failure-mode-catalog.cjs +227 -0
- package/bin/failure-taxonomy.cjs +177 -0
- package/bin/formal-scope-scan.cjs +179 -0
- package/bin/gate-a-grounding.cjs +334 -0
- package/bin/gate-b-abstraction.cjs +243 -0
- package/bin/gate-c-validation.cjs +166 -0
- package/bin/generate-formal-specs.cjs +17 -17
- package/bin/generate-petri-net.cjs +3 -3
- package/bin/generate-tla-cfg.cjs +5 -5
- package/bin/git-heatmap.cjs +571 -0
- package/bin/harness-diagnostic.cjs +326 -0
- package/bin/hazard-model.cjs +261 -0
- package/bin/install-formal-tools.cjs +1 -1
- package/bin/install.js +184 -139
- package/bin/instrumentation-map.cjs +178 -0
- package/bin/invariant-catalog.cjs +437 -0
- package/bin/issue-classifier.cjs +2 -2
- package/bin/load-baseline-requirements.cjs +4 -4
- package/bin/manage-agents-core.cjs +32 -32
- package/bin/migrate-to-slots.cjs +39 -39
- package/bin/mismatch-register.cjs +217 -0
- package/bin/nForma.cjs +176 -81
- package/bin/{qgsd-solve.cjs → nf-solve.cjs} +327 -14
- package/bin/observe-config.cjs +8 -0
- package/bin/observe-debt-writer.cjs +1 -1
- package/bin/observe-handler-deps.cjs +356 -0
- package/bin/observe-handler-grafana.cjs +2 -17
- package/bin/observe-handler-internal.cjs +5 -5
- package/bin/observe-handler-logstash.cjs +2 -17
- package/bin/observe-handler-prometheus.cjs +2 -17
- package/bin/observe-handler-upstream.cjs +251 -0
- package/bin/observe-handlers.cjs +12 -33
- package/bin/observe-render.cjs +68 -22
- package/bin/observe-utils.cjs +37 -0
- package/bin/observed-fsm.cjs +324 -0
- package/bin/planning-paths.cjs +6 -0
- package/bin/polyrepo.cjs +1 -1
- package/bin/probe-quorum-slots.cjs +1 -1
- package/bin/promote-gate-maturity.cjs +274 -0
- package/bin/promote-model.cjs +1 -1
- package/bin/propose-debug-invariants.cjs +1 -1
- package/bin/quorum-cache.cjs +144 -0
- package/bin/quorum-consensus-gate.cjs +1 -1
- package/bin/quorum-slot-dispatch.cjs +6 -6
- package/bin/requirements-core.cjs +1 -1
- package/bin/review-mcp-logs.cjs +1 -1
- package/bin/risk-heatmap.cjs +151 -0
- package/bin/run-account-manager-tlc.cjs +4 -4
- package/bin/run-account-pool-alloy.cjs +2 -2
- package/bin/run-alloy.cjs +2 -2
- package/bin/run-audit-alloy.cjs +2 -2
- package/bin/run-breaker-tlc.cjs +3 -3
- package/bin/run-formal-check.cjs +9 -9
- package/bin/run-formal-verify.cjs +30 -9
- package/bin/run-installer-alloy.cjs +2 -2
- package/bin/run-oscillation-tlc.cjs +4 -4
- package/bin/run-phase-tlc.cjs +1 -1
- package/bin/run-protocol-tlc.cjs +4 -4
- package/bin/run-quorum-composition-alloy.cjs +2 -2
- package/bin/run-sensitivity-sweep.cjs +2 -2
- package/bin/run-stop-hook-tlc.cjs +3 -3
- package/bin/run-tlc.cjs +21 -21
- package/bin/run-transcript-alloy.cjs +2 -2
- package/bin/secrets.cjs +5 -5
- package/bin/security-sweep.cjs +238 -0
- package/bin/sensitivity-report.cjs +3 -3
- package/bin/set-secret.cjs +5 -5
- package/bin/setup-telemetry-cron.sh +3 -3
- package/bin/stall-detector.cjs +126 -0
- package/bin/state-candidates.cjs +206 -0
- package/bin/sync-baseline-requirements.cjs +1 -1
- package/bin/telemetry-collector.cjs +1 -1
- package/bin/test-changed.cjs +111 -0
- package/bin/test-recipe-gen.cjs +250 -0
- package/bin/trace-corpus-stats.cjs +211 -0
- package/bin/unified-mcp-server.mjs +3 -3
- package/bin/update-scoreboard.cjs +1 -1
- package/bin/validate-memory.cjs +2 -2
- package/bin/validate-traces.cjs +10 -10
- package/bin/verify-quorum-health.cjs +66 -5
- package/bin/xstate-to-tla.cjs +4 -4
- package/bin/xstate-trace-walker.cjs +3 -3
- package/commands/{qgsd → nf}/add-phase.md +3 -3
- package/commands/{qgsd → nf}/add-requirement.md +3 -3
- package/commands/{qgsd → nf}/add-todo.md +3 -3
- package/commands/{qgsd → nf}/audit-milestone.md +4 -4
- package/commands/{qgsd → nf}/check-todos.md +3 -3
- package/commands/{qgsd → nf}/cleanup.md +3 -3
- package/commands/{qgsd → nf}/close-formal-gaps.md +2 -2
- package/commands/{qgsd → nf}/complete-milestone.md +9 -9
- package/commands/{qgsd → nf}/debug.md +9 -9
- package/commands/{qgsd → nf}/discuss-phase.md +3 -3
- package/commands/{qgsd → nf}/execute-phase.md +15 -15
- package/commands/{qgsd → nf}/fix-tests.md +3 -3
- package/commands/{qgsd → nf}/formal-test-sync.md +1 -1
- package/commands/{qgsd → nf}/health.md +3 -3
- package/commands/{qgsd → nf}/help.md +3 -3
- package/commands/{qgsd → nf}/insert-phase.md +3 -3
- package/commands/nf/join-discord.md +18 -0
- package/commands/{qgsd → nf}/list-phase-assumptions.md +2 -2
- package/commands/{qgsd → nf}/map-codebase.md +7 -7
- package/commands/{qgsd → nf}/map-requirements.md +3 -3
- package/commands/{qgsd → nf}/mcp-restart.md +3 -3
- package/commands/{qgsd → nf}/mcp-set-model.md +8 -8
- package/commands/{qgsd → nf}/mcp-setup.md +63 -63
- package/commands/{qgsd → nf}/mcp-status.md +3 -3
- package/commands/{qgsd → nf}/mcp-update.md +7 -7
- package/commands/{qgsd → nf}/new-milestone.md +8 -8
- package/commands/{qgsd → nf}/new-project.md +8 -8
- package/commands/{qgsd → nf}/observe.md +49 -16
- package/commands/{qgsd → nf}/pause-work.md +3 -3
- package/commands/{qgsd → nf}/plan-milestone-gaps.md +5 -5
- package/commands/{qgsd → nf}/plan-phase.md +6 -6
- package/commands/{qgsd → nf}/polyrepo.md +2 -2
- package/commands/{qgsd → nf}/progress.md +3 -3
- package/commands/{qgsd → nf}/queue.md +2 -2
- package/commands/{qgsd → nf}/quick.md +8 -8
- package/commands/{qgsd → nf}/quorum-test.md +10 -10
- package/commands/{qgsd → nf}/quorum.md +40 -40
- package/commands/{qgsd → nf}/reapply-patches.md +2 -2
- package/commands/{qgsd → nf}/remove-phase.md +3 -3
- package/commands/{qgsd → nf}/research-phase.md +12 -12
- package/commands/{qgsd → nf}/resume-work.md +3 -3
- package/commands/nf/review-requirements.md +31 -0
- package/commands/{qgsd → nf}/set-profile.md +3 -3
- package/commands/{qgsd → nf}/settings.md +6 -6
- package/commands/{qgsd → nf}/solve.md +35 -35
- package/commands/{qgsd → nf}/sync-baselines.md +4 -4
- package/commands/{qgsd → nf}/triage.md +10 -10
- package/commands/{qgsd → nf}/update.md +3 -3
- package/commands/{qgsd → nf}/verify-work.md +5 -5
- package/hooks/dist/config-loader.js +188 -32
- package/hooks/dist/conformance-schema.cjs +2 -2
- package/hooks/dist/gsd-context-monitor.js +118 -13
- package/hooks/dist/{qgsd-check-update.js → nf-check-update.js} +5 -5
- package/hooks/dist/{qgsd-circuit-breaker.js → nf-circuit-breaker.js} +35 -24
- package/hooks/dist/nf-circuit-breaker.test.js +1002 -0
- package/hooks/dist/{qgsd-precompact.js → nf-precompact.js} +13 -13
- package/hooks/dist/nf-precompact.test.js +227 -0
- package/hooks/dist/{qgsd-prompt.js → nf-prompt.js} +110 -33
- package/hooks/dist/nf-prompt.test.js +698 -0
- package/hooks/dist/nf-session-start.js +185 -0
- package/hooks/dist/nf-session-start.test.js +354 -0
- package/hooks/dist/{qgsd-slot-correlator.js → nf-slot-correlator.js} +13 -5
- package/hooks/dist/nf-slot-correlator.test.js +85 -0
- package/hooks/dist/{qgsd-spec-regen.js → nf-spec-regen.js} +17 -8
- package/hooks/dist/nf-spec-regen.test.js +73 -0
- package/hooks/dist/{qgsd-statusline.js → nf-statusline.js} +12 -3
- package/hooks/dist/nf-statusline.test.js +157 -0
- package/hooks/dist/{qgsd-stop.js → nf-stop.js} +152 -18
- package/hooks/dist/nf-stop.test.js +1388 -0
- package/hooks/dist/{qgsd-token-collector.js → nf-token-collector.js} +12 -4
- package/hooks/dist/nf-token-collector.test.js +262 -0
- package/hooks/dist/unified-mcp-server.mjs +2 -2
- package/package.json +4 -4
- package/scripts/build-hooks.js +13 -6
- package/scripts/secret-audit.sh +1 -1
- package/scripts/verify-hooks-sync.cjs +90 -0
- package/templates/{qgsd.json → nf.json} +4 -4
- package/commands/qgsd/join-discord.md +0 -18
- 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', '
|
|
157
|
+
path.join(process.cwd(), '.planning', 'nf.json'),
|
|
158
158
|
];
|
|
159
159
|
for (const cfgPath of paths) {
|
|
160
160
|
try {
|
|
@@ -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/
|
|
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/
|
|
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/
|
|
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(`
|
|
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/
|
|
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(`
|
|
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/
|
|
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
|
package/bin/review-mcp-logs.cjs
CHANGED
|
@@ -192,7 +192,7 @@ const fmtMs = (ms) => {
|
|
|
192
192
|
};
|
|
193
193
|
|
|
194
194
|
console.log(`\n${bold('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━')}`);
|
|
195
|
-
console.log(bold('
|
|
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
|
|
5
|
-
// Source spec: .planning/formal/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.
|
|
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', '
|
|
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
|
|
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.
|
|
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
|
|
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.
|
|
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');
|
package/bin/run-audit-alloy.cjs
CHANGED
|
@@ -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
|
|
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.
|
|
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');
|
package/bin/run-breaker-tlc.cjs
CHANGED
|
@@ -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
|
|
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.
|
|
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', '
|
|
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).
|
package/bin/run-formal-check.cjs
CHANGED
|
@@ -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/
|
|
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/
|
|
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/
|
|
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/
|
|
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/
|
|
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/
|
|
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/
|
|
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/
|
|
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/
|
|
147
|
+
'.planning/formal/tla/NFMCPEnv.tla',
|
|
148
148
|
'-workers', '1'
|
|
149
149
|
]
|
|
150
150
|
}
|
|
@@ -21,14 +21,15 @@
|
|
|
21
21
|
// Traceability (3) — generate-traceability-matrix.cjs (requirements <-> properties matrix)
|
|
22
22
|
// check-coverage-guard.cjs (coverage regression guard vs baseline)
|
|
23
23
|
// analyze-state-space.cjs (state-space risk classification per TLA+ model)
|
|
24
|
+
// Gates (3) — gate-a-grounding.cjs, gate-b-abstraction.cjs, gate-c-validation.cjs
|
|
24
25
|
// Registry (N) — custom check commands from model-registry.json
|
|
25
26
|
// ─────────────────────────────────────────────────────────────
|
|
26
|
-
// Total:
|
|
27
|
+
// Total: 37+ steps (dynamic — registry can add more)
|
|
27
28
|
//
|
|
28
29
|
// Usage:
|
|
29
30
|
// node bin/run-formal-verify.cjs # all 28 steps
|
|
30
31
|
// node bin/run-formal-verify.cjs --concurrent # run tool groups in parallel (old behavior)
|
|
31
|
-
//
|
|
32
|
+
// NF_FORMAL_CONCURRENT=1 node bin/run-formal-verify.cjs # same via env var
|
|
32
33
|
// node bin/run-formal-verify.cjs --only=generate # source extraction only (2 steps)
|
|
33
34
|
// node bin/run-formal-verify.cjs --only=tla # TLA+ only (10 steps)
|
|
34
35
|
// node bin/run-formal-verify.cjs --only=alloy # Alloy only (8 steps)
|
|
@@ -63,7 +64,7 @@ for (const arg of process.argv.slice(2)) {
|
|
|
63
64
|
}
|
|
64
65
|
|
|
65
66
|
// ── Runner picker maps ─────────────────────────────────────────────────────────
|
|
66
|
-
// Maps known
|
|
67
|
+
// Maps known nForma model names to their specialized runners. Unknown models
|
|
67
68
|
// fall back to generic runners (run-tlc.cjs, run-alloy.cjs, run-prism.cjs).
|
|
68
69
|
|
|
69
70
|
const TLA_RUNNER_MAP = {
|
|
@@ -328,9 +329,9 @@ const STATIC_STEPS = [
|
|
|
328
329
|
// ─ Source extraction — must run first so generated specs are fresh ──────────
|
|
329
330
|
{
|
|
330
331
|
tool: 'generate', id: 'generate:tla-from-xstate',
|
|
331
|
-
label: 'Generate TLA+ spec (
|
|
332
|
+
label: 'Generate TLA+ spec (NFQuorum_xstate.tla) + TLC model config from XState machine (xstate-to-tla)',
|
|
332
333
|
type: 'node', script: 'xstate-to-tla.cjs',
|
|
333
|
-
args: ['src/machines/
|
|
334
|
+
args: ['src/machines/nf-workflow.machine.ts', '--module=NFQuorum', '--config=.planning/formal/tla/guards/nf-workflow.json'],
|
|
334
335
|
},
|
|
335
336
|
{
|
|
336
337
|
tool: 'generate', id: 'generate:alloy-prism-specs',
|
|
@@ -394,6 +395,26 @@ const STATIC_STEPS = [
|
|
|
394
395
|
type: 'node', script: 'analyze-state-space.cjs', args: [],
|
|
395
396
|
nonCritical: true,
|
|
396
397
|
},
|
|
398
|
+
|
|
399
|
+
// ─ Gates — cross-layer alignment checks ───────────────────────────────────
|
|
400
|
+
{
|
|
401
|
+
tool: 'gates', id: 'gates:gate-a',
|
|
402
|
+
label: 'Gate A -- L1-L2 grounding alignment score',
|
|
403
|
+
type: 'node', script: 'gate-a-grounding.cjs', args: [],
|
|
404
|
+
nonCritical: true,
|
|
405
|
+
},
|
|
406
|
+
{
|
|
407
|
+
tool: 'gates', id: 'gates:gate-b',
|
|
408
|
+
label: 'Gate B -- L2-L3 traceability alignment score',
|
|
409
|
+
type: 'node', script: 'gate-b-abstraction.cjs', args: [],
|
|
410
|
+
nonCritical: true,
|
|
411
|
+
},
|
|
412
|
+
{
|
|
413
|
+
tool: 'gates', id: 'gates:gate-c',
|
|
414
|
+
label: 'Gate C -- L3-TC validation alignment score',
|
|
415
|
+
type: 'node', script: 'gate-c-validation.cjs', args: [],
|
|
416
|
+
nonCritical: true,
|
|
417
|
+
},
|
|
397
418
|
];
|
|
398
419
|
|
|
399
420
|
// Discover dynamic model steps from ROOT/.planning/formal/
|
|
@@ -412,7 +433,7 @@ process.stdout.write(TAG + ' Discovered models: ' + uniqueDynamicSteps.length +
|
|
|
412
433
|
const argv = process.argv.slice(2);
|
|
413
434
|
const onlyArg = argv.find(a => a.startsWith('--only='));
|
|
414
435
|
const only = onlyArg ? onlyArg.split('=')[1] : null;
|
|
415
|
-
const concurrent = argv.includes('--concurrent') || process.env.
|
|
436
|
+
const concurrent = argv.includes('--concurrent') || process.env.NF_FORMAL_CONCURRENT === '1';
|
|
416
437
|
|
|
417
438
|
const steps = only
|
|
418
439
|
? STEPS.filter(s => s.tool === only || s.id === only)
|
|
@@ -421,7 +442,7 @@ const steps = only
|
|
|
421
442
|
if (only && steps.length === 0) {
|
|
422
443
|
process.stderr.write(
|
|
423
444
|
TAG + ' Unknown --only value: ' + only + '\n' +
|
|
424
|
-
TAG + ' Valid values: tla, alloy, prism, petri, generate, ci, uppaal, registry, or a step id\n'
|
|
445
|
+
TAG + ' Valid values: tla, alloy, prism, petri, generate, ci, uppaal, gates, registry, or a step id\n'
|
|
425
446
|
);
|
|
426
447
|
process.exit(1);
|
|
427
448
|
}
|
|
@@ -550,7 +571,7 @@ async function runOnce() {
|
|
|
550
571
|
fs.writeFileSync(ndjsonPath, '', 'utf8');
|
|
551
572
|
|
|
552
573
|
process.stdout.write(TAG + ' ' + HR + '\n');
|
|
553
|
-
process.stdout.write(TAG + '
|
|
574
|
+
process.stdout.write(TAG + ' nForma Formal Verification Suite\n');
|
|
554
575
|
if (only) {
|
|
555
576
|
process.stdout.write(TAG + ' Filter: --only=' + only + '\n');
|
|
556
577
|
}
|
|
@@ -641,7 +662,7 @@ if (watchArg) {
|
|
|
641
662
|
// by spawning with a custom cwd. __dirname-relative paths would always point
|
|
642
663
|
// to the real repo's src/machines/ regardless of spawn cwd, breaking isolation.
|
|
643
664
|
const machineDir = path.join(process.cwd(), 'src', 'machines');
|
|
644
|
-
const machineName = '
|
|
665
|
+
const machineName = 'nf-workflow.machine.ts';
|
|
645
666
|
let debounceTimer = null;
|
|
646
667
|
let running = false; // concurrent-run guard
|
|
647
668
|
let watcher = null;
|