@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,178 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
// bin/instrumentation-map.cjs
|
|
4
|
+
// Scans hooks for conformance event emission points and maps to state variables.
|
|
5
|
+
// Validates discovered actions against event-vocabulary.json.
|
|
6
|
+
//
|
|
7
|
+
// Requirement: EVID-01
|
|
8
|
+
|
|
9
|
+
const fs = require('fs');
|
|
10
|
+
const path = require('path');
|
|
11
|
+
|
|
12
|
+
const ROOT = process.env.PROJECT_ROOT || path.join(__dirname, '..');
|
|
13
|
+
const EVIDENCE_DIR = path.join(ROOT, '.planning', 'formal', 'evidence');
|
|
14
|
+
const VOCAB_PATH = path.join(EVIDENCE_DIR, 'event-vocabulary.json');
|
|
15
|
+
const OUTPUT_PATH = path.join(EVIDENCE_DIR, 'instrumentation-map.json');
|
|
16
|
+
|
|
17
|
+
const JSON_FLAG = process.argv.includes('--json');
|
|
18
|
+
|
|
19
|
+
// ── Hook scanning ───────────────────────────────────────────────────────────
|
|
20
|
+
|
|
21
|
+
const HOOK_FILES = [
|
|
22
|
+
'hooks/nf-prompt.js',
|
|
23
|
+
'hooks/nf-stop.js',
|
|
24
|
+
'hooks/nf-circuit-breaker.js',
|
|
25
|
+
];
|
|
26
|
+
|
|
27
|
+
// Also scan observe handlers
|
|
28
|
+
const OBSERVE_GLOB = 'bin/observe-handler-';
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Scan a file for conformance event emission points.
|
|
32
|
+
* Looks for appendFileSync calls near conformance-events and extracts action types.
|
|
33
|
+
*/
|
|
34
|
+
function scanFile(filePath) {
|
|
35
|
+
const absPath = path.join(ROOT, filePath);
|
|
36
|
+
if (!fs.existsSync(absPath)) return [];
|
|
37
|
+
|
|
38
|
+
const lines = fs.readFileSync(absPath, 'utf8').split('\n');
|
|
39
|
+
const emissionPoints = [];
|
|
40
|
+
|
|
41
|
+
for (let i = 0; i < lines.length; i++) {
|
|
42
|
+
const line = lines[i];
|
|
43
|
+
|
|
44
|
+
// Look for action: 'something' patterns (conformance event construction)
|
|
45
|
+
const actionMatch = line.match(/action:\s+['"]([^'"]+)['"]/);
|
|
46
|
+
if (actionMatch) {
|
|
47
|
+
const action = actionMatch[1];
|
|
48
|
+
|
|
49
|
+
// Look for state variable context in nearby lines (within 10 lines)
|
|
50
|
+
const contextLines = lines.slice(Math.max(0, i - 10), Math.min(lines.length, i + 10)).join('\n');
|
|
51
|
+
const stateVars = [];
|
|
52
|
+
|
|
53
|
+
// Extract state-related variables
|
|
54
|
+
if (/from_state|fromState|from:/i.test(contextLines)) stateVars.push('from_state');
|
|
55
|
+
if (/to_state|toState|to:/i.test(contextLines)) stateVars.push('to_state');
|
|
56
|
+
if (/verdict|decision/i.test(contextLines)) stateVars.push('verdict');
|
|
57
|
+
if (/fanOut|fan_out|quorumSize/i.test(contextLines)) stateVars.push('quorum_size');
|
|
58
|
+
if (/oscillation|breaker/i.test(contextLines)) stateVars.push('breaker_state');
|
|
59
|
+
|
|
60
|
+
emissionPoints.push({
|
|
61
|
+
file: filePath,
|
|
62
|
+
line_number: i + 1,
|
|
63
|
+
action,
|
|
64
|
+
state_variables: stateVars,
|
|
65
|
+
vocabulary_match: null, // filled later
|
|
66
|
+
xstate_event: null, // filled later
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// Also detect type: 'quorum_fallback_*' patterns
|
|
71
|
+
const typeMatch = line.match(/type:\s+['"]([^'"]+)['"]/);
|
|
72
|
+
if (typeMatch && /conformance|quorum_fallback/.test(typeMatch[1])) {
|
|
73
|
+
emissionPoints.push({
|
|
74
|
+
file: filePath,
|
|
75
|
+
line_number: i + 1,
|
|
76
|
+
action: typeMatch[1],
|
|
77
|
+
state_variables: [],
|
|
78
|
+
vocabulary_match: null,
|
|
79
|
+
xstate_event: null,
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
return emissionPoints;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// ── Main ────────────────────────────────────────────────────────────────────
|
|
88
|
+
|
|
89
|
+
function main() {
|
|
90
|
+
// Ensure output directory
|
|
91
|
+
if (!fs.existsSync(EVIDENCE_DIR)) {
|
|
92
|
+
fs.mkdirSync(EVIDENCE_DIR, { recursive: true });
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// Load vocabulary
|
|
96
|
+
const vocab = JSON.parse(fs.readFileSync(VOCAB_PATH, 'utf8'));
|
|
97
|
+
const vocabActions = vocab.vocabulary;
|
|
98
|
+
const vocabKeys = Object.keys(vocabActions).filter(k => k !== 'undefined');
|
|
99
|
+
|
|
100
|
+
// Scan hook files
|
|
101
|
+
let allEmissions = [];
|
|
102
|
+
for (const hookFile of HOOK_FILES) {
|
|
103
|
+
const points = scanFile(hookFile);
|
|
104
|
+
allEmissions.push(...points);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// Scan observe handlers
|
|
108
|
+
const binDir = path.join(ROOT, 'bin');
|
|
109
|
+
if (fs.existsSync(binDir)) {
|
|
110
|
+
const entries = fs.readdirSync(binDir);
|
|
111
|
+
for (const entry of entries) {
|
|
112
|
+
if (entry.startsWith('observe-handler-') && entry.endsWith('.cjs') && !entry.includes('.test.')) {
|
|
113
|
+
const points = scanFile(path.join('bin', entry));
|
|
114
|
+
allEmissions.push(...points);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// Validate against vocabulary and fill in xstate_event
|
|
120
|
+
const mappedActions = new Set();
|
|
121
|
+
for (const ep of allEmissions) {
|
|
122
|
+
if (ep.action in vocabActions) {
|
|
123
|
+
ep.vocabulary_match = true;
|
|
124
|
+
const vocabEntry = vocabActions[ep.action];
|
|
125
|
+
ep.xstate_event = vocabEntry.xstate_event; // may be null (e.g., mcp_call)
|
|
126
|
+
if (ep.xstate_event === null) {
|
|
127
|
+
ep.no_xstate_mapping = true;
|
|
128
|
+
}
|
|
129
|
+
mappedActions.add(ep.action);
|
|
130
|
+
} else {
|
|
131
|
+
ep.vocabulary_match = false;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// Coverage calculation: mapped vocabulary actions / total vocabulary actions (excluding "undefined")
|
|
136
|
+
const coveragePct = vocabKeys.length > 0
|
|
137
|
+
? Math.round((mappedActions.size / vocabKeys.length) * 1000) / 10
|
|
138
|
+
: 0;
|
|
139
|
+
|
|
140
|
+
const unmappedActions = vocabKeys.filter(k => !mappedActions.has(k));
|
|
141
|
+
|
|
142
|
+
// Build output
|
|
143
|
+
const result = {
|
|
144
|
+
schema_version: '1',
|
|
145
|
+
generated: new Date().toISOString(),
|
|
146
|
+
emission_points: allEmissions,
|
|
147
|
+
coverage: {
|
|
148
|
+
total_vocabulary_actions: vocabKeys.length,
|
|
149
|
+
mapped_actions: mappedActions.size,
|
|
150
|
+
coverage_pct: coveragePct,
|
|
151
|
+
},
|
|
152
|
+
unmapped_actions: unmappedActions,
|
|
153
|
+
summary: `Found ${allEmissions.length} emission points across ${HOOK_FILES.length} hooks and observe handlers. ` +
|
|
154
|
+
`Coverage: ${mappedActions.size}/${vocabKeys.length} vocabulary actions mapped (${coveragePct}%). ` +
|
|
155
|
+
`Unmapped: ${unmappedActions.join(', ') || 'none'}.`,
|
|
156
|
+
};
|
|
157
|
+
|
|
158
|
+
fs.writeFileSync(OUTPUT_PATH, JSON.stringify(result, null, 2) + '\n', 'utf8');
|
|
159
|
+
|
|
160
|
+
if (JSON_FLAG) {
|
|
161
|
+
console.log(JSON.stringify(result, null, 2));
|
|
162
|
+
} else {
|
|
163
|
+
console.log(`Instrumentation Map Generated`);
|
|
164
|
+
console.log(` Emission points: ${allEmissions.length}`);
|
|
165
|
+
console.log(` Coverage: ${mappedActions.size}/${vocabKeys.length} (${coveragePct}%)`);
|
|
166
|
+
if (unmappedActions.length > 0) {
|
|
167
|
+
console.log(` Unmapped: ${unmappedActions.join(', ')}`);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// Export for testing
|
|
173
|
+
module.exports = { scanFile, HOOK_FILES };
|
|
174
|
+
|
|
175
|
+
// Run if invoked directly
|
|
176
|
+
if (require.main === module) {
|
|
177
|
+
main();
|
|
178
|
+
}
|
|
@@ -0,0 +1,437 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
// bin/invariant-catalog.cjs
|
|
4
|
+
// Builds a unified invariant catalog aggregating declared (TLA+ .cfg, spec/invariants.md)
|
|
5
|
+
// and observed (trace-mined) invariants into a queryable JSON catalog.
|
|
6
|
+
// Requirements: SEM-01
|
|
7
|
+
//
|
|
8
|
+
// Usage:
|
|
9
|
+
// node bin/invariant-catalog.cjs # print summary to stdout
|
|
10
|
+
// node bin/invariant-catalog.cjs --json # print full catalog JSON to stdout
|
|
11
|
+
|
|
12
|
+
const fs = require('fs');
|
|
13
|
+
const path = require('path');
|
|
14
|
+
|
|
15
|
+
const ROOT = process.env.PROJECT_ROOT || path.join(__dirname, '..');
|
|
16
|
+
const FORMAL = path.join(ROOT, '.planning', 'formal');
|
|
17
|
+
const TLA_DIR = path.join(FORMAL, 'tla');
|
|
18
|
+
const SPEC_DIR = path.join(FORMAL, 'spec');
|
|
19
|
+
const OUT_DIR = path.join(FORMAL, 'semantics');
|
|
20
|
+
const OUT_FILE = path.join(OUT_DIR, 'invariant-catalog.json');
|
|
21
|
+
const CONFORMANCE_PATH = path.join(ROOT, '.planning', 'telemetry', 'conformance-events.jsonl');
|
|
22
|
+
const TRACE_STATS_PATH = path.join(FORMAL, 'evidence', 'trace-corpus-stats.json');
|
|
23
|
+
|
|
24
|
+
const JSON_FLAG = process.argv.includes('--json');
|
|
25
|
+
|
|
26
|
+
// ── Source A: TLC .cfg files ─────────────────────────────────────────────────
|
|
27
|
+
|
|
28
|
+
function parseCfgFiles() {
|
|
29
|
+
const invariants = [];
|
|
30
|
+
const cfgFiles = fs.readdirSync(TLA_DIR).filter(f => f.startsWith('MC') && f.endsWith('.cfg'));
|
|
31
|
+
|
|
32
|
+
for (const cfgFile of cfgFiles) {
|
|
33
|
+
const content = fs.readFileSync(path.join(TLA_DIR, cfgFile), 'utf8');
|
|
34
|
+
const lines = content.split('\n');
|
|
35
|
+
const config = cfgFile.replace(/\.cfg$/, '');
|
|
36
|
+
let inBlock = false;
|
|
37
|
+
|
|
38
|
+
for (let i = 0; i < lines.length; i++) {
|
|
39
|
+
const line = lines[i];
|
|
40
|
+
const trimmed = line.trim();
|
|
41
|
+
|
|
42
|
+
// Single-line: INVARIANT Name
|
|
43
|
+
const singleMatch = trimmed.match(/^INVARIANT\s+(\S+)$/);
|
|
44
|
+
if (singleMatch) {
|
|
45
|
+
invariants.push({
|
|
46
|
+
name: singleMatch[1],
|
|
47
|
+
source: 'tla_cfg',
|
|
48
|
+
source_file: `tla/${cfgFile}`,
|
|
49
|
+
type: 'declared',
|
|
50
|
+
formalism: 'tla',
|
|
51
|
+
config,
|
|
52
|
+
});
|
|
53
|
+
continue;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Single-line: PROPERTY Name
|
|
57
|
+
const propMatch = trimmed.match(/^PROPERTY\s+(\S+)$/);
|
|
58
|
+
if (propMatch) {
|
|
59
|
+
invariants.push({
|
|
60
|
+
name: propMatch[1],
|
|
61
|
+
source: 'tla_cfg',
|
|
62
|
+
source_file: `tla/${cfgFile}`,
|
|
63
|
+
type: 'declared',
|
|
64
|
+
formalism: 'tla',
|
|
65
|
+
config,
|
|
66
|
+
});
|
|
67
|
+
continue;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// Block: INVARIANTS keyword
|
|
71
|
+
if (trimmed === 'INVARIANTS') {
|
|
72
|
+
inBlock = true;
|
|
73
|
+
continue;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Block: PROPERTIES keyword
|
|
77
|
+
if (trimmed === 'PROPERTIES') {
|
|
78
|
+
inBlock = true;
|
|
79
|
+
continue;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// End block on next keyword, empty line, or non-indented line
|
|
83
|
+
if (inBlock) {
|
|
84
|
+
const isIndented = line.length > 0 && (line[0] === ' ' || line[0] === '\t');
|
|
85
|
+
if (!trimmed || (!isIndented && /^[A-Z]/.test(trimmed))) {
|
|
86
|
+
inBlock = false;
|
|
87
|
+
// re-process this line (might be a keyword)
|
|
88
|
+
if (trimmed.match(/^INVARIANT\s*/)) i--;
|
|
89
|
+
else if (trimmed.match(/^PROPERTY\s*/)) i--;
|
|
90
|
+
else if (trimmed.match(/^SPECIFICATION\s*/)) { /* skip */ }
|
|
91
|
+
else if (trimmed.match(/^CONSTANTS?\s*/)) { /* skip */ }
|
|
92
|
+
else if (trimmed.match(/^CONSTRAINT\s*/)) { /* skip */ }
|
|
93
|
+
else if (trimmed.match(/^CHECK_DEADLOCK\s*/)) { /* skip */ }
|
|
94
|
+
continue;
|
|
95
|
+
}
|
|
96
|
+
invariants.push({
|
|
97
|
+
name: trimmed,
|
|
98
|
+
source: 'tla_cfg',
|
|
99
|
+
source_file: `tla/${cfgFile}`,
|
|
100
|
+
type: 'declared',
|
|
101
|
+
formalism: 'tla',
|
|
102
|
+
config,
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
return invariants;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// ── Source B: spec/*/invariants.md files ──────────────────────────────────────
|
|
111
|
+
|
|
112
|
+
function parseSpecInvariants() {
|
|
113
|
+
const invariants = [];
|
|
114
|
+
if (!fs.existsSync(SPEC_DIR)) return invariants;
|
|
115
|
+
|
|
116
|
+
const specDirs = fs.readdirSync(SPEC_DIR).filter(d =>
|
|
117
|
+
fs.statSync(path.join(SPEC_DIR, d)).isDirectory()
|
|
118
|
+
);
|
|
119
|
+
|
|
120
|
+
for (const dir of specDirs) {
|
|
121
|
+
const invFile = path.join(SPEC_DIR, dir, 'invariants.md');
|
|
122
|
+
if (!fs.existsSync(invFile)) continue;
|
|
123
|
+
|
|
124
|
+
const content = fs.readFileSync(invFile, 'utf8');
|
|
125
|
+
const lines = content.split('\n');
|
|
126
|
+
let currentName = null;
|
|
127
|
+
let propertyExpr = null;
|
|
128
|
+
|
|
129
|
+
for (const line of lines) {
|
|
130
|
+
// ## section headers = invariant names
|
|
131
|
+
const headerMatch = line.match(/^##\s+(.+)$/);
|
|
132
|
+
if (headerMatch) {
|
|
133
|
+
// Save previous if exists
|
|
134
|
+
if (currentName) {
|
|
135
|
+
const formalism = (propertyExpr && /(<>|PROPERTY|liveness|LivenessProperty|Reachable|Eventually|Terminates|Progress)/i.test(propertyExpr || currentName))
|
|
136
|
+
? 'liveness' : 'safety';
|
|
137
|
+
invariants.push({
|
|
138
|
+
name: currentName,
|
|
139
|
+
source: 'spec_invariants_md',
|
|
140
|
+
source_file: `spec/${dir}/invariants.md`,
|
|
141
|
+
type: 'declared',
|
|
142
|
+
formalism,
|
|
143
|
+
property_expression: propertyExpr || null,
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
currentName = headerMatch[1].trim();
|
|
147
|
+
propertyExpr = null;
|
|
148
|
+
continue;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// **Property:** `expression`
|
|
152
|
+
const propMatch = line.match(/\*\*Property:\*\*\s*`([^`]+)`/);
|
|
153
|
+
if (propMatch && currentName) {
|
|
154
|
+
propertyExpr = propMatch[1];
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// Save last entry
|
|
159
|
+
if (currentName) {
|
|
160
|
+
const formalism = (propertyExpr && /<>|PROPERTY|liveness|LivenessProperty|Reachable|Eventually|Terminates|Progress/i.test(propertyExpr || currentName))
|
|
161
|
+
? 'liveness' : 'safety';
|
|
162
|
+
invariants.push({
|
|
163
|
+
name: currentName,
|
|
164
|
+
source: 'spec_invariants_md',
|
|
165
|
+
source_file: `spec/${dir}/invariants.md`,
|
|
166
|
+
type: 'declared',
|
|
167
|
+
formalism,
|
|
168
|
+
property_expression: propertyExpr || null,
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
return invariants;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// ── Source C: Observed invariants (curated trace checks) ─────────────────────
|
|
176
|
+
|
|
177
|
+
function mineObservedInvariants() {
|
|
178
|
+
const invariants = [];
|
|
179
|
+
|
|
180
|
+
// Load trace-corpus-stats.json for aggregate data
|
|
181
|
+
let traceStats = null;
|
|
182
|
+
if (fs.existsSync(TRACE_STATS_PATH)) {
|
|
183
|
+
traceStats = JSON.parse(fs.readFileSync(TRACE_STATS_PATH, 'utf8'));
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// Load conformance-events.jsonl for sequence-based checks
|
|
187
|
+
let events = [];
|
|
188
|
+
if (fs.existsSync(CONFORMANCE_PATH)) {
|
|
189
|
+
const raw = fs.readFileSync(CONFORMANCE_PATH, 'utf8').trim().split('\n');
|
|
190
|
+
for (const line of raw) {
|
|
191
|
+
try { events.push(JSON.parse(line)); } catch { /* skip malformed */ }
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
if (!traceStats && events.length === 0) {
|
|
196
|
+
process.stderr.write('Warning: No trace data available for observed invariants\n');
|
|
197
|
+
return invariants;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
const totalSessions = traceStats ? traceStats.sessions.length : 0;
|
|
201
|
+
|
|
202
|
+
// Check 1: quorum_start always precedes quorum_complete within a session
|
|
203
|
+
// Requires conformance-events.jsonl for event ordering per session
|
|
204
|
+
if (events.length > 0 && traceStats) {
|
|
205
|
+
let holds = true;
|
|
206
|
+
let sessionsChecked = 0;
|
|
207
|
+
|
|
208
|
+
for (const session of traceStats.sessions) {
|
|
209
|
+
const sessionStart = new Date(session.start).getTime();
|
|
210
|
+
const sessionEnd = new Date(session.end).getTime();
|
|
211
|
+
const sessionEvents = events.filter(e => {
|
|
212
|
+
const t = new Date(e.ts).getTime();
|
|
213
|
+
return t >= sessionStart && t <= sessionEnd;
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
const relevant = sessionEvents.filter(e =>
|
|
217
|
+
e.action === 'quorum_start' || e.action === 'quorum_complete'
|
|
218
|
+
);
|
|
219
|
+
|
|
220
|
+
let startCount = 0;
|
|
221
|
+
for (const evt of relevant) {
|
|
222
|
+
if (evt.action === 'quorum_start') startCount++;
|
|
223
|
+
if (evt.action === 'quorum_complete') {
|
|
224
|
+
if (startCount === 0) { holds = false; break; }
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
if (!holds) break;
|
|
228
|
+
sessionsChecked++;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
if (holds) {
|
|
232
|
+
invariants.push({
|
|
233
|
+
name: 'quorum_start_precedes_complete',
|
|
234
|
+
source: 'trace_mining',
|
|
235
|
+
type: 'observed',
|
|
236
|
+
property_expression: 'quorum_start always precedes quorum_complete within a session',
|
|
237
|
+
confidence: 'curated',
|
|
238
|
+
evidence_sessions: sessionsChecked,
|
|
239
|
+
});
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
// Check 2: circuit_break events only occur during active quorum sessions
|
|
244
|
+
// Requires conformance-events.jsonl to check session context
|
|
245
|
+
if (events.length > 0 && traceStats) {
|
|
246
|
+
let holds = true;
|
|
247
|
+
let sessionsChecked = 0;
|
|
248
|
+
|
|
249
|
+
for (const session of traceStats.sessions) {
|
|
250
|
+
const sessionStart = new Date(session.start).getTime();
|
|
251
|
+
const sessionEnd = new Date(session.end).getTime();
|
|
252
|
+
const sessionEvents = events.filter(e => {
|
|
253
|
+
const t = new Date(e.ts).getTime();
|
|
254
|
+
return t >= sessionStart && t <= sessionEnd;
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
const circuitBreaks = sessionEvents.filter(e => e.action === 'circuit_break');
|
|
258
|
+
const hasQuorumStart = sessionEvents.some(e => e.action === 'quorum_start');
|
|
259
|
+
|
|
260
|
+
if (circuitBreaks.length > 0 && !hasQuorumStart) {
|
|
261
|
+
holds = false;
|
|
262
|
+
break;
|
|
263
|
+
}
|
|
264
|
+
sessionsChecked++;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
if (holds) {
|
|
268
|
+
invariants.push({
|
|
269
|
+
name: 'circuit_break_within_quorum_session',
|
|
270
|
+
source: 'trace_mining',
|
|
271
|
+
type: 'observed',
|
|
272
|
+
property_expression: 'circuit_break events only occur during active quorum sessions',
|
|
273
|
+
confidence: 'curated',
|
|
274
|
+
evidence_sessions: sessionsChecked,
|
|
275
|
+
});
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
// Check 3: no session has more quorum_complete than quorum_start
|
|
280
|
+
// Can use trace-corpus-stats.json aggregate counts
|
|
281
|
+
if (traceStats) {
|
|
282
|
+
let holds = true;
|
|
283
|
+
let sessionsChecked = 0;
|
|
284
|
+
|
|
285
|
+
for (const session of traceStats.sessions) {
|
|
286
|
+
const starts = session.actions.quorum_start || 0;
|
|
287
|
+
const completes = session.actions.quorum_complete || 0;
|
|
288
|
+
if (completes > starts) {
|
|
289
|
+
holds = false;
|
|
290
|
+
break;
|
|
291
|
+
}
|
|
292
|
+
sessionsChecked++;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
if (holds) {
|
|
296
|
+
invariants.push({
|
|
297
|
+
name: 'complete_bounded_by_start',
|
|
298
|
+
source: 'trace_mining',
|
|
299
|
+
type: 'observed',
|
|
300
|
+
property_expression: 'no session has more quorum_complete events than quorum_start events',
|
|
301
|
+
confidence: 'curated',
|
|
302
|
+
evidence_sessions: sessionsChecked,
|
|
303
|
+
});
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
// Check 4: sessions with quorum_complete also have quorum_block
|
|
308
|
+
// Requires trace-corpus-stats.json -- quorum must block before completing
|
|
309
|
+
if (traceStats) {
|
|
310
|
+
let holds = true;
|
|
311
|
+
let sessionsChecked = 0;
|
|
312
|
+
|
|
313
|
+
for (const session of traceStats.sessions) {
|
|
314
|
+
const blocks = (session.actions.quorum_block || 0) + (session.actions.quorum_block_r3_2 || 0);
|
|
315
|
+
const completes = session.actions.quorum_complete || 0;
|
|
316
|
+
if (completes > 0 && blocks === 0) {
|
|
317
|
+
holds = false;
|
|
318
|
+
break;
|
|
319
|
+
}
|
|
320
|
+
sessionsChecked++;
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
if (holds) {
|
|
324
|
+
invariants.push({
|
|
325
|
+
name: 'complete_requires_block',
|
|
326
|
+
source: 'trace_mining',
|
|
327
|
+
type: 'observed',
|
|
328
|
+
property_expression: 'sessions with quorum_complete also have quorum_block events',
|
|
329
|
+
confidence: 'curated',
|
|
330
|
+
evidence_sessions: sessionsChecked,
|
|
331
|
+
});
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
// Check 5: event action counts are consistent (sum of actions = event_count)
|
|
336
|
+
// Requires trace-corpus-stats.json -- structural data integrity check
|
|
337
|
+
if (traceStats) {
|
|
338
|
+
let holds = true;
|
|
339
|
+
let sessionsChecked = 0;
|
|
340
|
+
|
|
341
|
+
for (const session of traceStats.sessions) {
|
|
342
|
+
const sum = Object.values(session.actions).reduce((a, b) => a + b, 0);
|
|
343
|
+
if (sum !== session.event_count) {
|
|
344
|
+
holds = false;
|
|
345
|
+
break;
|
|
346
|
+
}
|
|
347
|
+
sessionsChecked++;
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
if (holds) {
|
|
351
|
+
invariants.push({
|
|
352
|
+
name: 'action_count_consistency',
|
|
353
|
+
source: 'trace_mining',
|
|
354
|
+
type: 'observed',
|
|
355
|
+
property_expression: 'sum of per-action counts equals total event_count for every session',
|
|
356
|
+
confidence: 'curated',
|
|
357
|
+
evidence_sessions: sessionsChecked,
|
|
358
|
+
});
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
return invariants;
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
// ── Deduplication ────────────────────────────────────────────────────────────
|
|
366
|
+
|
|
367
|
+
function deduplicateInvariants(rawInvariants) {
|
|
368
|
+
const map = new Map();
|
|
369
|
+
|
|
370
|
+
for (const inv of rawInvariants) {
|
|
371
|
+
// For tla_cfg: deduplicate by (name, config/model)
|
|
372
|
+
// For others: deduplicate by (name, source)
|
|
373
|
+
const modelKey = inv.config || inv.source_file || inv.source;
|
|
374
|
+
const key = `${inv.name}::${modelKey}`;
|
|
375
|
+
|
|
376
|
+
if (map.has(key)) {
|
|
377
|
+
const existing = map.get(key);
|
|
378
|
+
if (!existing.check_references) {
|
|
379
|
+
existing.check_references = [existing.source_file || existing.source];
|
|
380
|
+
}
|
|
381
|
+
existing.check_references.push(inv.source_file || inv.source);
|
|
382
|
+
} else {
|
|
383
|
+
map.set(key, { ...inv });
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
return [...map.values()];
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
// ── Main ─────────────────────────────────────────────────────────────────────
|
|
391
|
+
|
|
392
|
+
function main() {
|
|
393
|
+
const cfgInvariants = parseCfgFiles();
|
|
394
|
+
const specInvariants = parseSpecInvariants();
|
|
395
|
+
const observedInvariants = mineObservedInvariants();
|
|
396
|
+
|
|
397
|
+
const allRaw = [...cfgInvariants, ...specInvariants, ...observedInvariants];
|
|
398
|
+
const deduped = deduplicateInvariants(allRaw);
|
|
399
|
+
|
|
400
|
+
const byType = { declared: 0, observed: 0 };
|
|
401
|
+
const bySource = { tla_cfg: 0, spec_invariants_md: 0, trace_mining: 0 };
|
|
402
|
+
|
|
403
|
+
for (const inv of deduped) {
|
|
404
|
+
byType[inv.type] = (byType[inv.type] || 0) + 1;
|
|
405
|
+
bySource[inv.source] = (bySource[inv.source] || 0) + 1;
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
const catalog = {
|
|
409
|
+
schema_version: '1',
|
|
410
|
+
generated: new Date().toISOString(),
|
|
411
|
+
invariants: deduped,
|
|
412
|
+
summary: {
|
|
413
|
+
total_raw: allRaw.length,
|
|
414
|
+
total_deduplicated: deduped.length,
|
|
415
|
+
by_type: byType,
|
|
416
|
+
by_source: bySource,
|
|
417
|
+
},
|
|
418
|
+
};
|
|
419
|
+
|
|
420
|
+
// Ensure output directory exists
|
|
421
|
+
if (!fs.existsSync(OUT_DIR)) fs.mkdirSync(OUT_DIR, { recursive: true });
|
|
422
|
+
fs.writeFileSync(OUT_FILE, JSON.stringify(catalog, null, 2) + '\n');
|
|
423
|
+
|
|
424
|
+
if (JSON_FLAG) {
|
|
425
|
+
process.stdout.write(JSON.stringify(catalog, null, 2) + '\n');
|
|
426
|
+
} else {
|
|
427
|
+
console.log(`Invariant Catalog written to ${path.relative(ROOT, OUT_FILE)}`);
|
|
428
|
+
console.log(` Raw: ${allRaw.length} Deduplicated: ${deduped.length}`);
|
|
429
|
+
console.log(` By type: declared=${byType.declared} observed=${byType.observed}`);
|
|
430
|
+
console.log(` By source: tla_cfg=${bySource.tla_cfg} spec_invariants_md=${bySource.spec_invariants_md} trace_mining=${bySource.trace_mining}`);
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
// Export for testing
|
|
435
|
+
module.exports = { parseCfgFiles, parseSpecInvariants, mineObservedInvariants, deduplicateInvariants };
|
|
436
|
+
|
|
437
|
+
if (require.main === module) main();
|
package/bin/issue-classifier.cjs
CHANGED
|
@@ -77,7 +77,7 @@ if (circuitBreaker.active === true) {
|
|
|
77
77
|
id: 'circuit-breaker-active',
|
|
78
78
|
priority: 90,
|
|
79
79
|
description: 'Circuit breaker is currently active — oscillation was detected and execution is paused.',
|
|
80
|
-
action: 'Run /
|
|
80
|
+
action: 'Run /nf:debug to diagnose the oscillation root cause, then run `npx nforma --reset-breaker`.',
|
|
81
81
|
surfaced: false,
|
|
82
82
|
detectedAt: now,
|
|
83
83
|
});
|
|
@@ -131,7 +131,7 @@ if (!circuitBreaker.active && (circuitBreaker.triggerCount || 0) > 3) {
|
|
|
131
131
|
id: 'circuit-breaker-repeated-triggers',
|
|
132
132
|
priority: 50,
|
|
133
133
|
description: `Circuit breaker has triggered ${circuitBreaker.triggerCount} times — recurring oscillation pattern detected.`,
|
|
134
|
-
action: 'Run /
|
|
134
|
+
action: 'Run /nf:discuss-phase to review recent commit patterns; consider adding explicit done-criteria to plans.',
|
|
135
135
|
surfaced: false,
|
|
136
136
|
detectedAt: now,
|
|
137
137
|
});
|
|
@@ -8,11 +8,11 @@ const path = require('path');
|
|
|
8
8
|
* Load baseline requirements filtered by project profile.
|
|
9
9
|
*
|
|
10
10
|
* @param {string} profile - One of: web, mobile, desktop, api, cli, library
|
|
11
|
-
* @param {string} [basePath] - Path to baseline-requirements directory, defaults to
|
|
11
|
+
* @param {string} [basePath] - Path to baseline-requirements directory, defaults to core/defaults/baseline-requirements
|
|
12
12
|
* @returns {Object} { profile, label, description, categories: [...], total }
|
|
13
13
|
*/
|
|
14
14
|
function loadBaselineRequirements(profile, basePath) {
|
|
15
|
-
const defaultBasePath = path.resolve(__dirname, '../
|
|
15
|
+
const defaultBasePath = path.resolve(__dirname, '../core/defaults/baseline-requirements');
|
|
16
16
|
const basePathToUse = basePath || defaultBasePath;
|
|
17
17
|
|
|
18
18
|
// Read index.json
|
|
@@ -105,7 +105,7 @@ function loadBaselineRequirements(profile, basePath) {
|
|
|
105
105
|
* @returns {Object} { profile, label, intent, categories, packs_applied, total }
|
|
106
106
|
*/
|
|
107
107
|
function loadBaselineRequirementsFromIntent(intent, basePath) {
|
|
108
|
-
const defaultBasePath = path.resolve(__dirname, '../
|
|
108
|
+
const defaultBasePath = path.resolve(__dirname, '../core/defaults/baseline-requirements');
|
|
109
109
|
const basePathToUse = basePath || defaultBasePath;
|
|
110
110
|
|
|
111
111
|
// Validate base_profile
|
|
@@ -231,7 +231,7 @@ if (require.main === module) {
|
|
|
231
231
|
const args = process.argv.slice(2);
|
|
232
232
|
|
|
233
233
|
if (args.includes('--list-profiles')) {
|
|
234
|
-
const indexPath = path.resolve(__dirname, '../
|
|
234
|
+
const indexPath = path.resolve(__dirname, '../core/defaults/baseline-requirements/index.json');
|
|
235
235
|
const indexContent = fs.readFileSync(indexPath, 'utf8');
|
|
236
236
|
const index = JSON.parse(indexContent);
|
|
237
237
|
const profiles = Object.entries(index.profiles).map(([key, val]) => ({
|