@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,172 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* migrate-formal-dir.cjs
|
|
6
|
+
*
|
|
7
|
+
* Migrates a legacy .formal/ directory at the project root into the canonical
|
|
8
|
+
* .planning/formal/ location. Projects that adopted formal verification before
|
|
9
|
+
* the layout consolidation may still have root-level .formal/ — this script
|
|
10
|
+
* detects, merges, and optionally removes it.
|
|
11
|
+
*
|
|
12
|
+
* Conflict resolution: .planning/formal/ (canonical) always wins. If a file
|
|
13
|
+
* exists in both locations, the legacy version is skipped.
|
|
14
|
+
*
|
|
15
|
+
* Usage:
|
|
16
|
+
* node bin/migrate-formal-dir.cjs [--project-root=PATH] [--json] [--remove-legacy]
|
|
17
|
+
*
|
|
18
|
+
* Exit codes:
|
|
19
|
+
* 0 = success (or nothing to migrate)
|
|
20
|
+
* 1 = could not start (invalid ROOT)
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
const fs = require('fs');
|
|
24
|
+
const path = require('path');
|
|
25
|
+
|
|
26
|
+
// ─── CLI args ────────────────────────────────────────────────────────────────
|
|
27
|
+
|
|
28
|
+
const args = process.argv.slice(2);
|
|
29
|
+
|
|
30
|
+
function getFlag(name) {
|
|
31
|
+
return args.some(a => a === `--${name}`);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function getOption(name) {
|
|
35
|
+
const prefix = `--${name}=`;
|
|
36
|
+
const arg = args.find(a => a.startsWith(prefix));
|
|
37
|
+
return arg ? arg.slice(prefix.length) : null;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const jsonMode = getFlag('json');
|
|
41
|
+
const removeLegacy = getFlag('remove-legacy');
|
|
42
|
+
const ROOT = getOption('project-root') || process.cwd();
|
|
43
|
+
|
|
44
|
+
const TAG = '[migrate-formal]';
|
|
45
|
+
|
|
46
|
+
// ─── Logging ─────────────────────────────────────────────────────────────────
|
|
47
|
+
|
|
48
|
+
const logs = [];
|
|
49
|
+
|
|
50
|
+
function log(msg) {
|
|
51
|
+
if (!jsonMode) {
|
|
52
|
+
console.log(`${TAG} ${msg}`);
|
|
53
|
+
}
|
|
54
|
+
logs.push(msg);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// ─── Validate ROOT ──────────────────────────────────────────────────────────
|
|
58
|
+
|
|
59
|
+
if (!fs.existsSync(ROOT) || !fs.statSync(ROOT).isDirectory()) {
|
|
60
|
+
if (jsonMode) {
|
|
61
|
+
console.log(JSON.stringify({ error: `Invalid project root: ${ROOT}` }));
|
|
62
|
+
} else {
|
|
63
|
+
console.error(`${TAG} ERROR: Invalid project root: ${ROOT}`);
|
|
64
|
+
}
|
|
65
|
+
process.exit(1);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// ─── Paths ───────────────────────────────────────────────────────────────────
|
|
69
|
+
|
|
70
|
+
const legacyDir = path.join(ROOT, '.formal');
|
|
71
|
+
const canonDir = path.join(ROOT, '.planning', 'formal');
|
|
72
|
+
|
|
73
|
+
// ─── Detect ──────────────────────────────────────────────────────────────────
|
|
74
|
+
|
|
75
|
+
if (!fs.existsSync(legacyDir) || !fs.statSync(legacyDir).isDirectory()) {
|
|
76
|
+
log('No legacy .formal/ found — nothing to migrate');
|
|
77
|
+
if (jsonMode) {
|
|
78
|
+
console.log(JSON.stringify({
|
|
79
|
+
legacy_found: false,
|
|
80
|
+
copied: 0,
|
|
81
|
+
skipped: 0,
|
|
82
|
+
total: 0,
|
|
83
|
+
removed: false,
|
|
84
|
+
files_copied: [],
|
|
85
|
+
files_skipped: []
|
|
86
|
+
}));
|
|
87
|
+
}
|
|
88
|
+
process.exit(0);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// ─── Ensure target ──────────────────────────────────────────────────────────
|
|
92
|
+
|
|
93
|
+
fs.mkdirSync(canonDir, { recursive: true });
|
|
94
|
+
|
|
95
|
+
// ─── Walk helper ─────────────────────────────────────────────────────────────
|
|
96
|
+
|
|
97
|
+
function walkDir(dir) {
|
|
98
|
+
const results = [];
|
|
99
|
+
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
100
|
+
const full = path.join(dir, entry.name);
|
|
101
|
+
if (entry.isDirectory()) {
|
|
102
|
+
results.push(...walkDir(full));
|
|
103
|
+
} else if (entry.isFile()) {
|
|
104
|
+
results.push(full);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
return results;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// ─── Walk and merge ─────────────────────────────────────────────────────────
|
|
111
|
+
|
|
112
|
+
const allFiles = walkDir(legacyDir);
|
|
113
|
+
const filesCopied = [];
|
|
114
|
+
const filesSkipped = [];
|
|
115
|
+
|
|
116
|
+
for (const srcFile of allFiles) {
|
|
117
|
+
const relPath = path.relative(legacyDir, srcFile);
|
|
118
|
+
const destFile = path.join(canonDir, relPath);
|
|
119
|
+
|
|
120
|
+
try {
|
|
121
|
+
if (fs.existsSync(destFile)) {
|
|
122
|
+
log(`Skipped (already exists in .planning/formal/): ${relPath}`);
|
|
123
|
+
filesSkipped.push(relPath);
|
|
124
|
+
} else {
|
|
125
|
+
// Ensure intermediate directories
|
|
126
|
+
fs.mkdirSync(path.dirname(destFile), { recursive: true });
|
|
127
|
+
fs.copyFileSync(srcFile, destFile);
|
|
128
|
+
log(`Copied: ${relPath}`);
|
|
129
|
+
filesCopied.push(relPath);
|
|
130
|
+
}
|
|
131
|
+
} catch (err) {
|
|
132
|
+
log(`ERROR copying ${relPath}: ${err.message}`);
|
|
133
|
+
// Fail-open: continue with remaining files
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// ─── Summary ─────────────────────────────────────────────────────────────────
|
|
138
|
+
|
|
139
|
+
const total = allFiles.length;
|
|
140
|
+
log(`Migration complete: ${filesCopied.length} copied, ${filesSkipped.length} skipped (conflicts), ${total} total files in legacy .formal/`);
|
|
141
|
+
|
|
142
|
+
// ─── Optional removal ────────────────────────────────────────────────────────
|
|
143
|
+
|
|
144
|
+
let removed = false;
|
|
145
|
+
|
|
146
|
+
if (removeLegacy && total > 0) {
|
|
147
|
+
try {
|
|
148
|
+
fs.rmSync(legacyDir, { recursive: true, force: true });
|
|
149
|
+
log('Removed legacy .formal/ directory');
|
|
150
|
+
removed = true;
|
|
151
|
+
} catch (err) {
|
|
152
|
+
log(`ERROR removing legacy .formal/: ${err.message}`);
|
|
153
|
+
}
|
|
154
|
+
} else if (!removeLegacy && total > 0) {
|
|
155
|
+
log('Legacy .formal/ preserved — pass --remove-legacy to remove');
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// ─── JSON output ─────────────────────────────────────────────────────────────
|
|
159
|
+
|
|
160
|
+
if (jsonMode) {
|
|
161
|
+
console.log(JSON.stringify({
|
|
162
|
+
legacy_found: true,
|
|
163
|
+
copied: filesCopied.length,
|
|
164
|
+
skipped: filesSkipped.length,
|
|
165
|
+
total,
|
|
166
|
+
removed,
|
|
167
|
+
files_copied: filesCopied,
|
|
168
|
+
files_skipped: filesSkipped
|
|
169
|
+
}));
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
process.exit(0);
|
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* migrate-planning.cjs — Auto-migrate .planning/ from flat layout to v0.27+ hierarchy.
|
|
6
|
+
*
|
|
7
|
+
* Safe to run multiple times (idempotent). Moves files, never deletes.
|
|
8
|
+
* Writes a .planning/.migrated marker on completion.
|
|
9
|
+
*
|
|
10
|
+
* Usage:
|
|
11
|
+
* node bin/migrate-planning.cjs [--dry-run] [--root <dir>]
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
const fs = require('fs');
|
|
15
|
+
const path = require('path');
|
|
16
|
+
|
|
17
|
+
function migrate(root, dryRun) {
|
|
18
|
+
const planDir = path.join(root, '.planning');
|
|
19
|
+
if (!fs.existsSync(planDir)) {
|
|
20
|
+
if (!dryRun) console.log('[migrate-planning] No .planning/ directory — nothing to do.');
|
|
21
|
+
return { moved: 0, skipped: 0 };
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const stats = { moved: 0, skipped: 0, errors: [] };
|
|
25
|
+
|
|
26
|
+
function moveFile(src, dest) {
|
|
27
|
+
if (!fs.existsSync(src)) { stats.skipped++; return; }
|
|
28
|
+
if (fs.existsSync(dest)) { stats.skipped++; return; } // already migrated
|
|
29
|
+
if (dryRun) {
|
|
30
|
+
console.log(` [dry-run] ${path.relative(root, src)} → ${path.relative(root, dest)}`);
|
|
31
|
+
stats.moved++;
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
try {
|
|
35
|
+
fs.mkdirSync(path.dirname(dest), { recursive: true });
|
|
36
|
+
fs.renameSync(src, dest);
|
|
37
|
+
stats.moved++;
|
|
38
|
+
} catch (err) {
|
|
39
|
+
stats.errors.push({ src, dest, error: err.message });
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const entries = fs.readdirSync(planDir);
|
|
44
|
+
|
|
45
|
+
// ─── 1. Quorum round session logs → quorum/rounds/ ────────────────────────
|
|
46
|
+
for (const e of entries) {
|
|
47
|
+
if (e.startsWith('quorum-rounds-session-') || (e.startsWith('quorum-rounds-') && e.endsWith('.jsonl'))) {
|
|
48
|
+
moveFile(
|
|
49
|
+
path.join(planDir, e),
|
|
50
|
+
path.join(planDir, 'quorum', 'rounds', e)
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// ─── 2. Quorum slot correlation files → quorum/correlations/ ──────────────
|
|
56
|
+
for (const e of entries) {
|
|
57
|
+
if (e.startsWith('quorum-slot-corr-') && e.endsWith('.json')) {
|
|
58
|
+
moveFile(
|
|
59
|
+
path.join(planDir, e),
|
|
60
|
+
path.join(planDir, 'quorum', 'correlations', e)
|
|
61
|
+
);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// ─── 3. Scoreboard + failures → quorum/ ──────────────────────────────────
|
|
66
|
+
moveFile(
|
|
67
|
+
path.join(planDir, 'quorum-scoreboard.json'),
|
|
68
|
+
path.join(planDir, 'quorum', 'scoreboard.json')
|
|
69
|
+
);
|
|
70
|
+
moveFile(
|
|
71
|
+
path.join(planDir, 'quorum-failures.json'),
|
|
72
|
+
path.join(planDir, 'quorum', 'failures.json')
|
|
73
|
+
);
|
|
74
|
+
|
|
75
|
+
// ─── 3b. debates/ → quorum/debates/ ────────────────────────────────────
|
|
76
|
+
const debatesDir = path.join(planDir, 'debates');
|
|
77
|
+
if (fs.existsSync(debatesDir) && fs.statSync(debatesDir).isDirectory()) {
|
|
78
|
+
const debateFiles = fs.readdirSync(debatesDir);
|
|
79
|
+
for (const f of debateFiles) {
|
|
80
|
+
moveFile(
|
|
81
|
+
path.join(debatesDir, f),
|
|
82
|
+
path.join(planDir, 'quorum', 'debates', f)
|
|
83
|
+
);
|
|
84
|
+
}
|
|
85
|
+
// Remove empty debates/ directory
|
|
86
|
+
if (!dryRun) {
|
|
87
|
+
try {
|
|
88
|
+
const remaining = fs.readdirSync(debatesDir);
|
|
89
|
+
if (remaining.length === 0) fs.rmdirSync(debatesDir);
|
|
90
|
+
} catch (_) {}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// ─── 4. Telemetry files → telemetry/ ──────────────────────────────────────
|
|
95
|
+
moveFile(
|
|
96
|
+
path.join(planDir, 'conformance-events.jsonl'),
|
|
97
|
+
path.join(planDir, 'telemetry', 'conformance-events.jsonl')
|
|
98
|
+
);
|
|
99
|
+
moveFile(
|
|
100
|
+
path.join(planDir, 'token-usage.jsonl'),
|
|
101
|
+
path.join(planDir, 'telemetry', 'token-usage.jsonl')
|
|
102
|
+
);
|
|
103
|
+
|
|
104
|
+
// ─── 4. Milestone audit/integration files → milestones/ ───────────────────
|
|
105
|
+
for (const e of entries) {
|
|
106
|
+
// v0.X-MILESTONE-AUDIT.md
|
|
107
|
+
const auditMatch = e.match(/^(v[\d.]+)-MILESTONE-AUDIT\.md$/);
|
|
108
|
+
if (auditMatch) {
|
|
109
|
+
// Only move if not already in milestones/
|
|
110
|
+
const dest = path.join(planDir, 'milestones', e);
|
|
111
|
+
moveFile(path.join(planDir, e), dest);
|
|
112
|
+
continue;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// v0.X-INTEGRATION-{CHECK,SUMMARY,KEY-FILES,REPORT}.{md,txt}
|
|
116
|
+
const intMatch = e.match(/^(v[\d.]+)-INTEGRATION-/);
|
|
117
|
+
if (intMatch) {
|
|
118
|
+
moveFile(
|
|
119
|
+
path.join(planDir, e),
|
|
120
|
+
path.join(planDir, 'milestones', e)
|
|
121
|
+
);
|
|
122
|
+
continue;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// ─── 5. State backups → archive/state-backups/ ────────────────────────────
|
|
127
|
+
for (const e of entries) {
|
|
128
|
+
if (e.startsWith('STATE.md.bak-')) {
|
|
129
|
+
moveFile(
|
|
130
|
+
path.join(planDir, e),
|
|
131
|
+
path.join(planDir, 'archive', 'state-backups', e)
|
|
132
|
+
);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// ─── 6. Dated design docs → archive/designs/ ─────────────────────────────
|
|
137
|
+
for (const e of entries) {
|
|
138
|
+
if (/^\d{4}-\d{2}-\d{2}-/.test(e)) {
|
|
139
|
+
moveFile(
|
|
140
|
+
path.join(planDir, e),
|
|
141
|
+
path.join(planDir, 'archive', 'designs', e)
|
|
142
|
+
);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// ─── 7. Old roadmap versions → archive/designs/ ──────────────────────────
|
|
147
|
+
for (const e of entries) {
|
|
148
|
+
if (/^ROADMAP-v[\d.]+\.md$/.test(e)) {
|
|
149
|
+
moveFile(
|
|
150
|
+
path.join(planDir, e),
|
|
151
|
+
path.join(planDir, 'archive', 'designs', e)
|
|
152
|
+
);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// ─── 8. Write .planning/.gitignore ─────────────────────────────────────────
|
|
157
|
+
if (!dryRun) {
|
|
158
|
+
const gitignorePath = path.join(planDir, '.gitignore');
|
|
159
|
+
const gitignoreContent = [
|
|
160
|
+
'# Auto-generated by migrate-planning.cjs',
|
|
161
|
+
'# Ephemeral quorum runtime data',
|
|
162
|
+
'quorum/rounds/',
|
|
163
|
+
'quorum/correlations/',
|
|
164
|
+
'quorum/scoreboard.json',
|
|
165
|
+
'quorum/failures.json',
|
|
166
|
+
'',
|
|
167
|
+
'# Telemetry (append-only logs)',
|
|
168
|
+
'telemetry/conformance-events.jsonl',
|
|
169
|
+
'telemetry/token-usage.jsonl',
|
|
170
|
+
'',
|
|
171
|
+
'# State backups',
|
|
172
|
+
'archive/state-backups/',
|
|
173
|
+
'',
|
|
174
|
+
].join('\n');
|
|
175
|
+
fs.writeFileSync(gitignorePath, gitignoreContent, 'utf8');
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// ─── 9. Write migration marker ────────────────────────────────────────────
|
|
179
|
+
if (!dryRun && stats.moved > 0) {
|
|
180
|
+
const markerPath = path.join(planDir, '.migrated');
|
|
181
|
+
fs.writeFileSync(markerPath, JSON.stringify({
|
|
182
|
+
version: '0.27',
|
|
183
|
+
migratedAt: new Date().toISOString(),
|
|
184
|
+
filesMoved: stats.moved,
|
|
185
|
+
}, null, 2), 'utf8');
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
return stats;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// ─── CLI entry ───────────────────────────────────────────────────────────────
|
|
192
|
+
if (require.main === module) {
|
|
193
|
+
const args = process.argv.slice(2);
|
|
194
|
+
const dryRun = args.includes('--dry-run');
|
|
195
|
+
const rootIdx = args.indexOf('--root');
|
|
196
|
+
const root = rootIdx >= 0 ? args[rootIdx + 1] : process.cwd();
|
|
197
|
+
|
|
198
|
+
console.log(`[migrate-planning] ${dryRun ? 'DRY RUN — ' : ''}Migrating ${root}/.planning/`);
|
|
199
|
+
const stats = migrate(root, dryRun);
|
|
200
|
+
console.log(`[migrate-planning] Done: ${stats.moved} moved, ${stats.skipped} skipped`);
|
|
201
|
+
if (stats.errors && stats.errors.length) {
|
|
202
|
+
console.error(`[migrate-planning] Errors:`, stats.errors);
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
module.exports = { migrate };
|
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
const fs = require('fs');
|
|
5
|
+
const path = require('path');
|
|
6
|
+
const os = require('os');
|
|
7
|
+
|
|
8
|
+
// SLOT_MIGRATION_MAP: old model-based names → new slot names
|
|
9
|
+
const SLOT_MIGRATION_MAP = {
|
|
10
|
+
'codex-cli': 'codex-cli-1',
|
|
11
|
+
'gemini-cli': 'gemini-cli-1',
|
|
12
|
+
'opencode': 'opencode-1',
|
|
13
|
+
'copilot-cli': 'copilot-1',
|
|
14
|
+
'claude-deepseek': 'claude-1',
|
|
15
|
+
'claude-minimax': 'claude-2',
|
|
16
|
+
'claude-qwen-coder': 'claude-3',
|
|
17
|
+
'claude-kimi': 'claude-4',
|
|
18
|
+
'claude-llama4': 'claude-5',
|
|
19
|
+
'claude-glm': 'claude-6',
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Migrate ~/.claude.json mcpServers keys from model-based names to slot names.
|
|
24
|
+
* @param {string} claudeJsonPath - Absolute path to ~/.claude.json
|
|
25
|
+
* @param {boolean} dryRun - If true, do not write changes
|
|
26
|
+
* @returns {{ changed: number, renamed: Array<{from: string, to: string}> }}
|
|
27
|
+
*/
|
|
28
|
+
function migrateClaudeJson(claudeJsonPath, dryRun = false) {
|
|
29
|
+
let raw;
|
|
30
|
+
try {
|
|
31
|
+
raw = JSON.parse(fs.readFileSync(claudeJsonPath, 'utf8'));
|
|
32
|
+
} catch (e) {
|
|
33
|
+
if (e.code === 'ENOENT') {
|
|
34
|
+
return { changed: 0, renamed: [] };
|
|
35
|
+
}
|
|
36
|
+
throw new Error(`Failed to read ${claudeJsonPath}: ${e.message}`);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const servers = raw.mcpServers || {};
|
|
40
|
+
let changed = 0;
|
|
41
|
+
const renamed = [];
|
|
42
|
+
|
|
43
|
+
for (const [oldName, newName] of Object.entries(SLOT_MIGRATION_MAP)) {
|
|
44
|
+
if (servers[oldName] !== undefined && servers[newName] === undefined) {
|
|
45
|
+
// Rename: assign to new key, delete old key
|
|
46
|
+
servers[newName] = servers[oldName];
|
|
47
|
+
delete servers[oldName];
|
|
48
|
+
changed++;
|
|
49
|
+
renamed.push({ from: oldName, to: newName });
|
|
50
|
+
}
|
|
51
|
+
// oldName absent + newName present → already migrated (skip, idempotent)
|
|
52
|
+
// both present → skip (safety — don't overwrite)
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
if (changed > 0) {
|
|
56
|
+
raw.mcpServers = servers;
|
|
57
|
+
if (!dryRun) {
|
|
58
|
+
fs.writeFileSync(claudeJsonPath, JSON.stringify(raw, null, 2) + '\n', 'utf8');
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
return { changed, renamed };
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// tool_prefix migration map for qgsd.json
|
|
66
|
+
const QGSD_PREFIX_MAP = {
|
|
67
|
+
'mcp__codex-cli__': 'mcp__codex-cli-1__',
|
|
68
|
+
'mcp__gemini-cli__': 'mcp__gemini-cli-1__',
|
|
69
|
+
'mcp__opencode__': 'mcp__opencode-1__',
|
|
70
|
+
'mcp__copilot-cli__': 'mcp__copilot-1__',
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Migrate ~/.claude/qgsd.json required_models tool_prefix values to slot-based prefixes.
|
|
75
|
+
* @param {string} qgsdJsonPath - Absolute path to ~/.claude/qgsd.json
|
|
76
|
+
* @param {boolean} dryRun - If true, do not write changes
|
|
77
|
+
* @returns {{ changed: number, patched: Array<{key: string, from: string, to: string}> }}
|
|
78
|
+
*/
|
|
79
|
+
function migrateQgsdJson(qgsdJsonPath, dryRun = false) {
|
|
80
|
+
if (!fs.existsSync(qgsdJsonPath)) {
|
|
81
|
+
return { changed: 0, patched: [] };
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
let raw;
|
|
85
|
+
try {
|
|
86
|
+
raw = JSON.parse(fs.readFileSync(qgsdJsonPath, 'utf8'));
|
|
87
|
+
} catch (e) {
|
|
88
|
+
throw new Error(`Failed to read ${qgsdJsonPath}: ${e.message}`);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const requiredModels = raw.required_models;
|
|
92
|
+
if (!requiredModels || typeof requiredModels !== 'object') {
|
|
93
|
+
return { changed: 0, patched: [] };
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
let changed = 0;
|
|
97
|
+
const patched = [];
|
|
98
|
+
|
|
99
|
+
for (const [modelKey, modelDef] of Object.entries(requiredModels)) {
|
|
100
|
+
if (modelDef && typeof modelDef.tool_prefix === 'string') {
|
|
101
|
+
const newPrefix = QGSD_PREFIX_MAP[modelDef.tool_prefix];
|
|
102
|
+
if (newPrefix) {
|
|
103
|
+
patched.push({ key: modelKey, from: modelDef.tool_prefix, to: newPrefix });
|
|
104
|
+
modelDef.tool_prefix = newPrefix;
|
|
105
|
+
changed++;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
if (changed > 0 && !dryRun) {
|
|
111
|
+
fs.writeFileSync(qgsdJsonPath, JSON.stringify(raw, null, 2) + '\n', 'utf8');
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
return { changed, patched };
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Populate quorum_active in ~/.claude/qgsd.json from current mcpServers in ~/.claude.json.
|
|
119
|
+
* Idempotent: skips if quorum_active already present and non-empty.
|
|
120
|
+
* @param {string} qgsdJsonPath - Absolute path to ~/.claude/qgsd.json
|
|
121
|
+
* @param {string} claudeJsonPath - Absolute path to ~/.claude.json
|
|
122
|
+
* @param {boolean} dryRun - If true, do not write changes
|
|
123
|
+
* @returns {{ skipped: boolean, slots: string[] }}
|
|
124
|
+
*/
|
|
125
|
+
function populateActiveSlots(qgsdJsonPath, claudeJsonPath, dryRun = false) {
|
|
126
|
+
// Read current slot names from ~/.claude.json
|
|
127
|
+
let slotNames = [];
|
|
128
|
+
try {
|
|
129
|
+
const raw = JSON.parse(fs.readFileSync(claudeJsonPath, 'utf8'));
|
|
130
|
+
slotNames = Object.keys(raw.mcpServers || {});
|
|
131
|
+
} catch (e) {
|
|
132
|
+
if (e.code !== 'ENOENT') throw new Error(`Failed to read ${claudeJsonPath}: ${e.message}`);
|
|
133
|
+
// ~/.claude.json absent — nothing to populate
|
|
134
|
+
return { skipped: true, slots: [] };
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// Read or create qgsd.json
|
|
138
|
+
let qgsdConfig = {};
|
|
139
|
+
try {
|
|
140
|
+
qgsdConfig = JSON.parse(fs.readFileSync(qgsdJsonPath, 'utf8'));
|
|
141
|
+
} catch (e) {
|
|
142
|
+
if (e.code !== 'ENOENT') throw new Error(`Failed to read ${qgsdJsonPath}: ${e.message}`);
|
|
143
|
+
// qgsd.json absent — create minimal object
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// Idempotent: skip if already set and non-empty
|
|
147
|
+
if (Array.isArray(qgsdConfig.quorum_active) && qgsdConfig.quorum_active.length > 0) {
|
|
148
|
+
return { skipped: true, slots: qgsdConfig.quorum_active };
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// Populate and write
|
|
152
|
+
qgsdConfig.quorum_active = slotNames;
|
|
153
|
+
if (!dryRun) {
|
|
154
|
+
fs.mkdirSync(path.dirname(qgsdJsonPath), { recursive: true });
|
|
155
|
+
fs.writeFileSync(qgsdJsonPath, JSON.stringify(qgsdConfig, null, 2) + '\n', 'utf8');
|
|
156
|
+
}
|
|
157
|
+
return { skipped: false, slots: slotNames };
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// CLI entrypoint
|
|
161
|
+
if (require.main === module) {
|
|
162
|
+
const dryRun = process.argv.includes('--dry-run');
|
|
163
|
+
const claudeJsonPath = path.join(os.homedir(), '.claude.json');
|
|
164
|
+
const qgsdJsonPath = path.join(os.homedir(), '.claude', 'qgsd.json');
|
|
165
|
+
|
|
166
|
+
let r1, r2;
|
|
167
|
+
try {
|
|
168
|
+
r1 = migrateClaudeJson(claudeJsonPath, dryRun);
|
|
169
|
+
} catch (e) {
|
|
170
|
+
console.error(`Error migrating ~/.claude.json: ${e.message}`);
|
|
171
|
+
process.exit(1);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
try {
|
|
175
|
+
r2 = migrateQgsdJson(qgsdJsonPath, dryRun);
|
|
176
|
+
} catch (e) {
|
|
177
|
+
console.error(`Error migrating ~/.claude/qgsd.json: ${e.message}`);
|
|
178
|
+
process.exit(1);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
let r3;
|
|
182
|
+
try {
|
|
183
|
+
r3 = populateActiveSlots(qgsdJsonPath, claudeJsonPath, dryRun);
|
|
184
|
+
if (!r3.skipped) {
|
|
185
|
+
console.log(`[migrate-to-slots] quorum_active populated: ${r3.slots.join(', ')}`);
|
|
186
|
+
} else {
|
|
187
|
+
console.log(`[migrate-to-slots] quorum_active already set (${r3.slots.length} slots) — skipped`);
|
|
188
|
+
}
|
|
189
|
+
} catch (e) {
|
|
190
|
+
console.error(`Error populating quorum_active: ${e.message}`);
|
|
191
|
+
process.exit(1);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
if (r1.changed === 0 && r2.changed === 0) {
|
|
195
|
+
console.log('Already migrated — no changes needed');
|
|
196
|
+
} else if (dryRun) {
|
|
197
|
+
const totalRenames = r1.renamed.length + r2.patched.length;
|
|
198
|
+
console.log(`[DRY RUN] Would rename ${totalRenames} entries:`);
|
|
199
|
+
for (const { from, to } of r1.renamed) {
|
|
200
|
+
console.log(` mcpServers: ${from} → ${to}`);
|
|
201
|
+
}
|
|
202
|
+
for (const { key, from, to } of r2.patched) {
|
|
203
|
+
console.log(` qgsd.json required_models.${key}.tool_prefix: ${from} → ${to}`);
|
|
204
|
+
}
|
|
205
|
+
} else {
|
|
206
|
+
if (r1.changed > 0) {
|
|
207
|
+
console.log(`Migrated ${r1.changed} mcpServers entries:`);
|
|
208
|
+
for (const { from, to } of r1.renamed) {
|
|
209
|
+
console.log(` ${from} → ${to}`);
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
if (r2.changed > 0) {
|
|
213
|
+
console.log(`Patched ${r2.changed} qgsd.json tool_prefix values`);
|
|
214
|
+
for (const { key, from, to } of r2.patched) {
|
|
215
|
+
console.log(` required_models.${key}.tool_prefix: ${from} → ${to}`);
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
process.exit(0);
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/**
|
|
224
|
+
* Append a single slot name to quorum_active in qgsd.json if not already present.
|
|
225
|
+
* Idempotent: no-op if slot is already in the array.
|
|
226
|
+
* @param {string} slotName - e.g. "copilot-2"
|
|
227
|
+
* @param {string} qgsdJsonPath - path to ~/.claude/qgsd.json
|
|
228
|
+
* @param {boolean} dryRun - if true, report but do not write
|
|
229
|
+
* @returns {{ added: boolean, slot: string, skipped: boolean }}
|
|
230
|
+
*/
|
|
231
|
+
function addSlotToQuorumActive(slotName, qgsdJsonPath, dryRun = false) {
|
|
232
|
+
let qgsdConfig = {};
|
|
233
|
+
try {
|
|
234
|
+
if (fs.existsSync(qgsdJsonPath)) {
|
|
235
|
+
qgsdConfig = JSON.parse(fs.readFileSync(qgsdJsonPath, 'utf8'));
|
|
236
|
+
}
|
|
237
|
+
} catch (e) {
|
|
238
|
+
return { added: false, slot: slotName, skipped: true, error: e.message };
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
const active = Array.isArray(qgsdConfig.quorum_active) ? qgsdConfig.quorum_active : [];
|
|
242
|
+
if (active.includes(slotName)) {
|
|
243
|
+
return { added: false, slot: slotName, skipped: true, reason: 'already present' };
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
if (dryRun) {
|
|
247
|
+
return { added: true, slot: slotName, skipped: false, dryRun: true };
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
qgsdConfig.quorum_active = [...active, slotName];
|
|
251
|
+
fs.writeFileSync(qgsdJsonPath, JSON.stringify(qgsdConfig, null, 2) + '\n');
|
|
252
|
+
return { added: true, slot: slotName, skipped: false };
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
module.exports = { migrateClaudeJson, migrateQgsdJson, populateActiveSlots, addSlotToQuorumActive, SLOT_MIGRATION_MAP };
|