@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,300 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
// bin/generate-triage-bundle.cjs
|
|
4
|
+
// Reads .planning/formal/check-results.ndjson, writes .planning/formal/diff-report.md and .planning/formal/suspects.md.
|
|
5
|
+
// Requirements: TRIAGE-01
|
|
6
|
+
|
|
7
|
+
const fs = require('fs');
|
|
8
|
+
const path = require('path');
|
|
9
|
+
const { parseNDJSON, groupByFormalism } = require('./verify-formal-results.cjs');
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Parse .planning/formal/check-results.ndjson relative to cwd.
|
|
13
|
+
* Uses parseNDJSON from verify-formal-results.cjs (fail-open, skips malformed lines).
|
|
14
|
+
* @returns {object[]} — parsed result records (may be empty)
|
|
15
|
+
*/
|
|
16
|
+
function parseCurrentNDJSON() {
|
|
17
|
+
const ndjsonPath = path.join(process.cwd(), '.planning', 'formal', 'check-results.ndjson');
|
|
18
|
+
return parseNDJSON(ndjsonPath);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Filter and sort suspects from current NDJSON records.
|
|
23
|
+
* Suspects = result=fail OR triage_tags.length > 0.
|
|
24
|
+
* Sort: fail > warn+tags > inconclusive+tags > everything else.
|
|
25
|
+
* @param {object[]} results — parsed NDJSON records
|
|
26
|
+
* @returns {object[]} — suspect records, sorted by priority
|
|
27
|
+
*/
|
|
28
|
+
function generateSuspects(results) {
|
|
29
|
+
const suspects = results.filter(r =>
|
|
30
|
+
r.result === 'fail' ||
|
|
31
|
+
(Array.isArray(r.triage_tags) && r.triage_tags.length > 0)
|
|
32
|
+
);
|
|
33
|
+
|
|
34
|
+
const priority = (r) => {
|
|
35
|
+
if (r.result === 'fail') return 0;
|
|
36
|
+
if (r.result === 'warn' && r.triage_tags && r.triage_tags.length > 0) return 1;
|
|
37
|
+
return 2;
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
suspects.sort((a, b) => priority(a) - priority(b));
|
|
41
|
+
return suspects;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Load previous run's check_id->result snapshot from embedded JSON block in diff-report.md.
|
|
46
|
+
* Returns {} if no previous report exists or no JSON block found (fail-open).
|
|
47
|
+
* @returns {Object.<string, string>} — map of check_id to result string
|
|
48
|
+
*/
|
|
49
|
+
function loadPreviousSnapshot() {
|
|
50
|
+
const diffPath = path.join(process.cwd(), '.planning', 'formal', 'diff-report.md');
|
|
51
|
+
let content;
|
|
52
|
+
try {
|
|
53
|
+
content = fs.readFileSync(diffPath, 'utf8');
|
|
54
|
+
} catch (_) {
|
|
55
|
+
return {}; // no previous report — first run
|
|
56
|
+
}
|
|
57
|
+
// Extract JSON block from "## Previous Run (for next comparison)" section
|
|
58
|
+
const match = content.match(/```json\s*\n([\s\S]*?)```/);
|
|
59
|
+
if (!match) return {};
|
|
60
|
+
try {
|
|
61
|
+
const parsed = JSON.parse(match[1]);
|
|
62
|
+
if (typeof parsed === 'object' && parsed !== null && !Array.isArray(parsed)) {
|
|
63
|
+
return parsed;
|
|
64
|
+
}
|
|
65
|
+
} catch (_) { /* malformed JSON block — treat as first run */ }
|
|
66
|
+
return {};
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Compute per-check delta between current and previous run.
|
|
71
|
+
* @param {object[]} currentResults — parsed NDJSON records
|
|
72
|
+
* @param {Object.<string, string>} previousSnapshot — check_id->result from last run
|
|
73
|
+
* @returns {{ transitioned: object[], newChecks: object[], removedChecks: string[], unchanged: number }}
|
|
74
|
+
*/
|
|
75
|
+
function computeDeltas(currentResults, previousSnapshot) {
|
|
76
|
+
const currentMap = {};
|
|
77
|
+
for (const r of currentResults) {
|
|
78
|
+
if (r.check_id) currentMap[r.check_id] = r;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const transitioned = [];
|
|
82
|
+
const newChecks = [];
|
|
83
|
+
let unchanged = 0;
|
|
84
|
+
|
|
85
|
+
for (const r of currentResults) {
|
|
86
|
+
if (!r.check_id) continue;
|
|
87
|
+
if (!(r.check_id in previousSnapshot)) {
|
|
88
|
+
newChecks.push(r);
|
|
89
|
+
} else if (previousSnapshot[r.check_id] !== r.result) {
|
|
90
|
+
transitioned.push({ ...r, previousResult: previousSnapshot[r.check_id] });
|
|
91
|
+
} else {
|
|
92
|
+
unchanged++;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const removedChecks = Object.keys(previousSnapshot).filter(id => !(id in currentMap));
|
|
97
|
+
|
|
98
|
+
return { transitioned, newChecks, removedChecks, unchanged };
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Build check_id->result map from current results (for embedding in diff-report.md).
|
|
103
|
+
* @param {object[]} currentResults
|
|
104
|
+
* @returns {Object.<string, string>}
|
|
105
|
+
*/
|
|
106
|
+
function buildCurrentSnapshot(currentResults) {
|
|
107
|
+
const snap = {};
|
|
108
|
+
for (const r of currentResults) {
|
|
109
|
+
if (r.check_id) snap[r.check_id] = r.result;
|
|
110
|
+
}
|
|
111
|
+
return snap;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Generate diff-report.md content.
|
|
116
|
+
* @param {object[]} currentResults
|
|
117
|
+
* @param {Object} deltas — from computeDeltas
|
|
118
|
+
* @param {boolean} isFirstRun
|
|
119
|
+
* @returns {string} — full markdown content
|
|
120
|
+
*/
|
|
121
|
+
function formatDiffReport(currentResults, deltas, isFirstRun) {
|
|
122
|
+
const timestamp = new Date().toISOString();
|
|
123
|
+
const grouped = groupByFormalism(currentResults);
|
|
124
|
+
|
|
125
|
+
// Overall status: fail > inconclusive > pass
|
|
126
|
+
let overallStatus = 'pass';
|
|
127
|
+
for (const counts of Object.values(grouped)) {
|
|
128
|
+
if (counts.fail > 0) { overallStatus = 'fail'; break; }
|
|
129
|
+
if (counts.inconclusive > 0 && overallStatus !== 'fail') overallStatus = 'inconclusive';
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
const pass = currentResults.filter(r => r.result === 'pass').length;
|
|
133
|
+
const fail = currentResults.filter(r => r.result === 'fail').length;
|
|
134
|
+
const other = currentResults.length - pass - fail;
|
|
135
|
+
|
|
136
|
+
const lines = [
|
|
137
|
+
'# Formal Verification Diff Report',
|
|
138
|
+
'',
|
|
139
|
+
`**Generated:** ${timestamp}`,
|
|
140
|
+
`**Current Run:** ${pass} pass, ${fail} fail` + (other > 0 ? `, ${other} warn/inconclusive` : ''),
|
|
141
|
+
`**Previous Run:** ${isFirstRun ? 'first run — no previous snapshot' : `${deltas.transitioned.length} transitions, ${deltas.newChecks.length} new, ${deltas.removedChecks.length} removed`}`,
|
|
142
|
+
`**Overall Status:** ${overallStatus}`,
|
|
143
|
+
'',
|
|
144
|
+
];
|
|
145
|
+
|
|
146
|
+
if (isFirstRun) {
|
|
147
|
+
lines.push('**Status:** First run — no previous snapshot to compare against.');
|
|
148
|
+
lines.push('');
|
|
149
|
+
lines.push('All checks are new. Run again after a second verification to see deltas.');
|
|
150
|
+
lines.push('');
|
|
151
|
+
} else {
|
|
152
|
+
// Transitioned checks
|
|
153
|
+
if (deltas.transitioned.length > 0) {
|
|
154
|
+
lines.push('## Transitioned Checks');
|
|
155
|
+
lines.push('');
|
|
156
|
+
lines.push('| Check | Previous | Current | Summary |');
|
|
157
|
+
lines.push('|-------|----------|---------|---------|');
|
|
158
|
+
for (const r of deltas.transitioned) {
|
|
159
|
+
const summary = (r.summary || '').substring(0, 60);
|
|
160
|
+
lines.push(`| ${r.check_id} | ${r.previousResult} | ${r.result} | ${summary} |`);
|
|
161
|
+
}
|
|
162
|
+
lines.push('');
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// New checks
|
|
166
|
+
if (deltas.newChecks.length > 0) {
|
|
167
|
+
lines.push('## New Checks');
|
|
168
|
+
lines.push('');
|
|
169
|
+
lines.push('| Check | Result | Summary |');
|
|
170
|
+
lines.push('|-------|--------|---------|');
|
|
171
|
+
for (const r of deltas.newChecks) {
|
|
172
|
+
const summary = (r.summary || '').substring(0, 60);
|
|
173
|
+
lines.push(`| ${r.check_id} | ${r.result} | ${summary} |`);
|
|
174
|
+
}
|
|
175
|
+
lines.push('');
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// Removed checks
|
|
179
|
+
if (deltas.removedChecks.length > 0) {
|
|
180
|
+
lines.push('## Removed Checks');
|
|
181
|
+
lines.push('');
|
|
182
|
+
for (const id of deltas.removedChecks) {
|
|
183
|
+
lines.push(`- ${id}: no longer run`);
|
|
184
|
+
}
|
|
185
|
+
lines.push('');
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// Unchanged — summary only
|
|
189
|
+
if (deltas.unchanged > 0) {
|
|
190
|
+
lines.push('## Unchanged Checks');
|
|
191
|
+
lines.push('');
|
|
192
|
+
lines.push(`${deltas.unchanged} check(s) unchanged from previous run — no action needed.`);
|
|
193
|
+
lines.push('');
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// Embedded snapshot — REQUIRED for next run delta computation
|
|
198
|
+
const snapshot = buildCurrentSnapshot(currentResults);
|
|
199
|
+
lines.push('## Previous Run (for next comparison)');
|
|
200
|
+
lines.push('');
|
|
201
|
+
lines.push('```json');
|
|
202
|
+
lines.push(JSON.stringify(snapshot));
|
|
203
|
+
lines.push('```');
|
|
204
|
+
|
|
205
|
+
return lines.join('\n');
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* Generate suspects.md content.
|
|
210
|
+
* @param {object[]} suspects — from generateSuspects (pre-sorted)
|
|
211
|
+
* @returns {string} — full markdown content
|
|
212
|
+
*/
|
|
213
|
+
function formatSuspectsReport(suspects) {
|
|
214
|
+
const timestamp = new Date().toISOString();
|
|
215
|
+
const lines = [
|
|
216
|
+
'# Formal Verification Suspects',
|
|
217
|
+
'',
|
|
218
|
+
`**Generated:** ${timestamp}`,
|
|
219
|
+
`**Total Suspects:** ${suspects.length}`,
|
|
220
|
+
'',
|
|
221
|
+
];
|
|
222
|
+
|
|
223
|
+
if (suspects.length === 0) {
|
|
224
|
+
lines.push('No suspects found — all checks passed without triage tags.');
|
|
225
|
+
return lines.join('\n');
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
const critical = suspects.filter(r => r.result === 'fail');
|
|
229
|
+
const warnings = suspects.filter(r => r.result === 'warn' && Array.isArray(r.triage_tags) && r.triage_tags.length > 0);
|
|
230
|
+
const inconclusive = suspects.filter(r => r.result === 'inconclusive' && Array.isArray(r.triage_tags) && r.triage_tags.length > 0);
|
|
231
|
+
const other = suspects.filter(r =>
|
|
232
|
+
!['fail'].includes(r.result) &&
|
|
233
|
+
!(r.result === 'warn' && Array.isArray(r.triage_tags) && r.triage_tags.length > 0) &&
|
|
234
|
+
!(r.result === 'inconclusive' && Array.isArray(r.triage_tags) && r.triage_tags.length > 0)
|
|
235
|
+
);
|
|
236
|
+
|
|
237
|
+
function renderGroup(title, group) {
|
|
238
|
+
if (group.length === 0) return;
|
|
239
|
+
lines.push(`## ${title}`);
|
|
240
|
+
lines.push('');
|
|
241
|
+
for (const r of group) {
|
|
242
|
+
lines.push(`### ${r.check_id}`);
|
|
243
|
+
lines.push(`- **Property:** ${r.property || 'N/A'}`);
|
|
244
|
+
lines.push(`- **Summary:** ${r.summary || 'N/A'}`);
|
|
245
|
+
lines.push(`- **Runtime:** ${r.runtime_ms != null ? r.runtime_ms + 'ms' : 'N/A'}`);
|
|
246
|
+
lines.push(`- **Tags:** ${Array.isArray(r.triage_tags) && r.triage_tags.length > 0 ? r.triage_tags.join(', ') : 'none'}`);
|
|
247
|
+
lines.push('');
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
renderGroup('Critical Failures (result=fail)', critical);
|
|
252
|
+
renderGroup('Warnings with Tags (result=warn)', warnings);
|
|
253
|
+
renderGroup('Inconclusive with Tags (result=inconclusive)', inconclusive);
|
|
254
|
+
renderGroup('Other Suspects', other);
|
|
255
|
+
|
|
256
|
+
return lines.join('\n');
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
function writeFileSafe(filePath, content) {
|
|
260
|
+
try {
|
|
261
|
+
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
262
|
+
fs.writeFileSync(filePath, content, 'utf8');
|
|
263
|
+
process.stderr.write('[generate-triage-bundle] Wrote: ' + filePath + '\n');
|
|
264
|
+
} catch (err) {
|
|
265
|
+
process.stderr.write('[generate-triage-bundle] Warning: could not write ' + filePath + ': ' + err.message + '\n');
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
/**
|
|
270
|
+
* Main CLI entry point.
|
|
271
|
+
* Reads .planning/formal/check-results.ndjson relative to process.cwd().
|
|
272
|
+
* Writes .planning/formal/diff-report.md and .planning/formal/suspects.md.
|
|
273
|
+
*/
|
|
274
|
+
function main() {
|
|
275
|
+
const currentResults = parseCurrentNDJSON();
|
|
276
|
+
const previousSnapshot = loadPreviousSnapshot();
|
|
277
|
+
const isFirstRun = Object.keys(previousSnapshot).length === 0;
|
|
278
|
+
const deltas = computeDeltas(currentResults, previousSnapshot);
|
|
279
|
+
const suspects = generateSuspects(currentResults);
|
|
280
|
+
|
|
281
|
+
const formalDir = path.join(process.cwd(), '.planning', 'formal');
|
|
282
|
+
writeFileSafe(path.join(formalDir, 'diff-report.md'), formatDiffReport(currentResults, deltas, isFirstRun));
|
|
283
|
+
writeFileSafe(path.join(formalDir, 'suspects.md'), formatSuspectsReport(suspects));
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
// ── Exports ───────────────────────────────────────────────────────────────────
|
|
287
|
+
// Exported for testing without CLI execution side effects.
|
|
288
|
+
if (require.main === module) {
|
|
289
|
+
main();
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
module.exports = {
|
|
293
|
+
parseCurrentNDJSON,
|
|
294
|
+
generateSuspects,
|
|
295
|
+
computeDeltas,
|
|
296
|
+
loadPreviousSnapshot,
|
|
297
|
+
buildCurrentSnapshot,
|
|
298
|
+
formatDiffReport,
|
|
299
|
+
formatSuspectsReport,
|
|
300
|
+
};
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* gh-account-rotate.cjs — rotate to the next gh auth account
|
|
6
|
+
*
|
|
7
|
+
* Called by call-quorum-slot.cjs as oauth_rotation.rotate_cmd for copilot-1.
|
|
8
|
+
* Delegates all gh auth status parsing to the gh-cli auth driver (single source of truth).
|
|
9
|
+
*
|
|
10
|
+
* Usage:
|
|
11
|
+
* node bin/gh-account-rotate.cjs
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
const { spawnSync } = require('child_process');
|
|
15
|
+
const { parseGhStatus } = require('./auth-drivers/gh-cli.cjs');
|
|
16
|
+
|
|
17
|
+
const { accounts, active } = parseGhStatus();
|
|
18
|
+
|
|
19
|
+
if (accounts.length < 2) {
|
|
20
|
+
process.stderr.write('[gh-rotate] Only one gh account — nothing to rotate\n');
|
|
21
|
+
process.exit(0);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const idx = active ? accounts.indexOf(active) : 0;
|
|
25
|
+
const next = accounts[(idx + 1) % accounts.length];
|
|
26
|
+
|
|
27
|
+
process.stderr.write(`[gh-rotate] ${active ?? '?'} → ${next}\n`);
|
|
28
|
+
|
|
29
|
+
const r = spawnSync('gh', ['auth', 'switch', '--user', next, '--hostname', 'github.com'], {
|
|
30
|
+
stdio: 'inherit',
|
|
31
|
+
encoding: 'utf8',
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
process.exit(r.status ?? 0);
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
// bin/initialize-model-registry.cjs
|
|
4
|
+
// One-time idempotent initialization of .planning/formal/model-registry.json.
|
|
5
|
+
//
|
|
6
|
+
// Scans .planning/formal/tla/, .planning/formal/alloy/, and .planning/formal/prism/ for canonical model files
|
|
7
|
+
// and creates model-registry.json with provenance metadata for each.
|
|
8
|
+
//
|
|
9
|
+
// Usage:
|
|
10
|
+
// node bin/initialize-model-registry.cjs
|
|
11
|
+
//
|
|
12
|
+
// Idempotent: if .planning/formal/model-registry.json already exists, exits 0 silently.
|
|
13
|
+
// Run this once after cloning the repo, before any generate/promote/debug operations.
|
|
14
|
+
|
|
15
|
+
const fs = require('fs');
|
|
16
|
+
const path = require('path');
|
|
17
|
+
|
|
18
|
+
const ROOT = path.join(__dirname, '..');
|
|
19
|
+
const REGISTRY_PATH = path.join(ROOT, '.planning', 'formal', 'model-registry.json');
|
|
20
|
+
|
|
21
|
+
// ── Idempotent guard ──────────────────────────────────────────────────────────
|
|
22
|
+
if (fs.existsSync(REGISTRY_PATH)) {
|
|
23
|
+
process.exit(0);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// ── Scan .planning/formal/ subdirectories ───────────────────────────────────────────────
|
|
27
|
+
const SCAN_DIRS = [
|
|
28
|
+
{ dir: path.join(ROOT, '.planning', 'formal', 'tla'), exts: ['.tla'] },
|
|
29
|
+
{ dir: path.join(ROOT, '.planning', 'formal', 'alloy'), exts: ['.als'] },
|
|
30
|
+
{ dir: path.join(ROOT, '.planning', 'formal', 'prism'), exts: ['.pm'] },
|
|
31
|
+
];
|
|
32
|
+
|
|
33
|
+
// Files to skip — not canonical specs
|
|
34
|
+
const SKIP_PATTERNS = [
|
|
35
|
+
/_TTrace_/, // TLC error trace artifacts
|
|
36
|
+
];
|
|
37
|
+
|
|
38
|
+
const models = {};
|
|
39
|
+
|
|
40
|
+
for (const { dir, exts } of SCAN_DIRS) {
|
|
41
|
+
if (!fs.existsSync(dir)) continue;
|
|
42
|
+
|
|
43
|
+
let entries;
|
|
44
|
+
try {
|
|
45
|
+
entries = fs.readdirSync(dir);
|
|
46
|
+
} catch (err) {
|
|
47
|
+
process.stderr.write('[initialize-model-registry] Cannot read dir ' + dir + ': ' + err.message + '\n');
|
|
48
|
+
continue;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
for (const filename of entries) {
|
|
52
|
+
const ext = path.extname(filename);
|
|
53
|
+
if (!exts.includes(ext)) continue;
|
|
54
|
+
|
|
55
|
+
// Skip non-canonical files
|
|
56
|
+
const skip = SKIP_PATTERNS.some(pattern => pattern.test(filename));
|
|
57
|
+
if (skip) continue;
|
|
58
|
+
|
|
59
|
+
const absPath = path.join(dir, filename);
|
|
60
|
+
|
|
61
|
+
// Compute registry key — relative from ROOT, no leading './' or '/'
|
|
62
|
+
let key = path.relative(ROOT, absPath).replace(/\\/g, '/');
|
|
63
|
+
if (key.startsWith('./')) key = key.slice(2);
|
|
64
|
+
if (key.startsWith('/')) key = key.slice(1);
|
|
65
|
+
|
|
66
|
+
// Validate key format
|
|
67
|
+
if (key.startsWith('/') || key.startsWith('./')) {
|
|
68
|
+
process.stderr.write('[initialize-model-registry] WARNING: key has invalid prefix: ' + key + '\n');
|
|
69
|
+
continue;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const mtime = fs.statSync(absPath).mtime.toISOString();
|
|
73
|
+
|
|
74
|
+
// Detect generated files (xstate-derived)
|
|
75
|
+
const isGenerated = filename.includes('xstate');
|
|
76
|
+
|
|
77
|
+
models[key] = {
|
|
78
|
+
version: 1,
|
|
79
|
+
last_updated: mtime,
|
|
80
|
+
update_source: isGenerated ? 'generate' : 'manual',
|
|
81
|
+
source_id: isGenerated ? 'generate:tla-from-xstate' : null,
|
|
82
|
+
session_id: null,
|
|
83
|
+
description: ''
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// ── Build registry with sorted keys ──────────────────────────────────────────
|
|
89
|
+
const sortedModels = {};
|
|
90
|
+
for (const key of Object.keys(models).sort()) {
|
|
91
|
+
sortedModels[key] = models[key];
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const registry = {
|
|
95
|
+
version: '1.0',
|
|
96
|
+
last_sync: new Date().toISOString(),
|
|
97
|
+
models: sortedModels
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
// ── Write registry ─────────────────────────────────────────────────────────
|
|
101
|
+
fs.mkdirSync(path.dirname(REGISTRY_PATH), { recursive: true });
|
|
102
|
+
fs.writeFileSync(REGISTRY_PATH, JSON.stringify(registry, null, 2), 'utf8');
|
|
103
|
+
|
|
104
|
+
const count = Object.keys(sortedModels).length;
|
|
105
|
+
process.stdout.write('[initialize-model-registry] Created .planning/formal/model-registry.json with ' + count + ' entries\n');
|