@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
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
'use strict';
|
|
3
|
-
// bin/
|
|
3
|
+
// bin/nf-solve.cjs
|
|
4
4
|
// Consistency solver orchestrator: sweeps Requirements->Formal->Tests->Code->Docs,
|
|
5
5
|
// computes a residual vector per layer transition, and auto-closes gaps.
|
|
6
6
|
//
|
|
7
|
-
// Layer transitions (8 forward + 3 reverse):
|
|
7
|
+
// Layer transitions (8 forward + 3 reverse + 3 layer alignment):
|
|
8
8
|
// R->F: Requirements without formal model coverage
|
|
9
9
|
// F->T: Formal invariants without test backing
|
|
10
10
|
// C->F: Code constants diverging from formal specs
|
|
@@ -17,22 +17,27 @@
|
|
|
17
17
|
// C->R: Source modules in bin/hooks/ with no requirement tracing
|
|
18
18
|
// T->R: Test files with no @req annotation or formal-test-sync mapping
|
|
19
19
|
// D->R: Doc capability claims without requirement backing
|
|
20
|
+
// Layer alignment (cross-layer gate checks):
|
|
21
|
+
// L1->L2: Gate A grounding alignment score
|
|
22
|
+
// L2->L3: Gate B traceability alignment score
|
|
23
|
+
// L3->TC: Gate C validation alignment score
|
|
20
24
|
//
|
|
21
25
|
// Usage:
|
|
22
|
-
// node bin/
|
|
23
|
-
// node bin/
|
|
24
|
-
// node bin/
|
|
25
|
-
// node bin/
|
|
26
|
-
// node bin/
|
|
27
|
-
// node bin/
|
|
26
|
+
// node bin/nf-solve.cjs # full sync, up to 3 iterations
|
|
27
|
+
// node bin/nf-solve.cjs --report-only # single sweep, no mutations
|
|
28
|
+
// node bin/nf-solve.cjs --max-iterations=1
|
|
29
|
+
// node bin/nf-solve.cjs --json # machine-readable output
|
|
30
|
+
// node bin/nf-solve.cjs --verbose # pipe child stderr to parent stderr
|
|
31
|
+
// node bin/nf-solve.cjs --fast # skip F->C and T->C layers for sub-second iteration
|
|
28
32
|
//
|
|
29
33
|
// Requirements: QUICK-140
|
|
30
34
|
|
|
31
35
|
const fs = require('fs');
|
|
32
36
|
const path = require('path');
|
|
37
|
+
const os = require('os');
|
|
33
38
|
const { spawnSync } = require('child_process');
|
|
34
39
|
|
|
35
|
-
const TAG = '[
|
|
40
|
+
const TAG = '[nf-solve]';
|
|
36
41
|
let ROOT = process.cwd();
|
|
37
42
|
const SCRIPT_DIR = __dirname;
|
|
38
43
|
const DEFAULT_MAX_ITERATIONS = 3;
|
|
@@ -249,7 +254,7 @@ function discoverDocFiles() {
|
|
|
249
254
|
const subWarnings = detectUninitializedSubmodules(marker.docs);
|
|
250
255
|
for (const w of subWarnings) {
|
|
251
256
|
console.error(
|
|
252
|
-
`[
|
|
257
|
+
`[nf-solve] WARNING: docs.${w.docKey} overlaps submodule "${w.name}" ` +
|
|
253
258
|
`(${w.submodule}) which is not initialized. Run: git submodule update --init ${w.submodule}`
|
|
254
259
|
);
|
|
255
260
|
}
|
|
@@ -748,6 +753,15 @@ function sweepTtoC() {
|
|
|
748
753
|
}
|
|
749
754
|
|
|
750
755
|
// Runner mode: node-test (default)
|
|
756
|
+
// V8 coverage collection: create temp dir and set NODE_V8_COVERAGE env var
|
|
757
|
+
let covDir = null;
|
|
758
|
+
try {
|
|
759
|
+
covDir = fs.mkdtempSync(path.join(os.tmpdir(), 'nf-solve-cov-'));
|
|
760
|
+
spawnOpts.env = Object.assign({}, process.env, { NODE_V8_COVERAGE: covDir });
|
|
761
|
+
} catch (e) {
|
|
762
|
+
covDir = null; // fail-open: continue without coverage
|
|
763
|
+
}
|
|
764
|
+
|
|
751
765
|
let result;
|
|
752
766
|
try {
|
|
753
767
|
result = spawnSync(process.execPath, ['--test'], spawnOpts);
|
|
@@ -789,6 +803,26 @@ function sweepTtoC() {
|
|
|
789
803
|
todoCount = 0;
|
|
790
804
|
}
|
|
791
805
|
|
|
806
|
+
// Collect V8 coverage data from temp directory (fail-open)
|
|
807
|
+
let coverageData = null;
|
|
808
|
+
try {
|
|
809
|
+
if (covDir && fs.existsSync(covDir)) {
|
|
810
|
+
const covFiles = fs.readdirSync(covDir).filter(f => f.endsWith('.json'));
|
|
811
|
+
coverageData = [];
|
|
812
|
+
for (const cf of covFiles) {
|
|
813
|
+
const raw = fs.readFileSync(path.join(covDir, cf), 'utf8');
|
|
814
|
+
coverageData.push(JSON.parse(raw));
|
|
815
|
+
}
|
|
816
|
+
if (coverageData.length === 0) coverageData = null;
|
|
817
|
+
}
|
|
818
|
+
} catch (e) {
|
|
819
|
+
coverageData = null; // fail-open
|
|
820
|
+
} finally {
|
|
821
|
+
try {
|
|
822
|
+
if (covDir) fs.rmSync(covDir, { recursive: true, force: true });
|
|
823
|
+
} catch (e) { /* ignore cleanup errors */ }
|
|
824
|
+
}
|
|
825
|
+
|
|
792
826
|
// Scope-based auto-detection: if scope is "generated-stubs-only", check if all failures
|
|
793
827
|
// are outside .planning/formal/generated-stubs/
|
|
794
828
|
if (tToCConfig.runner === 'node-test' && tToCConfig.scope === 'generated-stubs-only' && failCount > 0) {
|
|
@@ -805,6 +839,7 @@ function sweepTtoC() {
|
|
|
805
839
|
todo: todoCount,
|
|
806
840
|
runner_mismatch: true,
|
|
807
841
|
warning: 'All ' + failLines.length + ' failures are outside generated-stubs scope — likely runner mismatch',
|
|
842
|
+
v8_coverage: coverageData,
|
|
808
843
|
},
|
|
809
844
|
};
|
|
810
845
|
}
|
|
@@ -818,10 +853,93 @@ function sweepTtoC() {
|
|
|
818
853
|
failed: failCount,
|
|
819
854
|
skipped: skipCount,
|
|
820
855
|
todo: todoCount,
|
|
856
|
+
v8_coverage: coverageData,
|
|
821
857
|
},
|
|
822
858
|
};
|
|
823
859
|
}
|
|
824
860
|
|
|
861
|
+
/**
|
|
862
|
+
* Cross-reference V8 coverage data against formal-test-sync recipe source_files.
|
|
863
|
+
* Identifies "false green" properties: tests pass but exercise none of the implementing source files.
|
|
864
|
+
* Returns { available: false } when coverage data is null/undefined.
|
|
865
|
+
*/
|
|
866
|
+
function crossReferenceFormalCoverage(v8CoverageData) {
|
|
867
|
+
if (!v8CoverageData) return { available: false };
|
|
868
|
+
|
|
869
|
+
try {
|
|
870
|
+
const syncData = loadFormalTestSync();
|
|
871
|
+
const recipes = (syncData && syncData.recipes) ? syncData.recipes : [];
|
|
872
|
+
|
|
873
|
+
// Build set of covered absolute file paths from V8 data
|
|
874
|
+
const coveredFiles = new Set();
|
|
875
|
+
for (const entry of v8CoverageData) {
|
|
876
|
+
const results = entry.result || [];
|
|
877
|
+
for (const r of results) {
|
|
878
|
+
if (!r.url) continue;
|
|
879
|
+
const filePath = r.url.startsWith('file://') ? r.url.slice(7) : r.url;
|
|
880
|
+
const resolved = path.resolve(filePath);
|
|
881
|
+
// A file is "covered" if ANY function range has count > 0
|
|
882
|
+
const hasCoverage = (r.functions || []).some(fn =>
|
|
883
|
+
(fn.ranges || []).some(range => range.count > 0)
|
|
884
|
+
);
|
|
885
|
+
if (hasCoverage) coveredFiles.add(resolved);
|
|
886
|
+
}
|
|
887
|
+
}
|
|
888
|
+
|
|
889
|
+
const coverageRatios = [];
|
|
890
|
+
const falseGreens = [];
|
|
891
|
+
let propertiesWithTests = 0;
|
|
892
|
+
let fullyCovered = 0;
|
|
893
|
+
let partiallyCovered = 0;
|
|
894
|
+
let uncovered = 0;
|
|
895
|
+
|
|
896
|
+
for (const recipe of recipes) {
|
|
897
|
+
const sourceFiles = recipe.source_files_absolute || [];
|
|
898
|
+
if (sourceFiles.length === 0) continue;
|
|
899
|
+
const hasTest = !!(recipe.test_file || recipe.test_files);
|
|
900
|
+
if (hasTest) propertiesWithTests++;
|
|
901
|
+
|
|
902
|
+
let coveredCount = 0;
|
|
903
|
+
for (const sf of sourceFiles) {
|
|
904
|
+
if (coveredFiles.has(path.resolve(sf))) coveredCount++;
|
|
905
|
+
}
|
|
906
|
+
const ratio = coveredCount / sourceFiles.length;
|
|
907
|
+
const propName = recipe.property || recipe.invariant || recipe.id || 'unknown';
|
|
908
|
+
|
|
909
|
+
coverageRatios.push({ property: propName, ratio: ratio });
|
|
910
|
+
|
|
911
|
+
if (ratio === 0 && hasTest) {
|
|
912
|
+
falseGreens.push({
|
|
913
|
+
property: propName,
|
|
914
|
+
test_file: recipe.test_file || (recipe.test_files || [])[0] || 'unknown',
|
|
915
|
+
source_files: sourceFiles,
|
|
916
|
+
covered: 0,
|
|
917
|
+
});
|
|
918
|
+
uncovered++;
|
|
919
|
+
} else if (ratio < 1) {
|
|
920
|
+
partiallyCovered++;
|
|
921
|
+
} else {
|
|
922
|
+
fullyCovered++;
|
|
923
|
+
}
|
|
924
|
+
}
|
|
925
|
+
|
|
926
|
+
return {
|
|
927
|
+
available: true,
|
|
928
|
+
total_properties: recipes.length,
|
|
929
|
+
properties_with_tests: propertiesWithTests,
|
|
930
|
+
false_greens: falseGreens,
|
|
931
|
+
coverage_ratios: coverageRatios,
|
|
932
|
+
summary: {
|
|
933
|
+
fully_covered: fullyCovered,
|
|
934
|
+
partially_covered: partiallyCovered,
|
|
935
|
+
uncovered: uncovered,
|
|
936
|
+
},
|
|
937
|
+
};
|
|
938
|
+
} catch (e) {
|
|
939
|
+
return { available: false };
|
|
940
|
+
}
|
|
941
|
+
}
|
|
942
|
+
|
|
825
943
|
/**
|
|
826
944
|
* F->C: Formal verification to Code.
|
|
827
945
|
* Returns { residual: N, detail: {...} }
|
|
@@ -1818,11 +1936,147 @@ function assembleReverseCandidates(c_to_r, t_to_r, d_to_r) {
|
|
|
1818
1936
|
};
|
|
1819
1937
|
}
|
|
1820
1938
|
|
|
1939
|
+
// ── Layer alignment sweeps ────────────────────────────────────────────────────
|
|
1940
|
+
|
|
1941
|
+
/**
|
|
1942
|
+
* L1->L2: Gate A grounding alignment score.
|
|
1943
|
+
* Spawns gate-a-grounding.cjs --json and computes normalized 0-10 residual.
|
|
1944
|
+
* Returns { residual: N, detail: {...} }
|
|
1945
|
+
*/
|
|
1946
|
+
function sweepL1toL2() {
|
|
1947
|
+
if (fastMode) {
|
|
1948
|
+
return { residual: -1, detail: { skipped: true, reason: 'fast mode' } };
|
|
1949
|
+
}
|
|
1950
|
+
|
|
1951
|
+
const result = spawnTool('bin/gate-a-grounding.cjs', ['--json']);
|
|
1952
|
+
|
|
1953
|
+
// Gate scripts exit 1 when target_met is false but still produce valid JSON.
|
|
1954
|
+
// Only bail on spawn errors (no stdout to parse).
|
|
1955
|
+
if (!result.ok && !result.stdout) {
|
|
1956
|
+
return { residual: -1, detail: { error: true, stderr: (result.stderr || '').slice(0, 500) } };
|
|
1957
|
+
}
|
|
1958
|
+
|
|
1959
|
+
try {
|
|
1960
|
+
const data = JSON.parse(result.stdout);
|
|
1961
|
+
const score = data.grounding_score || 0;
|
|
1962
|
+
const residual = Math.ceil((1 - score) * 10);
|
|
1963
|
+
return {
|
|
1964
|
+
residual: residual,
|
|
1965
|
+
detail: {
|
|
1966
|
+
grounding_score: score,
|
|
1967
|
+
target: 0.8,
|
|
1968
|
+
gap: 0.8 - score,
|
|
1969
|
+
unexplained_breakdown: {
|
|
1970
|
+
instrumentation_bug: (data.unexplained_counts && data.unexplained_counts.instrumentation_bug) || 0,
|
|
1971
|
+
model_gap: (data.unexplained_counts && data.unexplained_counts.model_gap) || 0,
|
|
1972
|
+
genuine_violation: (data.unexplained_counts && data.unexplained_counts.genuine_violation) || 0,
|
|
1973
|
+
},
|
|
1974
|
+
},
|
|
1975
|
+
};
|
|
1976
|
+
} catch (err) {
|
|
1977
|
+
return { residual: -1, detail: { error: true, stderr: 'JSON parse failed: ' + err.message } };
|
|
1978
|
+
}
|
|
1979
|
+
}
|
|
1980
|
+
|
|
1981
|
+
/**
|
|
1982
|
+
* L2->L3: Gate B traceability alignment score.
|
|
1983
|
+
* Spawns gate-b-abstraction.cjs --json and computes normalized 0-10 residual.
|
|
1984
|
+
* Returns { residual: N, detail: {...} }
|
|
1985
|
+
*/
|
|
1986
|
+
function sweepL2toL3() {
|
|
1987
|
+
if (fastMode) {
|
|
1988
|
+
return { residual: -1, detail: { skipped: true, reason: 'fast mode' } };
|
|
1989
|
+
}
|
|
1990
|
+
|
|
1991
|
+
const result = spawnTool('bin/gate-b-abstraction.cjs', ['--json']);
|
|
1992
|
+
|
|
1993
|
+
// Gate scripts exit 1 when target_met is false but still produce valid JSON.
|
|
1994
|
+
// Only bail on spawn errors (no stdout to parse).
|
|
1995
|
+
if (!result.ok && !result.stdout) {
|
|
1996
|
+
return { residual: -1, detail: { error: true } };
|
|
1997
|
+
}
|
|
1998
|
+
|
|
1999
|
+
try {
|
|
2000
|
+
const data = JSON.parse(result.stdout);
|
|
2001
|
+
const score = data.gate_b_score || 0;
|
|
2002
|
+
const orphanedCount = data.orphaned_entries || 0;
|
|
2003
|
+
const rawResidual = Math.ceil((1 - score) * 10) + orphanedCount;
|
|
2004
|
+
const residual = Math.min(rawResidual, 10);
|
|
2005
|
+
return {
|
|
2006
|
+
residual: residual,
|
|
2007
|
+
detail: {
|
|
2008
|
+
gate_b_score: score,
|
|
2009
|
+
orphaned_count: orphanedCount,
|
|
2010
|
+
residual_capped: rawResidual > 10,
|
|
2011
|
+
},
|
|
2012
|
+
};
|
|
2013
|
+
} catch (err) {
|
|
2014
|
+
return { residual: -1, detail: { error: true } };
|
|
2015
|
+
}
|
|
2016
|
+
}
|
|
2017
|
+
|
|
2018
|
+
/**
|
|
2019
|
+
* L3->TC: Gate C validation alignment score.
|
|
2020
|
+
* Spawns gate-c-validation.cjs --json and computes normalized 0-10 residual.
|
|
2021
|
+
* Checks test-recipes.json staleness before scoring.
|
|
2022
|
+
* Returns { residual: N, detail: {...} }
|
|
2023
|
+
*/
|
|
2024
|
+
function sweepL3toTC() {
|
|
2025
|
+
if (fastMode) {
|
|
2026
|
+
return { residual: -1, detail: { skipped: true, reason: 'fast mode' } };
|
|
2027
|
+
}
|
|
2028
|
+
|
|
2029
|
+
// Check if test-recipes.json exists and staleness
|
|
2030
|
+
const recipesPath = path.join(ROOT, '.planning', 'formal', 'test-recipes', 'test-recipes.json');
|
|
2031
|
+
const catalogPath = path.join(ROOT, '.planning', 'formal', 'reasoning', 'failure-mode-catalog.json');
|
|
2032
|
+
|
|
2033
|
+
if (fs.existsSync(recipesPath) && fs.existsSync(catalogPath)) {
|
|
2034
|
+
try {
|
|
2035
|
+
const recipesMtime = fs.statSync(recipesPath).mtimeMs;
|
|
2036
|
+
const catalogMtime = fs.statSync(catalogPath).mtimeMs;
|
|
2037
|
+
if (recipesMtime < catalogMtime) {
|
|
2038
|
+
if (reportOnly) {
|
|
2039
|
+
process.stderr.write(TAG + ' WARNING: test-recipes.json is stale; run test-recipe-gen.cjs to update\n');
|
|
2040
|
+
} else {
|
|
2041
|
+
spawnTool('bin/test-recipe-gen.cjs', []);
|
|
2042
|
+
}
|
|
2043
|
+
}
|
|
2044
|
+
} catch (e) {
|
|
2045
|
+
// fail-open: skip staleness check
|
|
2046
|
+
}
|
|
2047
|
+
}
|
|
2048
|
+
|
|
2049
|
+
const result = spawnTool('bin/gate-c-validation.cjs', ['--json']);
|
|
2050
|
+
|
|
2051
|
+
// Gate scripts exit 1 when target_met is false but still produce valid JSON.
|
|
2052
|
+
// Only bail on spawn errors (no stdout to parse).
|
|
2053
|
+
if (!result.ok && !result.stdout) {
|
|
2054
|
+
return { residual: -1, detail: { error: true, stderr: (result.stderr || '').slice(0, 500) } };
|
|
2055
|
+
}
|
|
2056
|
+
|
|
2057
|
+
try {
|
|
2058
|
+
const data = JSON.parse(result.stdout);
|
|
2059
|
+
const score = data.gate_c_score || 0;
|
|
2060
|
+
const residual = Math.ceil((1 - score) * 10);
|
|
2061
|
+
return {
|
|
2062
|
+
residual: residual,
|
|
2063
|
+
detail: {
|
|
2064
|
+
gate_c_score: score,
|
|
2065
|
+
unvalidated_count: data.unvalidated_entries || 0,
|
|
2066
|
+
total_failure_modes: data.total_entries || 0,
|
|
2067
|
+
total_recipes: data.validated_entries || 0,
|
|
2068
|
+
},
|
|
2069
|
+
};
|
|
2070
|
+
} catch (err) {
|
|
2071
|
+
return { residual: -1, detail: { error: true, stderr: 'JSON parse failed: ' + err.message } };
|
|
2072
|
+
}
|
|
2073
|
+
}
|
|
2074
|
+
|
|
1821
2075
|
// ── Residual computation ─────────────────────────────────────────────────────
|
|
1822
2076
|
|
|
1823
2077
|
/**
|
|
1824
|
-
* Computes residual vector for all layer transitions (8 forward + 3 reverse).
|
|
1825
|
-
* Returns residual object with forward layers + reverse discovery layers.
|
|
2078
|
+
* Computes residual vector for all layer transitions (8 forward + 3 reverse + 3 layer alignment).
|
|
2079
|
+
* Returns residual object with forward layers + reverse discovery layers + layer alignment.
|
|
1826
2080
|
*/
|
|
1827
2081
|
function computeResidual() {
|
|
1828
2082
|
const r_to_f = sweepRtoF();
|
|
@@ -1831,6 +2085,12 @@ function computeResidual() {
|
|
|
1831
2085
|
const t_to_c = fastMode
|
|
1832
2086
|
? { residual: -1, detail: { skipped: true, reason: 'fast mode' } }
|
|
1833
2087
|
: sweepTtoC();
|
|
2088
|
+
|
|
2089
|
+
// Cross-reference V8 coverage against formal-test-sync recipe source_files
|
|
2090
|
+
if (t_to_c.detail && t_to_c.detail.v8_coverage) {
|
|
2091
|
+
t_to_c.detail.formal_coverage = crossReferenceFormalCoverage(t_to_c.detail.v8_coverage);
|
|
2092
|
+
}
|
|
2093
|
+
|
|
1834
2094
|
const f_to_c = fastMode
|
|
1835
2095
|
? { residual: -1, detail: { skipped: true, reason: 'fast mode' } }
|
|
1836
2096
|
: sweepFtoC();
|
|
@@ -1861,6 +2121,17 @@ function computeResidual() {
|
|
|
1861
2121
|
// Assemble deduplicated reverse candidates
|
|
1862
2122
|
const assembled_candidates = assembleReverseCandidates(c_to_r, t_to_r, d_to_r);
|
|
1863
2123
|
|
|
2124
|
+
// Layer alignment sweeps (cross-layer gate checks) — skip in fast mode
|
|
2125
|
+
const skipLayer = { residual: -1, detail: { skipped: true, reason: 'fast mode' } };
|
|
2126
|
+
const l1_to_l2 = fastMode ? skipLayer : sweepL1toL2();
|
|
2127
|
+
const l2_to_l3 = fastMode ? skipLayer : sweepL2toL3();
|
|
2128
|
+
const l3_to_tc = fastMode ? skipLayer : sweepL3toTC();
|
|
2129
|
+
|
|
2130
|
+
const layer_total =
|
|
2131
|
+
(l1_to_l2.residual >= 0 ? l1_to_l2.residual : 0) +
|
|
2132
|
+
(l2_to_l3.residual >= 0 ? l2_to_l3.residual : 0) +
|
|
2133
|
+
(l3_to_tc.residual >= 0 ? l3_to_tc.residual : 0);
|
|
2134
|
+
|
|
1864
2135
|
return {
|
|
1865
2136
|
r_to_f,
|
|
1866
2137
|
f_to_t,
|
|
@@ -1873,8 +2144,12 @@ function computeResidual() {
|
|
|
1873
2144
|
c_to_r,
|
|
1874
2145
|
t_to_r,
|
|
1875
2146
|
d_to_r,
|
|
2147
|
+
l1_to_l2,
|
|
2148
|
+
l2_to_l3,
|
|
2149
|
+
l3_to_tc,
|
|
1876
2150
|
assembled_candidates,
|
|
1877
2151
|
total,
|
|
2152
|
+
layer_total,
|
|
1878
2153
|
reverse_discovery_total,
|
|
1879
2154
|
timestamp: new Date().toISOString(),
|
|
1880
2155
|
};
|
|
@@ -1999,7 +2274,7 @@ function healthIndicator(residual) {
|
|
|
1999
2274
|
function formatReport(iterations, finalResidual, converged) {
|
|
2000
2275
|
const lines = [];
|
|
2001
2276
|
|
|
2002
|
-
lines.push('[
|
|
2277
|
+
lines.push('[nf-solve] Consistency Solver Report');
|
|
2003
2278
|
lines.push('');
|
|
2004
2279
|
lines.push(
|
|
2005
2280
|
'Iterations: ' +
|
|
@@ -2097,6 +2372,30 @@ function formatReport(iterations, finalResidual, converged) {
|
|
|
2097
2372
|
}
|
|
2098
2373
|
}
|
|
2099
2374
|
}
|
|
2375
|
+
|
|
2376
|
+
// Layer Alignment section
|
|
2377
|
+
if (finalResidual.l1_to_l2 || finalResidual.l2_to_l3 || finalResidual.l3_to_tc) {
|
|
2378
|
+
lines.push('');
|
|
2379
|
+
lines.push('Layer Alignment (cross-layer gate checks):');
|
|
2380
|
+
lines.push('─────────────────────────────────────────────');
|
|
2381
|
+
|
|
2382
|
+
const layerRows = [
|
|
2383
|
+
{ label: 'L1 -> L2 (Gate A)', residual: finalResidual.l1_to_l2 ? finalResidual.l1_to_l2.residual : -1 },
|
|
2384
|
+
{ label: 'L2 -> L3 (Gate B)', residual: finalResidual.l2_to_l3 ? finalResidual.l2_to_l3.residual : -1 },
|
|
2385
|
+
{ label: 'L3 -> TC (Gate C)', residual: finalResidual.l3_to_tc ? finalResidual.l3_to_tc.residual : -1 },
|
|
2386
|
+
];
|
|
2387
|
+
|
|
2388
|
+
for (const row of layerRows) {
|
|
2389
|
+
const res = row.residual >= 0 ? row.residual : '?';
|
|
2390
|
+
const health = healthIndicator(row.residual);
|
|
2391
|
+
const line = row.label.padEnd(28) + String(res).padStart(4) + ' ' + health;
|
|
2392
|
+
lines.push(line);
|
|
2393
|
+
}
|
|
2394
|
+
|
|
2395
|
+
const layerTotal = finalResidual.layer_total || 0;
|
|
2396
|
+
lines.push('─────────────────────────────────────────────');
|
|
2397
|
+
lines.push('Layer total: ' + layerTotal);
|
|
2398
|
+
}
|
|
2100
2399
|
lines.push('');
|
|
2101
2400
|
|
|
2102
2401
|
// Per-layer detail sections (only non-zero)
|
|
@@ -2161,6 +2460,16 @@ function formatReport(iterations, finalResidual, converged) {
|
|
|
2161
2460
|
lines.push('');
|
|
2162
2461
|
}
|
|
2163
2462
|
|
|
2463
|
+
// F->T->C coverage summary (shown regardless of T->C residual)
|
|
2464
|
+
if (finalResidual.t_to_c.detail && finalResidual.t_to_c.detail.formal_coverage &&
|
|
2465
|
+
finalResidual.t_to_c.detail.formal_coverage.available === true) {
|
|
2466
|
+
const fc = finalResidual.t_to_c.detail.formal_coverage;
|
|
2467
|
+
lines.push(' F->T->C coverage: ' + fc.summary.fully_covered + '/' +
|
|
2468
|
+
fc.total_properties + ' properties fully traced (' +
|
|
2469
|
+
fc.false_greens.length + ' false greens)');
|
|
2470
|
+
lines.push('');
|
|
2471
|
+
}
|
|
2472
|
+
|
|
2164
2473
|
if (finalResidual.f_to_c.residual > 0 || (finalResidual.f_to_c.detail && finalResidual.f_to_c.detail.inconclusive > 0)) {
|
|
2165
2474
|
lines.push('## F -> C (Formal -> Code)');
|
|
2166
2475
|
const detail = finalResidual.f_to_c.detail;
|
|
@@ -2324,7 +2633,7 @@ function truncateResidualDetail(residual) {
|
|
|
2324
2633
|
*/
|
|
2325
2634
|
function formatJSON(iterations, finalResidual, converged) {
|
|
2326
2635
|
const health = {};
|
|
2327
|
-
for (const key of ['r_to_f', 'f_to_t', 'c_to_f', 't_to_c', 'f_to_c', 'r_to_d', 'd_to_c', 'p_to_f', 'c_to_r', 't_to_r', 'd_to_r']) {
|
|
2636
|
+
for (const key of ['r_to_f', 'f_to_t', 'c_to_f', 't_to_c', 'f_to_c', 'r_to_d', 'd_to_c', 'p_to_f', 'c_to_r', 't_to_r', 'd_to_r', 'l1_to_l2', 'l2_to_l3', 'l3_to_tc']) {
|
|
2328
2637
|
const res = finalResidual[key] ? finalResidual[key].residual : -1;
|
|
2329
2638
|
health[key] = healthIndicator(res).split(/\s+/)[1]; // Extract GREEN/YELLOW/RED/UNKNOWN
|
|
2330
2639
|
}
|
|
@@ -2466,8 +2775,12 @@ module.exports = {
|
|
|
2466
2775
|
sweepCtoR,
|
|
2467
2776
|
sweepTtoR,
|
|
2468
2777
|
sweepDtoR,
|
|
2778
|
+
sweepL1toL2,
|
|
2779
|
+
sweepL2toL3,
|
|
2780
|
+
sweepL3toTC,
|
|
2469
2781
|
assembleReverseCandidates,
|
|
2470
2782
|
classifyCandidate,
|
|
2783
|
+
crossReferenceFormalCoverage,
|
|
2471
2784
|
};
|
|
2472
2785
|
|
|
2473
2786
|
// ── Entry point ──────────────────────────────────────────────────────────────
|
package/bin/observe-config.cjs
CHANGED
|
@@ -11,6 +11,10 @@ const path = require('node:path');
|
|
|
11
11
|
const ISSUE_TYPES = ['github', 'sentry', 'sentry-feedback', 'bash'];
|
|
12
12
|
// Source types that default to "drift"
|
|
13
13
|
const DRIFT_TYPES = ['prometheus', 'grafana', 'logstash'];
|
|
14
|
+
// Source types that default to "upstream"
|
|
15
|
+
const UPSTREAM_TYPES = ['upstream'];
|
|
16
|
+
// Source types that default to "deps"
|
|
17
|
+
const DEPS_TYPES = ['deps'];
|
|
14
18
|
|
|
15
19
|
/**
|
|
16
20
|
* Parse a YAML value string into appropriate JS type
|
|
@@ -324,6 +328,10 @@ function loadObserveConfig(configPath, basePath) {
|
|
|
324
328
|
source.issue_type = 'issue';
|
|
325
329
|
} else if (DRIFT_TYPES.includes(source.type)) {
|
|
326
330
|
source.issue_type = 'drift';
|
|
331
|
+
} else if (UPSTREAM_TYPES.includes(source.type)) {
|
|
332
|
+
source.issue_type = 'upstream';
|
|
333
|
+
} else if (DEPS_TYPES.includes(source.type)) {
|
|
334
|
+
source.issue_type = 'deps';
|
|
327
335
|
}
|
|
328
336
|
}
|
|
329
337
|
|