@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.
- 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-preflight.cjs +89 -0
- 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 +36 -86
- 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/{qgsd-precompact.js → nf-precompact.js} +13 -13
- package/hooks/dist/{qgsd-prompt.js → nf-prompt.js} +110 -33
- package/hooks/dist/nf-session-start.js +185 -0
- package/hooks/dist/{qgsd-slot-correlator.js → nf-slot-correlator.js} +13 -5
- package/hooks/dist/{qgsd-spec-regen.js → nf-spec-regen.js} +17 -8
- package/hooks/dist/{qgsd-statusline.js → nf-statusline.js} +12 -3
- package/hooks/dist/{qgsd-stop.js → nf-stop.js} +152 -18
- package/hooks/dist/{qgsd-token-collector.js → nf-token-collector.js} +12 -4
- package/hooks/dist/unified-mcp-server.mjs +2 -2
- package/package.json +6 -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,153 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
// bin/build-layer-manifest.cjs
|
|
4
|
+
// Classifies all formal models in model-registry.json into L1/L2/L3 layers
|
|
5
|
+
// and generates layer-manifest.json. Extends model-registry.json with
|
|
6
|
+
// source_layer, gate_maturity, and layer_maturity fields per model.
|
|
7
|
+
//
|
|
8
|
+
// Requirements: INTG-01, INTG-05, INTG-06
|
|
9
|
+
|
|
10
|
+
const fs = require('fs');
|
|
11
|
+
const path = require('path');
|
|
12
|
+
|
|
13
|
+
const ROOT = process.env.PROJECT_ROOT || path.join(__dirname, '..');
|
|
14
|
+
const REGISTRY_PATH = path.join(ROOT, '.planning', 'formal', 'model-registry.json');
|
|
15
|
+
const MANIFEST_PATH = path.join(ROOT, '.planning', 'formal', 'layer-manifest.json');
|
|
16
|
+
const SPEC_DIR = path.join(ROOT, '.planning', 'formal', 'spec');
|
|
17
|
+
|
|
18
|
+
const JSON_FLAG = process.argv.includes('--json');
|
|
19
|
+
|
|
20
|
+
// ── Classification rules ────────────────────────────────────────────────────
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Classify a model path into L1, L2, or L3.
|
|
24
|
+
*
|
|
25
|
+
* L3 (Reasoning): formalism "tla", "alloy", "prism", "uppaal"; files in tla/, alloy/, prism/ directories
|
|
26
|
+
* L2 (Semantics): spec/invariants.md files; XState machine definition; assumption-gaps.md
|
|
27
|
+
* L1 (Evidence): formalism "trace" or "redaction"; conformance-events.jsonl; debt.json; observe-handler outputs
|
|
28
|
+
*/
|
|
29
|
+
function classifyModel(modelPath) {
|
|
30
|
+
const normalized = modelPath.replace(/\\/g, '/');
|
|
31
|
+
|
|
32
|
+
// L3: files in tla/, alloy/, prism/ directories or with those extensions
|
|
33
|
+
if (/\/(tla|alloy|prism)\//.test(normalized)) return 'L3';
|
|
34
|
+
if (/\.(tla|als|pm)$/.test(normalized)) return 'L3';
|
|
35
|
+
|
|
36
|
+
// L2: spec/*/invariants.md, xstate machine definitions, assumption-gaps
|
|
37
|
+
if (/\/spec\/[^/]+\/invariants\.md$/.test(normalized)) return 'L2';
|
|
38
|
+
if (/xstate|machine\.js|machine\.ts/.test(normalized)) return 'L2';
|
|
39
|
+
if (/assumption-gaps/.test(normalized)) return 'L2';
|
|
40
|
+
|
|
41
|
+
// L1: trace, redaction, conformance-events, debt, observe-handler
|
|
42
|
+
if (/conformance-events/.test(normalized)) return 'L1';
|
|
43
|
+
if (/debt\.json/.test(normalized)) return 'L1';
|
|
44
|
+
if (/observe-handler/.test(normalized)) return 'L1';
|
|
45
|
+
if (/trace|redaction/.test(normalized)) return 'L1';
|
|
46
|
+
|
|
47
|
+
// Fallback: classify by check-result formalism if available
|
|
48
|
+
return 'L1'; // default to L1 (evidence)
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Determine layer_maturity for a model.
|
|
53
|
+
* Level 0: ungrounded (default)
|
|
54
|
+
* Level 1: has L2 semantic declarations (spec invariants.md exists for related module)
|
|
55
|
+
*/
|
|
56
|
+
function computeLayerMaturity(modelPath, specModules) {
|
|
57
|
+
const normalized = modelPath.replace(/\\/g, '/');
|
|
58
|
+
// Check if any spec module name appears in the model path
|
|
59
|
+
for (const mod of specModules) {
|
|
60
|
+
if (normalized.toLowerCase().includes(mod.toLowerCase())) {
|
|
61
|
+
return 1;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
return 0;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// ── Main ────────────────────────────────────────────────────────────────────
|
|
68
|
+
|
|
69
|
+
function main() {
|
|
70
|
+
// Read registry
|
|
71
|
+
const registry = JSON.parse(fs.readFileSync(REGISTRY_PATH, 'utf8'));
|
|
72
|
+
const models = registry.models;
|
|
73
|
+
const modelPaths = Object.keys(models);
|
|
74
|
+
|
|
75
|
+
// Discover spec modules with invariants.md
|
|
76
|
+
const specModules = [];
|
|
77
|
+
if (fs.existsSync(SPEC_DIR)) {
|
|
78
|
+
for (const entry of fs.readdirSync(SPEC_DIR, { withFileTypes: true })) {
|
|
79
|
+
if (entry.isDirectory()) {
|
|
80
|
+
const invPath = path.join(SPEC_DIR, entry.name, 'invariants.md');
|
|
81
|
+
if (fs.existsSync(invPath)) {
|
|
82
|
+
specModules.push(entry.name);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Classify each model and extend registry
|
|
89
|
+
const layers = { L1: [], L2: [], L3: [] };
|
|
90
|
+
const maturityDist = { 0: 0, 1: 0 };
|
|
91
|
+
|
|
92
|
+
for (const modelPath of modelPaths) {
|
|
93
|
+
const layer = classifyModel(modelPath);
|
|
94
|
+
const layerMaturity = computeLayerMaturity(modelPath, specModules);
|
|
95
|
+
const gateMat = 'ADVISORY';
|
|
96
|
+
|
|
97
|
+
// Extend registry entry
|
|
98
|
+
models[modelPath].source_layer = layer;
|
|
99
|
+
models[modelPath].gate_maturity = gateMat;
|
|
100
|
+
models[modelPath].layer_maturity = layerMaturity;
|
|
101
|
+
|
|
102
|
+
// Add to manifest layers
|
|
103
|
+
layers[layer].push({
|
|
104
|
+
path: modelPath,
|
|
105
|
+
description: models[modelPath].description || '',
|
|
106
|
+
grounding_status: layerMaturity > 0 ? 'has_semantic_declarations' : 'ungrounded'
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
maturityDist[layerMaturity] = (maturityDist[layerMaturity] || 0) + 1;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// Write updated model-registry.json
|
|
113
|
+
registry.last_sync = new Date().toISOString();
|
|
114
|
+
fs.writeFileSync(REGISTRY_PATH, JSON.stringify(registry, null, 2) + '\n', 'utf8');
|
|
115
|
+
|
|
116
|
+
// Generate layer-manifest.json
|
|
117
|
+
const manifest = {
|
|
118
|
+
schema_version: '1',
|
|
119
|
+
generated: new Date().toISOString(),
|
|
120
|
+
layers,
|
|
121
|
+
gate_relationships: {
|
|
122
|
+
A: { from: 'L1', to: 'L2', description: 'Evidence grounds semantic declarations' },
|
|
123
|
+
B: { from: 'L2', to: 'L3', description: 'Semantic declarations trace to reasoning models' },
|
|
124
|
+
C: { from: 'L3', to: 'TC', description: 'Reasoning models prove traceability completeness' }
|
|
125
|
+
},
|
|
126
|
+
summary: {
|
|
127
|
+
total_models: modelPaths.length,
|
|
128
|
+
L1_count: layers.L1.length,
|
|
129
|
+
L2_count: layers.L2.length,
|
|
130
|
+
L3_count: layers.L3.length,
|
|
131
|
+
maturity_distribution: maturityDist,
|
|
132
|
+
spec_modules_with_invariants: specModules.length
|
|
133
|
+
}
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
fs.writeFileSync(MANIFEST_PATH, JSON.stringify(manifest, null, 2) + '\n', 'utf8');
|
|
137
|
+
|
|
138
|
+
// Output
|
|
139
|
+
if (JSON_FLAG) {
|
|
140
|
+
console.log(JSON.stringify(manifest, null, 2));
|
|
141
|
+
} else {
|
|
142
|
+
console.log(`Layer Manifest Generated`);
|
|
143
|
+
console.log(` Total models: ${modelPaths.length}`);
|
|
144
|
+
console.log(` L1 (Evidence): ${layers.L1.length}`);
|
|
145
|
+
console.log(` L2 (Semantics): ${layers.L2.length}`);
|
|
146
|
+
console.log(` L3 (Reasoning): ${layers.L3.length}`);
|
|
147
|
+
console.log(` Maturity 0 (ungrounded): ${maturityDist[0] || 0}`);
|
|
148
|
+
console.log(` Maturity 1 (has semantics): ${maturityDist[1] || 0}`);
|
|
149
|
+
console.log(` Spec modules with invariants: ${specModules.length}`);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
main();
|
package/bin/call-quorum-slot.cjs
CHANGED
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
* Reads providers.json, dispatches to the slot's CLI (subprocess) or HTTP provider,
|
|
17
17
|
* prints the response text to stdout.
|
|
18
18
|
*
|
|
19
|
-
* Used by
|
|
19
|
+
* Used by nf-quorum-orchestrator (sub-agent) which cannot access MCP tools.
|
|
20
20
|
*
|
|
21
21
|
* Exit codes: 0 = success, 1 = error (message on stderr)
|
|
22
22
|
*/
|
|
@@ -205,8 +205,8 @@ if (!slot) {
|
|
|
205
205
|
// ─── Find providers.json ───────────────────────────────────────────────────────
|
|
206
206
|
function findProviders() {
|
|
207
207
|
const searchPaths = [
|
|
208
|
-
path.join(__dirname, 'providers.json'), // same dir (
|
|
209
|
-
path.join(os.homedir(), '.claude', '
|
|
208
|
+
path.join(__dirname, 'providers.json'), // same dir (nf-bin)
|
|
209
|
+
path.join(os.homedir(), '.claude', 'nf-bin', 'providers.json'), // installed fallback
|
|
210
210
|
];
|
|
211
211
|
|
|
212
212
|
// Also derive path from unified-1 MCP server config in ~/.claude.json
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
// bin/ccr-secure-config.cjs
|
|
3
|
-
// Reads the 3 CCR provider API keys from keytar (
|
|
3
|
+
// Reads the 3 CCR provider API keys from keytar (nforma service) and writes them
|
|
4
4
|
// into ~/.claude-code-router/config.json with chmod 600.
|
|
5
5
|
// Designed to be called at session start and on-demand. Fail-silent when keytar
|
|
6
6
|
// is unavailable or keys are not yet stored.
|
|
@@ -17,7 +17,7 @@ const CONFIG_PATH = path.join(os.homedir(), '.claude-code-router', 'config.json'
|
|
|
17
17
|
// Locate secrets.cjs — try installed global path first, then local dev path.
|
|
18
18
|
function findSecrets() {
|
|
19
19
|
const candidates = [
|
|
20
|
-
path.join(os.homedir(), '.claude', '
|
|
20
|
+
path.join(os.homedir(), '.claude', 'nf-bin', 'secrets.cjs'), // installed path
|
|
21
21
|
path.join(__dirname, 'secrets.cjs'), // local dev path
|
|
22
22
|
];
|
|
23
23
|
for (const p of candidates) {
|
|
@@ -39,9 +39,9 @@ async function main() {
|
|
|
39
39
|
|
|
40
40
|
let akashKey, togetherKey, fireworksKey;
|
|
41
41
|
try {
|
|
42
|
-
akashKey = await secrets.get('
|
|
43
|
-
togetherKey = await secrets.get('
|
|
44
|
-
fireworksKey = await secrets.get('
|
|
42
|
+
akashKey = await secrets.get('nforma', 'AKASHML_API_KEY');
|
|
43
|
+
togetherKey = await secrets.get('nforma', 'TOGETHER_API_KEY');
|
|
44
|
+
fireworksKey = await secrets.get('nforma', 'FIREWORKS_API_KEY');
|
|
45
45
|
} catch (e) {
|
|
46
46
|
process.stderr.write('[ccr-secure-config] keytar unavailable: ' + e.message + '\n');
|
|
47
47
|
process.exit(0);
|
|
@@ -5,7 +5,7 @@ const fs = require('fs');
|
|
|
5
5
|
const path = require('path');
|
|
6
6
|
|
|
7
7
|
// ---------------------------------------------------------------------------
|
|
8
|
-
// Forbidden SDK list — LLM SDKs that
|
|
8
|
+
// Forbidden SDK list — LLM SDKs that nForma must not bundle (ARCH-10)
|
|
9
9
|
// ---------------------------------------------------------------------------
|
|
10
10
|
|
|
11
11
|
const FORBIDDEN_SDKS = [
|
package/bin/check-mcp-health.cjs
CHANGED
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
* Usage:
|
|
13
13
|
* node bin/check-mcp-health.cjs [--timeout-ms N] [--json]
|
|
14
14
|
*
|
|
15
|
-
* Designed to be called at the start of /
|
|
15
|
+
* Designed to be called at the start of /nf:quorum to skip
|
|
16
16
|
* unresponsive servers before making full inference calls.
|
|
17
17
|
*/
|
|
18
18
|
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
*
|
|
14
14
|
* No LLM inference is performed — this completes in ~2–3 seconds.
|
|
15
15
|
*
|
|
16
|
-
* TTL cache at ~/.claude/
|
|
16
|
+
* TTL cache at ~/.claude/nf-provider-cache.json:
|
|
17
17
|
* - DOWN entries: 5 minutes TTL
|
|
18
18
|
* - UP entries: 3 minutes TTL
|
|
19
19
|
* Cache is read before probing; stale or missing → probe runs normally.
|
|
@@ -54,7 +54,7 @@ const NO_CACHE = hasFlag('--no-cache');
|
|
|
54
54
|
const CACHE_STATUS = hasFlag('--cache-status');
|
|
55
55
|
|
|
56
56
|
// ─── TTL cache constants ──────────────────────────────────────────────────────
|
|
57
|
-
const CACHE_FILE = path.join(os.homedir(), '.claude', '
|
|
57
|
+
const CACHE_FILE = path.join(os.homedir(), '.claude', 'nf-provider-cache.json');
|
|
58
58
|
const TTL_DOWN_MS = 300000; // 5 minutes
|
|
59
59
|
const TTL_UP_MS = 180000; // 3 minutes
|
|
60
60
|
|
|
@@ -122,11 +122,11 @@ try {
|
|
|
122
122
|
process.exit(1);
|
|
123
123
|
}
|
|
124
124
|
|
|
125
|
-
// Load quorum_active from ~/.claude/
|
|
125
|
+
// Load quorum_active from ~/.claude/nf.json (project config takes precedence)
|
|
126
126
|
let quorumActive = [];
|
|
127
127
|
try {
|
|
128
|
-
const globalQgsd = path.join(os.homedir(), '.claude', '
|
|
129
|
-
const projQgsd = path.join(process.cwd(), '.claude', '
|
|
128
|
+
const globalQgsd = path.join(os.homedir(), '.claude', 'nf.json');
|
|
129
|
+
const projQgsd = path.join(process.cwd(), '.claude', 'nf.json');
|
|
130
130
|
for (const cfgPath of [globalQgsd, projQgsd]) {
|
|
131
131
|
try {
|
|
132
132
|
const cfgRaw = JSON.parse(fs.readFileSync(cfgPath, 'utf8'));
|
|
@@ -239,7 +239,7 @@ function probeUrl(baseUrl, apiKey) {
|
|
|
239
239
|
const parsed = new URL(probeUrl);
|
|
240
240
|
const lib = parsed.protocol === 'https:' ? https : http;
|
|
241
241
|
|
|
242
|
-
const headers = { 'User-Agent': '
|
|
242
|
+
const headers = { 'User-Agent': 'nf-health-check/1.0' };
|
|
243
243
|
if (apiKey) headers['Authorization'] = `Bearer ${apiKey}`;
|
|
244
244
|
|
|
245
245
|
const req = lib.request(
|
package/bin/check-spec-sync.cjs
CHANGED
|
@@ -3,22 +3,22 @@
|
|
|
3
3
|
// bin/check-spec-sync.cjs
|
|
4
4
|
// Verifies that formal specs stay in sync with the XState machine.
|
|
5
5
|
//
|
|
6
|
-
// The XState machine (src/machines/
|
|
6
|
+
// The XState machine (src/machines/nf-workflow.machine.ts) is the SOURCE OF TRUTH.
|
|
7
7
|
// Formal specs must mirror it — not the other way around.
|
|
8
8
|
//
|
|
9
9
|
// Checks:
|
|
10
|
-
// 1. State names in
|
|
10
|
+
// 1. State names in NFQuorum.tla TypeOK match the XState machine states
|
|
11
11
|
// 2. MaxDeliberation in MCsafety.cfg and MCliveness.cfg matches the XState context default
|
|
12
|
-
// 3. Initial state in
|
|
12
|
+
// 3. Initial state in NFQuorum.tla Init matches the XState initial state
|
|
13
13
|
// 4. Alloy .als files do not reference state names or guard names not in XState machine
|
|
14
|
-
// 5. Guard names in XState machine match keys in .planning/formal/tla/guards/
|
|
14
|
+
// 5. Guard names in XState machine match keys in .planning/formal/tla/guards/nf-workflow.json (bidirectional)
|
|
15
15
|
//
|
|
16
16
|
// Exit 0 = in sync; Exit 1 = drift detected.
|
|
17
17
|
// Usage: node bin/check-spec-sync.cjs [--tla-path=<path>] [--guards-path=<path>]
|
|
18
18
|
//
|
|
19
19
|
// CLI overrides (for fixture-based tests):
|
|
20
|
-
// --tla-path=<abs-path> Override path to
|
|
21
|
-
// --guards-path=<abs-path> Override path to guards JSON (default: .planning/formal/tla/guards/
|
|
20
|
+
// --tla-path=<abs-path> Override path to NFQuorum.tla (default: .planning/formal/tla/NFQuorum.tla)
|
|
21
|
+
// --guards-path=<abs-path> Override path to guards JSON (default: .planning/formal/tla/guards/nf-workflow.json)
|
|
22
22
|
|
|
23
23
|
const { buildSync } = require('esbuild');
|
|
24
24
|
const fs = require('fs');
|
|
@@ -35,11 +35,11 @@ const guardsPathOverride = process.argv.find(a => a.startsWith('--guards-path=')
|
|
|
35
35
|
// Resolved paths (absolute)
|
|
36
36
|
const TLA_ABS_PATH = tlaPathOverride
|
|
37
37
|
? tlaPathOverride.slice('--tla-path='.length)
|
|
38
|
-
: path.join(ROOT, '.planning', 'formal', 'tla', '
|
|
38
|
+
: path.join(ROOT, '.planning', 'formal', 'tla', 'NFQuorum.tla');
|
|
39
39
|
|
|
40
40
|
const GUARDS_ABS_PATH = guardsPathOverride
|
|
41
41
|
? guardsPathOverride.slice('--guards-path='.length)
|
|
42
|
-
: path.join(ROOT, '.planning', 'formal', 'tla', 'guards', '
|
|
42
|
+
: path.join(ROOT, '.planning', 'formal', 'tla', 'guards', 'nf-workflow.json');
|
|
43
43
|
|
|
44
44
|
// ── Load files ───────────────────────────────────────────────────────────────
|
|
45
45
|
function load(rel) {
|
|
@@ -67,7 +67,7 @@ const livenessCfg = load('.planning/formal/tla/MCliveness.cfg');
|
|
|
67
67
|
// Compile the TypeScript machine to a temporary CJS bundle, then require() it.
|
|
68
68
|
// This gives us the live machine object — no regex parsing of raw source.
|
|
69
69
|
|
|
70
|
-
const MACHINE_FILE = path.join(ROOT, 'src/machines/
|
|
70
|
+
const MACHINE_FILE = path.join(ROOT, 'src/machines/nf-workflow.machine.ts');
|
|
71
71
|
const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'check-spec-sync-'));
|
|
72
72
|
const tmpBundle = path.join(tmpDir, 'machine.cjs');
|
|
73
73
|
|
|
@@ -128,7 +128,7 @@ if (machineObj && machineObj.config) {
|
|
|
128
128
|
// Fallback: use regex if esbuild fails (e.g., in environments without esbuild)
|
|
129
129
|
process.stderr.write('[check-spec-sync] esbuild extraction failed: ' + xstateExtractError + '\n');
|
|
130
130
|
process.stderr.write('[check-spec-sync] Falling back to regex extraction (limited)\n');
|
|
131
|
-
const machineSrc = load('src/machines/
|
|
131
|
+
const machineSrc = load('src/machines/nf-workflow.machine.ts');
|
|
132
132
|
xstateStateNames = (machineSrc.match(/^ ([A-Z_]+):\s*\{/gm) || []).map(l => l.trim().split(':')[0]);
|
|
133
133
|
const md = machineSrc.match(/maxDeliberation:\s*(\d+)/);
|
|
134
134
|
xstateMaxDelib = md ? parseInt(md[1], 10) : null;
|
|
@@ -161,7 +161,7 @@ function fail(msg) { errors.push(' FAIL ' + msg); }
|
|
|
161
161
|
function warn(msg) { warnings.push(' WARN ' + msg); }
|
|
162
162
|
function ok(msg) { process.stdout.write(' OK ' + msg + '\n'); }
|
|
163
163
|
|
|
164
|
-
process.stdout.write('\n[check-spec-sync] Source of truth: src/machines/
|
|
164
|
+
process.stdout.write('\n[check-spec-sync] Source of truth: src/machines/nf-workflow.machine.ts\n\n');
|
|
165
165
|
|
|
166
166
|
// Check 1: State names
|
|
167
167
|
if (xstateStateNames.length === 0) {
|
|
@@ -170,7 +170,7 @@ if (xstateStateNames.length === 0) {
|
|
|
170
170
|
ok('XState states: ' + xstateStateNames.join(', '));
|
|
171
171
|
|
|
172
172
|
if (tlaPhaseValues.length === 0) {
|
|
173
|
-
fail('Could not parse phase values from
|
|
173
|
+
fail('Could not parse phase values from NFQuorum.tla TypeOK');
|
|
174
174
|
} else {
|
|
175
175
|
ok('TLA+ phases: ' + tlaPhaseValues.join(', '));
|
|
176
176
|
|
|
@@ -182,7 +182,7 @@ if (xstateStateNames.length === 0) {
|
|
|
182
182
|
}
|
|
183
183
|
if (extra.length > 0) {
|
|
184
184
|
fail('TLA+ TypeOK has orphaned phases not in XState machine: ' + extra.join(', ') +
|
|
185
|
-
'\n (These TLA+ phases have no corresponding XState state — update
|
|
185
|
+
'\n (These TLA+ phases have no corresponding XState state — update NFQuorum.tla)');
|
|
186
186
|
}
|
|
187
187
|
if (missing.length === 0 && extra.length === 0) {
|
|
188
188
|
ok('State names match exactly');
|
|
@@ -226,7 +226,7 @@ if (xstateInitial === null) {
|
|
|
226
226
|
ok('XState initial state: ' + xstateInitial);
|
|
227
227
|
|
|
228
228
|
if (tlaInitPhase === null) {
|
|
229
|
-
fail('Could not parse Init phase from
|
|
229
|
+
fail('Could not parse Init phase from NFQuorum.tla');
|
|
230
230
|
} else if (tlaInitPhase !== xstateInitial) {
|
|
231
231
|
fail(
|
|
232
232
|
'TLA+ Init sets phase="' + tlaInitPhase +
|
|
@@ -244,7 +244,7 @@ if (xstateInitial === null) {
|
|
|
244
244
|
//
|
|
245
245
|
// Note: Alloy orphan detection uses warn() not fail() because Alloy models are intentional
|
|
246
246
|
// abstractions — they may use different predicate names (e.g., MajorityReached instead of
|
|
247
|
-
// minQuorumMet). The authoritative guard mapping check is in Check 5 (guards/
|
|
247
|
+
// minQuorumMet). The authoritative guard mapping check is in Check 5 (guards/nf-workflow.json).
|
|
248
248
|
const alloyDir = path.join(ROOT, '.planning', 'formal', 'alloy');
|
|
249
249
|
if (fs.existsSync(alloyDir)) {
|
|
250
250
|
const alsFiles = fs.readdirSync(alloyDir).filter(f => f.endsWith('.als'));
|
|
@@ -282,17 +282,17 @@ if (fs.existsSync(alloyDir)) {
|
|
|
282
282
|
// Mapping context:
|
|
283
283
|
// XState uses camelCase guard names (minQuorumMet, noInfiniteDeliberation, phaseMonotonicallyAdvances).
|
|
284
284
|
// TLA+ uses PascalCase predicates (MinQuorumMet, DeliberationBounded) — different names, same semantics.
|
|
285
|
-
// The bridge is .planning/formal/tla/guards/
|
|
285
|
+
// The bridge is .planning/formal/tla/guards/nf-workflow.json, which maps XState guard names to TLA+ expressions.
|
|
286
286
|
//
|
|
287
|
-
// If a guard is renamed in the XState machine, it must also be updated in guards/
|
|
288
|
-
// If a mapping entry is removed from guards/
|
|
287
|
+
// If a guard is renamed in the XState machine, it must also be updated in guards/nf-workflow.json.
|
|
288
|
+
// If a mapping entry is removed from guards/nf-workflow.json without removing the guard from
|
|
289
289
|
// the machine (or vice versa), this check will detect the inconsistency.
|
|
290
290
|
//
|
|
291
|
-
// This check also corroborates by scanning
|
|
291
|
+
// This check also corroborates by scanning NFQuorum.tla source text for camelCase guard name
|
|
292
292
|
// occurrences (they appear in the header comment block as documentation of guard translations).
|
|
293
293
|
if (xstateGuardNames.length > 0) {
|
|
294
294
|
if (!fs.existsSync(GUARDS_ABS_PATH)) {
|
|
295
|
-
fail('.planning/formal/tla/guards/
|
|
295
|
+
fail('.planning/formal/tla/guards/nf-workflow.json not found — cannot verify guard name sync');
|
|
296
296
|
} else {
|
|
297
297
|
let guardsJson = null;
|
|
298
298
|
try {
|
|
@@ -306,28 +306,28 @@ if (xstateGuardNames.length > 0) {
|
|
|
306
306
|
ok('Guards JSON mapped names: ' + mappedGuardNames.join(', '));
|
|
307
307
|
|
|
308
308
|
// Forward check: XState guard → guards JSON (drift detection)
|
|
309
|
-
// If a guard is renamed in XState but guards/
|
|
309
|
+
// If a guard is renamed in XState but guards/nf-workflow.json still has the old name, this fires.
|
|
310
310
|
const missingFromMapping = xstateGuardNames.filter(g => !mappedGuardNames.includes(g));
|
|
311
311
|
if (missingFromMapping.length > 0) {
|
|
312
312
|
fail(
|
|
313
|
-
'XState guards not found in guards/
|
|
314
|
-
'\n (Update .planning/formal/tla/guards/
|
|
313
|
+
'XState guards not found in guards/nf-workflow.json: ' + missingFromMapping.join(', ') +
|
|
314
|
+
'\n (Update .planning/formal/tla/guards/nf-workflow.json to map these guard names to their TLA+ predicates)'
|
|
315
315
|
);
|
|
316
316
|
}
|
|
317
317
|
|
|
318
318
|
// Reverse check: guards JSON → XState (orphan detection)
|
|
319
|
-
// If a guard mapping entry exists in guards/
|
|
319
|
+
// If a guard mapping entry exists in guards/nf-workflow.json but the guard was removed from
|
|
320
320
|
// the XState machine, this is an orphaned mapping.
|
|
321
321
|
const orphanedGuards = mappedGuardNames.filter(g => !xstateGuardNames.includes(g));
|
|
322
322
|
if (orphanedGuards.length > 0) {
|
|
323
323
|
fail(
|
|
324
|
-
'guards/
|
|
324
|
+
'guards/nf-workflow.json references guards not in XState machine: ' + orphanedGuards.join(', ') +
|
|
325
325
|
'\n (These guard mappings are orphaned — remove them or restore the XState guard)'
|
|
326
326
|
);
|
|
327
327
|
}
|
|
328
328
|
|
|
329
329
|
if (missingFromMapping.length === 0 && orphanedGuards.length === 0) {
|
|
330
|
-
ok('Guard names match exactly between XState machine and guards/
|
|
330
|
+
ok('Guard names match exactly between XState machine and guards/nf-workflow.json');
|
|
331
331
|
}
|
|
332
332
|
|
|
333
333
|
// Corroboration: check TLA+ source mentions guard names in comment references
|
|
@@ -16,10 +16,10 @@ const VALIDATOR_FILE = 'bin/validate-traces.cjs';
|
|
|
16
16
|
|
|
17
17
|
const KNOWN_EMITTERS = [
|
|
18
18
|
'bin/validate-traces.cjs',
|
|
19
|
-
'hooks/
|
|
20
|
-
'hooks/
|
|
21
|
-
'hooks/dist/
|
|
22
|
-
'hooks/dist/
|
|
19
|
+
'hooks/nf-stop.js',
|
|
20
|
+
'hooks/nf-prompt.js',
|
|
21
|
+
'hooks/dist/nf-stop.js',
|
|
22
|
+
'hooks/dist/nf-prompt.js',
|
|
23
23
|
];
|
|
24
24
|
|
|
25
25
|
/**
|
|
@@ -44,7 +44,7 @@ function checkSchemaDrift(changedFiles) {
|
|
|
44
44
|
// but validator_updated alone does not satisfy emitter_updated — need a non-validator emitter
|
|
45
45
|
// OR validate-traces.cjs satisfies both when it IS the emitter.
|
|
46
46
|
// Per spec: atomic requires validator AND an emitter. validate-traces.cjs counts as emitter only
|
|
47
|
-
// when a separate hook file (
|
|
47
|
+
// when a separate hook file (nf-stop.js, nf-prompt.js, etc.) is also present.
|
|
48
48
|
const NON_VALIDATOR_EMITTERS = KNOWN_EMITTERS.filter(e => e !== VALIDATOR_FILE);
|
|
49
49
|
const emitterUpdated = changedFiles.some(f =>
|
|
50
50
|
NON_VALIDATOR_EMITTERS.some(emitter => f === emitter || f.includes(emitter))
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
// bin/conformance-schema.cjs
|
|
3
3
|
// Single source of truth for conformance event field enumerations.
|
|
4
|
-
// Imported by hooks (
|
|
4
|
+
// Imported by hooks (nf-stop.js, nf-prompt.js, nf-circuit-breaker.js) and validate-traces.cjs.
|
|
5
5
|
// NEVER add external require() calls — hooks have zero runtime dependencies.
|
|
6
6
|
|
|
7
|
-
const VALID_ACTIONS = ['quorum_start', 'quorum_complete', 'quorum_block', 'deliberation_round', 'circuit_break'];
|
|
7
|
+
const VALID_ACTIONS = ['quorum_start', 'quorum_complete', 'quorum_block', 'deliberation_round', 'circuit_break', 'cache_hit', 'budget_warn', 'budget_downgrade', 'stall_detected', 'security_sweep'];
|
|
8
8
|
const VALID_PHASES = ['IDLE', 'COLLECTING_VOTES', 'DELIBERATING', 'DECIDED'];
|
|
9
9
|
const VALID_OUTCOMES = ['APPROVE', 'BLOCK', 'UNAVAILABLE', 'DELIBERATE'];
|
|
10
10
|
const schema_version = '1';
|