@nforma.ai/nforma 0.2.1
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/LICENSE +22 -0
- package/README.md +1024 -0
- package/agents/qgsd-codebase-mapper.md +764 -0
- package/agents/qgsd-debugger.md +1201 -0
- package/agents/qgsd-executor.md +472 -0
- package/agents/qgsd-integration-checker.md +443 -0
- package/agents/qgsd-phase-researcher.md +502 -0
- package/agents/qgsd-plan-checker.md +643 -0
- package/agents/qgsd-planner.md +1182 -0
- package/agents/qgsd-project-researcher.md +621 -0
- package/agents/qgsd-quorum-orchestrator.md +628 -0
- package/agents/qgsd-quorum-slot-worker.md +41 -0
- package/agents/qgsd-quorum-synthesizer.md +133 -0
- package/agents/qgsd-quorum-test-worker.md +37 -0
- package/agents/qgsd-quorum-worker.md +161 -0
- package/agents/qgsd-research-synthesizer.md +239 -0
- package/agents/qgsd-roadmapper.md +660 -0
- package/agents/qgsd-verifier.md +628 -0
- package/bin/accept-debug-invariant.cjs +165 -0
- package/bin/account-manager.cjs +719 -0
- package/bin/aggregate-requirements.cjs +466 -0
- package/bin/analyze-assumptions.cjs +757 -0
- package/bin/analyze-state-space.cjs +921 -0
- package/bin/attribute-trace-divergence.cjs +150 -0
- package/bin/auth-drivers/gh-cli.cjs +93 -0
- package/bin/auth-drivers/index.cjs +46 -0
- package/bin/auth-drivers/pool.cjs +67 -0
- package/bin/auth-drivers/simple.cjs +95 -0
- package/bin/autoClosePtoF.cjs +110 -0
- package/bin/blessed-terminal.cjs +350 -0
- package/bin/build-phase-index.cjs +472 -0
- package/bin/call-quorum-slot.cjs +541 -0
- package/bin/ccr-secure-config.cjs +99 -0
- package/bin/ccr-secure-start.cjs +83 -0
- package/bin/check-bundled-sdks.cjs +177 -0
- package/bin/check-coverage-guard.cjs +112 -0
- package/bin/check-liveness-fairness.cjs +95 -0
- package/bin/check-mcp-health.cjs +123 -0
- package/bin/check-provider-health.cjs +395 -0
- package/bin/check-results-exit.cjs +24 -0
- package/bin/check-spec-sync.cjs +360 -0
- package/bin/check-trace-redaction.cjs +271 -0
- package/bin/check-trace-schema-drift.cjs +99 -0
- package/bin/compareDrift.cjs +21 -0
- package/bin/conformance-schema.cjs +12 -0
- package/bin/count-scenarios.cjs +420 -0
- package/bin/debt-dedup.cjs +144 -0
- package/bin/debt-ledger.cjs +61 -0
- package/bin/debt-retention.cjs +76 -0
- package/bin/debt-state-machine.cjs +80 -0
- package/bin/detect-coverage-gaps.cjs +204 -0
- package/bin/detect-project-intent.cjs +362 -0
- package/bin/export-prism-constants.cjs +164 -0
- package/bin/extract-annotations.cjs +633 -0
- package/bin/extractFormalExpected.cjs +104 -0
- package/bin/fingerprint-drift.cjs +24 -0
- package/bin/fingerprint-issue.cjs +46 -0
- package/bin/formal-core.cjs +519 -0
- package/bin/formal-ref-linker.cjs +141 -0
- package/bin/formal-test-sync.cjs +788 -0
- package/bin/generate-formal-specs.cjs +588 -0
- package/bin/generate-petri-net.cjs +397 -0
- package/bin/generate-phase-spec.cjs +249 -0
- package/bin/generate-proposed-changes.cjs +194 -0
- package/bin/generate-tla-cfg.cjs +122 -0
- package/bin/generate-traceability-matrix.cjs +701 -0
- package/bin/generate-triage-bundle.cjs +300 -0
- package/bin/gh-account-rotate.cjs +34 -0
- package/bin/initialize-model-registry.cjs +105 -0
- package/bin/install-formal-tools.cjs +382 -0
- package/bin/install.js +2424 -0
- package/bin/isNumericThreshold.cjs +34 -0
- package/bin/issue-classifier.cjs +151 -0
- package/bin/levenshtein.cjs +74 -0
- package/bin/lint-formal-models.cjs +580 -0
- package/bin/load-baseline-requirements.cjs +275 -0
- package/bin/manage-agents-core.cjs +815 -0
- package/bin/migrate-formal-dir.cjs +172 -0
- package/bin/migrate-planning.cjs +206 -0
- package/bin/migrate-to-slots.cjs +255 -0
- package/bin/nForma.cjs +2726 -0
- package/bin/observe-config.cjs +353 -0
- package/bin/observe-debt-writer.cjs +140 -0
- package/bin/observe-handler-grafana.cjs +128 -0
- package/bin/observe-handler-internal.cjs +301 -0
- package/bin/observe-handler-logstash.cjs +153 -0
- package/bin/observe-handler-prometheus.cjs +185 -0
- package/bin/observe-handlers.cjs +436 -0
- package/bin/observe-registry.cjs +131 -0
- package/bin/observe-render.cjs +168 -0
- package/bin/planning-paths.cjs +167 -0
- package/bin/polyrepo.cjs +560 -0
- package/bin/prism-priority.cjs +153 -0
- package/bin/probe-quorum-slots.cjs +167 -0
- package/bin/promote-model.cjs +225 -0
- package/bin/propose-debug-invariants.cjs +165 -0
- package/bin/providers.json +392 -0
- package/bin/pty-proxy.py +129 -0
- package/bin/qgsd-solve.cjs +2477 -0
- package/bin/quorum-consensus-gate.cjs +238 -0
- package/bin/quorum-formal-context.cjs +183 -0
- package/bin/quorum-slot-dispatch.cjs +934 -0
- package/bin/read-policy.cjs +60 -0
- package/bin/requirement-map.cjs +63 -0
- package/bin/requirements-core.cjs +247 -0
- package/bin/resolve-cli.cjs +101 -0
- package/bin/review-mcp-logs.cjs +294 -0
- package/bin/run-account-manager-tlc.cjs +188 -0
- package/bin/run-account-pool-alloy.cjs +158 -0
- package/bin/run-alloy.cjs +153 -0
- package/bin/run-audit-alloy.cjs +187 -0
- package/bin/run-breaker-tlc.cjs +181 -0
- package/bin/run-formal-check.cjs +395 -0
- package/bin/run-formal-verify.cjs +701 -0
- package/bin/run-installer-alloy.cjs +188 -0
- package/bin/run-oauth-rotation-prism.cjs +132 -0
- package/bin/run-oscillation-tlc.cjs +202 -0
- package/bin/run-phase-tlc.cjs +228 -0
- package/bin/run-prism.cjs +446 -0
- package/bin/run-protocol-tlc.cjs +201 -0
- package/bin/run-quorum-composition-alloy.cjs +155 -0
- package/bin/run-sensitivity-sweep.cjs +231 -0
- package/bin/run-stop-hook-tlc.cjs +188 -0
- package/bin/run-tlc.cjs +467 -0
- package/bin/run-transcript-alloy.cjs +173 -0
- package/bin/run-uppaal.cjs +264 -0
- package/bin/secrets.cjs +134 -0
- package/bin/sensitivity-report.cjs +219 -0
- package/bin/sensitivity-sweep-feedback.cjs +194 -0
- package/bin/set-secret.cjs +29 -0
- package/bin/setup-telemetry-cron.sh +36 -0
- package/bin/sweepPtoF.cjs +63 -0
- package/bin/sync-baseline-requirements.cjs +290 -0
- package/bin/task-envelope.cjs +360 -0
- package/bin/telemetry-collector.cjs +229 -0
- package/bin/unified-mcp-server.mjs +735 -0
- package/bin/update-agents.cjs +369 -0
- package/bin/update-scoreboard.cjs +1134 -0
- package/bin/validate-debt-entry.cjs +207 -0
- package/bin/validate-invariant.cjs +419 -0
- package/bin/validate-memory.cjs +389 -0
- package/bin/validate-requirements-haiku.cjs +435 -0
- package/bin/validate-traces.cjs +438 -0
- package/bin/verify-formal-results.cjs +124 -0
- package/bin/verify-quorum-health.cjs +273 -0
- package/bin/write-check-result.cjs +106 -0
- package/bin/xstate-to-tla.cjs +483 -0
- package/bin/xstate-trace-walker.cjs +205 -0
- package/commands/qgsd/add-phase.md +43 -0
- package/commands/qgsd/add-requirement.md +24 -0
- package/commands/qgsd/add-todo.md +47 -0
- package/commands/qgsd/audit-milestone.md +37 -0
- package/commands/qgsd/check-todos.md +45 -0
- package/commands/qgsd/cleanup.md +18 -0
- package/commands/qgsd/close-formal-gaps.md +33 -0
- package/commands/qgsd/complete-milestone.md +136 -0
- package/commands/qgsd/debug.md +166 -0
- package/commands/qgsd/discuss-phase.md +83 -0
- package/commands/qgsd/execute-phase.md +117 -0
- package/commands/qgsd/fix-tests.md +27 -0
- package/commands/qgsd/formal-test-sync.md +32 -0
- package/commands/qgsd/health.md +22 -0
- package/commands/qgsd/help.md +22 -0
- package/commands/qgsd/insert-phase.md +32 -0
- package/commands/qgsd/join-discord.md +18 -0
- package/commands/qgsd/list-phase-assumptions.md +46 -0
- package/commands/qgsd/map-codebase.md +71 -0
- package/commands/qgsd/map-requirements.md +20 -0
- package/commands/qgsd/mcp-restart.md +176 -0
- package/commands/qgsd/mcp-set-model.md +134 -0
- package/commands/qgsd/mcp-setup.md +1371 -0
- package/commands/qgsd/mcp-status.md +274 -0
- package/commands/qgsd/mcp-update.md +238 -0
- package/commands/qgsd/new-milestone.md +44 -0
- package/commands/qgsd/new-project.md +42 -0
- package/commands/qgsd/observe.md +260 -0
- package/commands/qgsd/pause-work.md +38 -0
- package/commands/qgsd/plan-milestone-gaps.md +34 -0
- package/commands/qgsd/plan-phase.md +44 -0
- package/commands/qgsd/polyrepo.md +50 -0
- package/commands/qgsd/progress.md +24 -0
- package/commands/qgsd/queue.md +54 -0
- package/commands/qgsd/quick.md +133 -0
- package/commands/qgsd/quorum-test.md +275 -0
- package/commands/qgsd/quorum.md +707 -0
- package/commands/qgsd/reapply-patches.md +110 -0
- package/commands/qgsd/remove-phase.md +31 -0
- package/commands/qgsd/research-phase.md +189 -0
- package/commands/qgsd/resume-work.md +40 -0
- package/commands/qgsd/set-profile.md +34 -0
- package/commands/qgsd/settings.md +39 -0
- package/commands/qgsd/solve.md +565 -0
- package/commands/qgsd/sync-baselines.md +119 -0
- package/commands/qgsd/triage.md +233 -0
- package/commands/qgsd/update.md +37 -0
- package/commands/qgsd/verify-work.md +38 -0
- package/hooks/dist/config-loader.js +297 -0
- package/hooks/dist/conformance-schema.cjs +12 -0
- package/hooks/dist/gsd-context-monitor.js +64 -0
- package/hooks/dist/qgsd-check-update.js +62 -0
- package/hooks/dist/qgsd-circuit-breaker.js +682 -0
- package/hooks/dist/qgsd-precompact.js +156 -0
- package/hooks/dist/qgsd-prompt.js +653 -0
- package/hooks/dist/qgsd-session-start.js +122 -0
- package/hooks/dist/qgsd-slot-correlator.js +58 -0
- package/hooks/dist/qgsd-spec-regen.js +86 -0
- package/hooks/dist/qgsd-statusline.js +91 -0
- package/hooks/dist/qgsd-stop.js +553 -0
- package/hooks/dist/qgsd-token-collector.js +133 -0
- package/hooks/dist/unified-mcp-server.mjs +669 -0
- package/package.json +95 -0
- package/scripts/build-hooks.js +46 -0
- package/scripts/postinstall.js +48 -0
- package/scripts/secret-audit.sh +45 -0
- package/templates/qgsd.json +49 -0
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// bin/ccr-secure-start.cjs
|
|
3
|
+
// Wrapper script for claude-code-router that:
|
|
4
|
+
// 1. Populates ~/.claude-code-router/config.json from keytar before starting CCR
|
|
5
|
+
// 2. Spawns CCR with the provided args
|
|
6
|
+
// 3. Wipes all provider api_key fields from config.json on exit or signal
|
|
7
|
+
//
|
|
8
|
+
// Usage: node bin/ccr-secure-start.cjs <ccr-binary> [args...]
|
|
9
|
+
// Example: node bin/ccr-secure-start.cjs ccr start
|
|
10
|
+
|
|
11
|
+
'use strict';
|
|
12
|
+
|
|
13
|
+
const fs = require('fs');
|
|
14
|
+
const path = require('path');
|
|
15
|
+
const os = require('os');
|
|
16
|
+
const { execFileSync, spawn } = require('child_process');
|
|
17
|
+
|
|
18
|
+
const CONFIG_PATH = path.join(os.homedir(), '.claude-code-router', 'config.json');
|
|
19
|
+
const SECURE_CONFIG = path.join(__dirname, 'ccr-secure-config.cjs');
|
|
20
|
+
|
|
21
|
+
// Synchronously wipe all api_key fields in config.json to empty strings.
|
|
22
|
+
// Called on exit and on signal to ensure keys are not left on disk.
|
|
23
|
+
function wipeKeys() {
|
|
24
|
+
try {
|
|
25
|
+
const raw = fs.readFileSync(CONFIG_PATH, 'utf8');
|
|
26
|
+
const config = JSON.parse(raw);
|
|
27
|
+
if (Array.isArray(config.providers)) {
|
|
28
|
+
for (const provider of config.providers) {
|
|
29
|
+
provider.api_key = '';
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
fs.writeFileSync(CONFIG_PATH, JSON.stringify(config, null, 2), { mode: 0o600 });
|
|
33
|
+
} catch (_) {
|
|
34
|
+
// Fail silently — config may not exist or may already be wiped
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
async function main() {
|
|
39
|
+
const ccrBin = process.argv[2];
|
|
40
|
+
const ccrArgs = process.argv.slice(3);
|
|
41
|
+
|
|
42
|
+
if (!ccrBin) {
|
|
43
|
+
process.stderr.write('Usage: node ccr-secure-start.cjs <ccr-binary> [args...]\n');
|
|
44
|
+
process.stderr.write('Example: node ccr-secure-start.cjs ccr start\n');
|
|
45
|
+
process.exit(1);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// Step 1: Populate config.json from keytar
|
|
49
|
+
try {
|
|
50
|
+
execFileSync(process.execPath, [SECURE_CONFIG], { stdio: 'inherit' });
|
|
51
|
+
} catch (e) {
|
|
52
|
+
process.stderr.write('[ccr-secure-start] Failed to populate CCR config: ' + e.message + '\n');
|
|
53
|
+
process.exit(1);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Step 2: Spawn CCR
|
|
57
|
+
const child = spawn(ccrBin, ccrArgs, { stdio: 'inherit' });
|
|
58
|
+
|
|
59
|
+
// Step 3: Wipe keys on CCR exit
|
|
60
|
+
child.on('exit', (code) => {
|
|
61
|
+
wipeKeys();
|
|
62
|
+
process.exit(code ?? 0);
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
// Wipe keys on SIGTERM (e.g. systemd stop, kill)
|
|
66
|
+
process.on('SIGTERM', () => {
|
|
67
|
+
child.kill('SIGTERM');
|
|
68
|
+
wipeKeys();
|
|
69
|
+
process.exit(0);
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
// Wipe keys on SIGINT (Ctrl-C)
|
|
73
|
+
process.on('SIGINT', () => {
|
|
74
|
+
child.kill('SIGINT');
|
|
75
|
+
wipeKeys();
|
|
76
|
+
process.exit(0);
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
main().catch((e) => {
|
|
81
|
+
process.stderr.write('[ccr-secure-start] Unexpected error: ' + e.message + '\n');
|
|
82
|
+
process.exit(1);
|
|
83
|
+
});
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
const fs = require('fs');
|
|
5
|
+
const path = require('path');
|
|
6
|
+
|
|
7
|
+
// ---------------------------------------------------------------------------
|
|
8
|
+
// Forbidden SDK list — LLM SDKs that QGSD must not bundle (ARCH-10)
|
|
9
|
+
// ---------------------------------------------------------------------------
|
|
10
|
+
|
|
11
|
+
const FORBIDDEN_SDKS = [
|
|
12
|
+
'@anthropic-ai/sdk',
|
|
13
|
+
'anthropic',
|
|
14
|
+
'openai',
|
|
15
|
+
'@google/generative-ai',
|
|
16
|
+
'google-generativeai',
|
|
17
|
+
'cohere-ai',
|
|
18
|
+
'@azure/openai',
|
|
19
|
+
];
|
|
20
|
+
|
|
21
|
+
// ---------------------------------------------------------------------------
|
|
22
|
+
// Import pattern builder
|
|
23
|
+
// ---------------------------------------------------------------------------
|
|
24
|
+
|
|
25
|
+
function buildImportPatterns() {
|
|
26
|
+
return FORBIDDEN_SDKS.map(sdk => {
|
|
27
|
+
// Escape special regex chars in SDK name (@ and /)
|
|
28
|
+
const escaped = sdk.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
29
|
+
return {
|
|
30
|
+
sdk,
|
|
31
|
+
patterns: [
|
|
32
|
+
// CommonJS require
|
|
33
|
+
new RegExp(`require\\s*\\(\\s*['"]${escaped}['"]\\s*\\)`),
|
|
34
|
+
// require.resolve
|
|
35
|
+
new RegExp(`require\\.resolve\\s*\\(\\s*['"]${escaped}['"]\\s*\\)`),
|
|
36
|
+
// ESM import
|
|
37
|
+
new RegExp(`import\\s+.*\\s+from\\s+['"]${escaped}['"]`),
|
|
38
|
+
],
|
|
39
|
+
};
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// ---------------------------------------------------------------------------
|
|
44
|
+
// Scope filtering
|
|
45
|
+
// ---------------------------------------------------------------------------
|
|
46
|
+
|
|
47
|
+
function isInScope(filePath) {
|
|
48
|
+
const normalized = filePath.replace(/\\/g, '/');
|
|
49
|
+
|
|
50
|
+
// Must be under bin/ or hooks/
|
|
51
|
+
const inScope = normalized.startsWith('bin/') || normalized.startsWith('hooks/') ||
|
|
52
|
+
normalized.includes('/bin/') || normalized.includes('/hooks/');
|
|
53
|
+
if (!inScope) return false;
|
|
54
|
+
|
|
55
|
+
// Must have .js, .cjs, or .mjs extension
|
|
56
|
+
if (!/\.(js|cjs|mjs)$/.test(normalized)) return false;
|
|
57
|
+
|
|
58
|
+
// Exclude test files
|
|
59
|
+
if (/\.test\.(js|cjs|mjs)$/.test(normalized)) return false;
|
|
60
|
+
|
|
61
|
+
// Exclude node_modules, docs, examples, .planning/formal
|
|
62
|
+
if (/node_modules|\/docs\/|\/examples\/|\.planning\/formal/.test(normalized)) return false;
|
|
63
|
+
|
|
64
|
+
return true;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// ---------------------------------------------------------------------------
|
|
68
|
+
// Line-level scanning
|
|
69
|
+
// ---------------------------------------------------------------------------
|
|
70
|
+
|
|
71
|
+
function scanFile(filePath) {
|
|
72
|
+
let content;
|
|
73
|
+
try {
|
|
74
|
+
content = fs.readFileSync(filePath, 'utf8');
|
|
75
|
+
} catch (_) {
|
|
76
|
+
// Fail-open on read errors
|
|
77
|
+
return [];
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const lines = content.split('\n');
|
|
81
|
+
const importPatterns = buildImportPatterns();
|
|
82
|
+
const violations = [];
|
|
83
|
+
|
|
84
|
+
for (let i = 0; i < lines.length; i++) {
|
|
85
|
+
const line = lines[i];
|
|
86
|
+
const trimmed = line.trimStart();
|
|
87
|
+
|
|
88
|
+
// Skip single-line comments
|
|
89
|
+
if (trimmed.startsWith('//')) continue;
|
|
90
|
+
|
|
91
|
+
for (const { sdk, patterns } of importPatterns) {
|
|
92
|
+
for (const pattern of patterns) {
|
|
93
|
+
if (pattern.test(line)) {
|
|
94
|
+
violations.push({
|
|
95
|
+
file: filePath,
|
|
96
|
+
line: i + 1,
|
|
97
|
+
content: line.trim(),
|
|
98
|
+
sdk,
|
|
99
|
+
violation: 'SDK_IMPORT',
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
return violations;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// ---------------------------------------------------------------------------
|
|
110
|
+
// File walker
|
|
111
|
+
// ---------------------------------------------------------------------------
|
|
112
|
+
|
|
113
|
+
function getAllSourceFiles(dir) {
|
|
114
|
+
const results = [];
|
|
115
|
+
const skipDirs = new Set(['node_modules', '.git', 'dist', '.formal']);
|
|
116
|
+
|
|
117
|
+
function walk(currentDir) {
|
|
118
|
+
let entries;
|
|
119
|
+
try {
|
|
120
|
+
entries = fs.readdirSync(currentDir, { withFileTypes: true });
|
|
121
|
+
} catch (_) {
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
for (const entry of entries) {
|
|
126
|
+
if (entry.isDirectory()) {
|
|
127
|
+
if (!skipDirs.has(entry.name)) {
|
|
128
|
+
walk(path.join(currentDir, entry.name));
|
|
129
|
+
}
|
|
130
|
+
} else if (entry.isFile() && /\.(js|cjs|mjs)$/.test(entry.name)) {
|
|
131
|
+
results.push(path.join(currentDir, entry.name));
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
walk(dir);
|
|
137
|
+
return results;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// ---------------------------------------------------------------------------
|
|
141
|
+
// Main
|
|
142
|
+
// ---------------------------------------------------------------------------
|
|
143
|
+
|
|
144
|
+
function main() {
|
|
145
|
+
console.log('[LINT] Architecture: SDK Bundling Check');
|
|
146
|
+
|
|
147
|
+
const allFiles = getAllSourceFiles('.');
|
|
148
|
+
const scopedFiles = allFiles
|
|
149
|
+
.map(f => f.startsWith('./') ? f.slice(2) : f)
|
|
150
|
+
.filter(isInScope);
|
|
151
|
+
|
|
152
|
+
console.log(`Scanned ${scopedFiles.length} files`);
|
|
153
|
+
|
|
154
|
+
const allViolations = [];
|
|
155
|
+
for (const file of scopedFiles) {
|
|
156
|
+
const violations = scanFile(file);
|
|
157
|
+
allViolations.push(...violations);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
console.log(`Found ${allViolations.length} violations`);
|
|
161
|
+
|
|
162
|
+
for (const v of allViolations) {
|
|
163
|
+
console.log(` ERROR: ${v.file}:${v.line}: ${v.content}`);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
process.exit(allViolations.length > 0 ? 1 : 0);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// ---------------------------------------------------------------------------
|
|
170
|
+
// Exports + CLI entry
|
|
171
|
+
// ---------------------------------------------------------------------------
|
|
172
|
+
|
|
173
|
+
module.exports = { FORBIDDEN_SDKS, buildImportPatterns, isInScope, scanFile, getAllSourceFiles };
|
|
174
|
+
|
|
175
|
+
if (require.main === module) {
|
|
176
|
+
main();
|
|
177
|
+
}
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
// bin/check-coverage-guard.cjs
|
|
4
|
+
// CI coverage guard — compares current traceability matrix coverage against
|
|
5
|
+
// a saved baseline and warns when the drop exceeds a configurable threshold.
|
|
6
|
+
//
|
|
7
|
+
// Usage:
|
|
8
|
+
// node bin/check-coverage-guard.cjs # compare current vs baseline
|
|
9
|
+
// node bin/check-coverage-guard.cjs --save-baseline # save current matrix as baseline
|
|
10
|
+
// node bin/check-coverage-guard.cjs --threshold 10 # override threshold (default: 15)
|
|
11
|
+
// node bin/check-coverage-guard.cjs --quiet # suppress output (exit code only)
|
|
12
|
+
//
|
|
13
|
+
// Requirements: TRACE-05
|
|
14
|
+
|
|
15
|
+
const fs = require('fs');
|
|
16
|
+
const path = require('path');
|
|
17
|
+
|
|
18
|
+
const TAG = '[check-coverage-guard]';
|
|
19
|
+
let ROOT = process.cwd();
|
|
20
|
+
|
|
21
|
+
// Parse --project-root (overrides CWD-based ROOT for cross-repo usage)
|
|
22
|
+
for (const arg of process.argv.slice(2)) {
|
|
23
|
+
if (arg.startsWith('--project-root=')) {
|
|
24
|
+
ROOT = path.resolve(arg.slice('--project-root='.length));
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const MATRIX_PATH = process.env.COVERAGE_GUARD_MATRIX_PATH || path.join(ROOT, '.planning', 'formal', 'traceability-matrix.json');
|
|
29
|
+
const BASELINE_PATH = process.env.COVERAGE_GUARD_BASELINE_PATH || path.join(ROOT, '.planning', 'formal', 'traceability-matrix.baseline.json');
|
|
30
|
+
|
|
31
|
+
// ── CLI flags ───────────────────────────────────────────────────────────────
|
|
32
|
+
|
|
33
|
+
const args = process.argv.slice(2);
|
|
34
|
+
const saveBaseline = args.includes('--save-baseline');
|
|
35
|
+
const quietMode = args.includes('--quiet');
|
|
36
|
+
const thresholdIdx = args.indexOf('--threshold');
|
|
37
|
+
const threshold = thresholdIdx !== -1 ? parseFloat(args[thresholdIdx + 1]) : 15;
|
|
38
|
+
|
|
39
|
+
// ── Helpers ─────────────────────────────────────────────────────────────────
|
|
40
|
+
|
|
41
|
+
function log(msg) {
|
|
42
|
+
if (!quietMode) {
|
|
43
|
+
process.stdout.write(TAG + ' ' + msg + '\n');
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function loadJSON(filePath) {
|
|
48
|
+
try {
|
|
49
|
+
return JSON.parse(fs.readFileSync(filePath, 'utf8'));
|
|
50
|
+
} catch (err) {
|
|
51
|
+
return null;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// ── Save Baseline Mode ──────────────────────────────────────────────────────
|
|
56
|
+
|
|
57
|
+
if (saveBaseline) {
|
|
58
|
+
const current = loadJSON(MATRIX_PATH);
|
|
59
|
+
if (!current) {
|
|
60
|
+
process.stderr.write(TAG + ' ERROR: Cannot read ' + MATRIX_PATH + '\n');
|
|
61
|
+
process.exit(1);
|
|
62
|
+
}
|
|
63
|
+
fs.writeFileSync(BASELINE_PATH, JSON.stringify(current, null, 2) + '\n', 'utf8');
|
|
64
|
+
const cs = current.coverage_summary || {};
|
|
65
|
+
log('Baseline saved: ' + (cs.coverage_percentage || 0) + '% coverage (' + (cs.covered_count || 0) + '/' + (cs.total_requirements || 0) + ' requirements)');
|
|
66
|
+
process.exit(0);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// ── Comparison Mode ─────────────────────────────────────────────────────────
|
|
70
|
+
|
|
71
|
+
const current = loadJSON(MATRIX_PATH);
|
|
72
|
+
if (!current) {
|
|
73
|
+
process.stderr.write(TAG + ' ERROR: Cannot read current matrix at ' + MATRIX_PATH + '\n');
|
|
74
|
+
process.exit(1);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
let baseline = loadJSON(BASELINE_PATH);
|
|
78
|
+
|
|
79
|
+
if (!baseline) {
|
|
80
|
+
// Auto-create baseline from current matrix
|
|
81
|
+
fs.writeFileSync(BASELINE_PATH, JSON.stringify(current, null, 2) + '\n', 'utf8');
|
|
82
|
+
const cs = current.coverage_summary || {};
|
|
83
|
+
log('No baseline found — creating from current matrix (' + (cs.coverage_percentage || 0) + '%)');
|
|
84
|
+
log('PASS: No regression (baseline just created)');
|
|
85
|
+
process.exit(0);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Extract coverage percentages
|
|
89
|
+
const currentCs = current.coverage_summary || {};
|
|
90
|
+
const baselineCs = baseline.coverage_summary || {};
|
|
91
|
+
const currentPct = currentCs.coverage_percentage || 0;
|
|
92
|
+
const baselinePct = baselineCs.coverage_percentage || 0;
|
|
93
|
+
const drop = Math.round((baselinePct - currentPct) * 10) / 10;
|
|
94
|
+
|
|
95
|
+
if (drop > threshold) {
|
|
96
|
+
// Coverage regression exceeds threshold
|
|
97
|
+
log('WARN: Coverage regression detected');
|
|
98
|
+
log(' Baseline: ' + baselinePct + '% (' + (baselineCs.covered_count || 0) + '/' + (baselineCs.total_requirements || 0) + ')');
|
|
99
|
+
log(' Current: ' + currentPct + '% (' + (currentCs.covered_count || 0) + '/' + (currentCs.total_requirements || 0) + ')');
|
|
100
|
+
log(' Drop: ' + drop + ' percentage points (threshold: ' + threshold + 'pp)');
|
|
101
|
+
log(' To update baseline: node bin/check-coverage-guard.cjs --save-baseline');
|
|
102
|
+
process.exit(1);
|
|
103
|
+
} else if (drop > 0) {
|
|
104
|
+
// Minor drop within threshold
|
|
105
|
+
log('PASS: Minor coverage change (' + drop + 'pp drop, within ' + threshold + 'pp threshold)');
|
|
106
|
+
log(' Baseline: ' + baselinePct + '% → Current: ' + currentPct + '%');
|
|
107
|
+
process.exit(0);
|
|
108
|
+
} else {
|
|
109
|
+
// No regression or improved
|
|
110
|
+
log('PASS: No regression (baseline: ' + baselinePct + '% → current: ' + currentPct + '%)');
|
|
111
|
+
process.exit(0);
|
|
112
|
+
}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
// bin/check-liveness-fairness.cjs
|
|
4
|
+
// CI step: scan all TLA+ MC*.cfg files for liveness properties lacking fairness declarations.
|
|
5
|
+
// Emits result='pass' if all fairness declarations found; result='inconclusive' if any missing.
|
|
6
|
+
// Always exits 0 (inconclusive is not a build failure).
|
|
7
|
+
// Requirements: LIVE-01, LIVE-02
|
|
8
|
+
|
|
9
|
+
const fs = require('fs');
|
|
10
|
+
const path = require('path');
|
|
11
|
+
const { detectLivenessProperties } = require('./run-tlc.cjs');
|
|
12
|
+
const { writeCheckResult } = require('./write-check-result.cjs');
|
|
13
|
+
const { getRequirementIds } = require('./requirement-map.cjs');
|
|
14
|
+
|
|
15
|
+
const TAG = '[check-liveness-fairness]';
|
|
16
|
+
|
|
17
|
+
// Allow env var overrides for testing (injected by test scaffold)
|
|
18
|
+
const FORMAL_TLA_DIR = process.env.FORMAL_TLA_DIR || path.join(__dirname, '..', '.planning', 'formal', 'tla');
|
|
19
|
+
const FORMAL_SPEC_DIR = process.env.FORMAL_SPEC_DIR || path.join(__dirname, '..', '.planning', 'formal', 'spec');
|
|
20
|
+
|
|
21
|
+
const startMs = Date.now();
|
|
22
|
+
|
|
23
|
+
// 1. Discover all MC*.cfg files dynamically
|
|
24
|
+
let cfgFiles;
|
|
25
|
+
try {
|
|
26
|
+
cfgFiles = fs.readdirSync(FORMAL_TLA_DIR)
|
|
27
|
+
.filter(f => /^MC.*\.cfg$/.test(f))
|
|
28
|
+
.map(f => ({ name: f.replace(/\.cfg$/, ''), path: path.join(FORMAL_TLA_DIR, f) }));
|
|
29
|
+
} catch (e) {
|
|
30
|
+
process.stderr.write(TAG + ' Warning: cannot read .planning/formal/tla dir: ' + e.message + '\n');
|
|
31
|
+
cfgFiles = [];
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// 2. For each config, detect liveness properties without fairness declarations
|
|
35
|
+
const allMissing = []; // { config, properties: string[] }
|
|
36
|
+
let configsChecked = 0;
|
|
37
|
+
|
|
38
|
+
for (const { name, path: cfgPath } of cfgFiles) {
|
|
39
|
+
const missing = detectLivenessProperties(name, cfgPath, FORMAL_SPEC_DIR);
|
|
40
|
+
configsChecked++;
|
|
41
|
+
if (missing.length > 0) {
|
|
42
|
+
allMissing.push({ config: name, properties: missing });
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// 3. Aggregate results
|
|
47
|
+
const runtimeMs = Date.now() - startMs;
|
|
48
|
+
const hasMissing = allMissing.length > 0;
|
|
49
|
+
const result = hasMissing ? 'inconclusive' : 'pass';
|
|
50
|
+
|
|
51
|
+
const missingList = allMissing
|
|
52
|
+
.map(({ config, properties }) => config + ': ' + properties.join(', '))
|
|
53
|
+
.join('; ');
|
|
54
|
+
|
|
55
|
+
const summaryText = hasMissing
|
|
56
|
+
? 'inconclusive: fairness declarations missing — ' + missingList
|
|
57
|
+
: 'pass: all liveness properties have fairness declarations (' + configsChecked + ' configs checked)';
|
|
58
|
+
|
|
59
|
+
// 4. Write v2.1 check result
|
|
60
|
+
try {
|
|
61
|
+
writeCheckResult({
|
|
62
|
+
tool: 'check-liveness-fairness',
|
|
63
|
+
formalism: 'tla',
|
|
64
|
+
result,
|
|
65
|
+
check_id: 'ci:liveness-fairness-lint',
|
|
66
|
+
surface: 'ci',
|
|
67
|
+
property: 'Liveness fairness declarations — all TLA+ liveness properties documented with WF/SF rationale',
|
|
68
|
+
runtime_ms: runtimeMs,
|
|
69
|
+
summary: summaryText,
|
|
70
|
+
triage_tags: hasMissing ? ['needs-fairness'] : [],
|
|
71
|
+
requirement_ids: getRequirementIds('ci:liveness-fairness-lint'),
|
|
72
|
+
metadata: {
|
|
73
|
+
configs_checked: configsChecked,
|
|
74
|
+
configs_missing: allMissing.length,
|
|
75
|
+
missing_detail: allMissing,
|
|
76
|
+
},
|
|
77
|
+
});
|
|
78
|
+
} catch (e) {
|
|
79
|
+
process.stderr.write(TAG + ' Warning: failed to write check result: ' + e.message + '\n');
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// 5. Print summary
|
|
83
|
+
process.stdout.write(TAG + ' Result: ' + result + '\n');
|
|
84
|
+
if (hasMissing) {
|
|
85
|
+
process.stdout.write(TAG + ' Missing fairness declarations:\n');
|
|
86
|
+
for (const { config, properties } of allMissing) {
|
|
87
|
+
process.stdout.write(' ' + config + ': ' + properties.join(', ') + '\n');
|
|
88
|
+
}
|
|
89
|
+
process.stdout.write(TAG + ' Add ## <PropertyName> sections to the matching .planning/formal/spec/<spec>/invariants.md\n');
|
|
90
|
+
} else {
|
|
91
|
+
process.stdout.write(TAG + ' All ' + configsChecked + ' configs have fairness declarations.\n');
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// Always exit 0 — inconclusive is not a build failure
|
|
95
|
+
process.exit(0);
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* check-mcp-health.cjs
|
|
6
|
+
*
|
|
7
|
+
* Pre-flight health check for all claude-mcp-server instances.
|
|
8
|
+
* Reads ~/.claude.json to find configured MCP servers, then calls
|
|
9
|
+
* health_check on each one sequentially. Exits non-zero if any
|
|
10
|
+
* configured server is unhealthy.
|
|
11
|
+
*
|
|
12
|
+
* Usage:
|
|
13
|
+
* node bin/check-mcp-health.cjs [--timeout-ms N] [--json]
|
|
14
|
+
*
|
|
15
|
+
* Designed to be called at the start of /qgsd:quorum to skip
|
|
16
|
+
* unresponsive servers before making full inference calls.
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
const { execFileSync } = require('child_process');
|
|
20
|
+
const fs = require('fs');
|
|
21
|
+
const path = require('path');
|
|
22
|
+
const os = require('os');
|
|
23
|
+
|
|
24
|
+
const args = process.argv.slice(2);
|
|
25
|
+
const getArg = (f) => { const i = args.indexOf(f); return i !== -1 && args[i+1] ? args[i+1] : null; };
|
|
26
|
+
const hasFlag = (f) => args.includes(f);
|
|
27
|
+
const TIMEOUT_MS = parseInt(getArg('--timeout-ms') ?? '12000', 10);
|
|
28
|
+
const JSON_OUT = hasFlag('--json');
|
|
29
|
+
|
|
30
|
+
// ─── Load MCP server list from ~/.claude.json ─────────────────────────────────
|
|
31
|
+
let mcpServers = {};
|
|
32
|
+
try {
|
|
33
|
+
const raw = JSON.parse(fs.readFileSync(path.join(os.homedir(), '.claude.json'), 'utf8'));
|
|
34
|
+
mcpServers = raw.mcpServers ?? {};
|
|
35
|
+
} catch (e) {
|
|
36
|
+
console.error('Could not read ~/.claude.json:', e.message);
|
|
37
|
+
process.exit(1);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Filter to claude-mcp-server instances (those using our dist/index.js)
|
|
41
|
+
const targets = Object.entries(mcpServers)
|
|
42
|
+
.filter(([, v]) => v.args?.some(a => a.includes('claude-mcp-server')))
|
|
43
|
+
.map(([name, cfg]) => ({ name, env: cfg.env ?? {} }));
|
|
44
|
+
|
|
45
|
+
if (targets.length === 0) {
|
|
46
|
+
console.log('No claude-mcp-server instances found in ~/.claude.json');
|
|
47
|
+
process.exit(0);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// ─── Run health check via MCP JSON-RPC over stdio ────────────────────────────
|
|
51
|
+
// We can't easily spawn MCP servers here — instead, we proxy through the
|
|
52
|
+
// installed `claude` CLI by calling each server's health_check tool
|
|
53
|
+
// via a one-shot claude -p call with the right env overrides.
|
|
54
|
+
|
|
55
|
+
const results = [];
|
|
56
|
+
|
|
57
|
+
for (const { name, env } of targets) {
|
|
58
|
+
const start = Date.now();
|
|
59
|
+
let healthy = false;
|
|
60
|
+
let error = null;
|
|
61
|
+
let latencyMs = 0;
|
|
62
|
+
|
|
63
|
+
const prompt = JSON.stringify({
|
|
64
|
+
method: 'health_check_proxy',
|
|
65
|
+
server: name,
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
// We use a simpler proxy: just call claude with the env overrides directly.
|
|
69
|
+
// This tests the same path that the MCP tool would use.
|
|
70
|
+
try {
|
|
71
|
+
const envForCall = { ...process.env, ...env };
|
|
72
|
+
const result = execFileSync('claude', [
|
|
73
|
+
'-p', 'Reply with exactly one word: ok',
|
|
74
|
+
'--output-format', 'json',
|
|
75
|
+
'--max-turns', '1',
|
|
76
|
+
], {
|
|
77
|
+
env: envForCall,
|
|
78
|
+
timeout: TIMEOUT_MS,
|
|
79
|
+
encoding: 'utf8',
|
|
80
|
+
});
|
|
81
|
+
latencyMs = Date.now() - start;
|
|
82
|
+
const parsed = JSON.parse(result);
|
|
83
|
+
healthy = !!(parsed.result || parsed.type === 'result');
|
|
84
|
+
} catch (e) {
|
|
85
|
+
latencyMs = Date.now() - start;
|
|
86
|
+
error = e.code === 'ETIMEDOUT'
|
|
87
|
+
? `Timed out after ${TIMEOUT_MS}ms`
|
|
88
|
+
: (e.message ?? String(e)).split('\n')[0].slice(0, 120);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
results.push({ name, healthy, latencyMs, error });
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// ─── Output ───────────────────────────────────────────────────────────────────
|
|
95
|
+
if (JSON_OUT) {
|
|
96
|
+
console.log(JSON.stringify(results, null, 2));
|
|
97
|
+
} else {
|
|
98
|
+
const green = (s) => `\x1b[32m${s}\x1b[0m`;
|
|
99
|
+
const red = (s) => `\x1b[31m${s}\x1b[0m`;
|
|
100
|
+
const dim = (s) => `\x1b[2m${s}\x1b[0m`;
|
|
101
|
+
const bold = (s) => `\x1b[1m${s}\x1b[0m`;
|
|
102
|
+
|
|
103
|
+
console.log(`\n${bold('━━━ MCP ENDPOINT HEALTH CHECK ━━━')}`);
|
|
104
|
+
for (const r of results) {
|
|
105
|
+
const icon = r.healthy ? green('✓') : red('✗');
|
|
106
|
+
const latency = `${r.latencyMs}ms`;
|
|
107
|
+
const status = r.healthy ? green('OK') : red('FAIL');
|
|
108
|
+
const detail = r.error ? dim(` ${r.error}`) : '';
|
|
109
|
+
console.log(` ${icon} ${r.name.padEnd(22)} ${status.padEnd(14)} ${dim(latency)}${detail}`);
|
|
110
|
+
}
|
|
111
|
+
console.log();
|
|
112
|
+
|
|
113
|
+
const unhealthy = results.filter(r => !r.healthy);
|
|
114
|
+
if (unhealthy.length > 0) {
|
|
115
|
+
console.log(red(`${unhealthy.length}/${results.length} servers unhealthy — skip these in quorum:`));
|
|
116
|
+
unhealthy.forEach(r => console.log(` • ${r.name}`));
|
|
117
|
+
console.log();
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// Exit 1 if any server is unhealthy (useful for scripting)
|
|
122
|
+
const anyUnhealthy = results.some(r => !r.healthy);
|
|
123
|
+
process.exit(anyUnhealthy ? 1 : 0);
|