refacil-sdd-ai 5.3.1 → 5.3.3
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/bin/cli.js +63 -13
- package/lib/codegraph.js +139 -0
- package/lib/spec-sync.js +7 -1
- package/package.json +2 -3
package/bin/cli.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
1
|
+
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
'use strict';
|
|
4
4
|
|
|
@@ -441,6 +441,40 @@ function maybeInjectCodegraphSuggestion() {
|
|
|
441
441
|
}
|
|
442
442
|
}
|
|
443
443
|
|
|
444
|
+
/** Throttle window for the global CodeGraph CLI upgrade check: once per 7 days. */
|
|
445
|
+
const CODEGRAPH_UPGRADE_THROTTLE_MS = 7 * 24 * 60 * 60 * 1000;
|
|
446
|
+
|
|
447
|
+
/**
|
|
448
|
+
* Upgrade the global codegraph CLI to the version floor when it is outdated,
|
|
449
|
+
* at most once per throttle window. Fire-and-forget (the upgrade itself runs in
|
|
450
|
+
* a detached background process). Stamps the marker BEFORE spawning so a slow or
|
|
451
|
+
* failed upgrade never retries on every session. Never throws.
|
|
452
|
+
*/
|
|
453
|
+
function maybeUpgradeCodegraph() {
|
|
454
|
+
try {
|
|
455
|
+
if (!codegraph.isOutdated()) return;
|
|
456
|
+
const markerDir = path.join(os.homedir(), '.refacil-sdd-ai');
|
|
457
|
+
const marker = path.join(markerDir, '.codegraph-upgrade-check');
|
|
458
|
+
let last = 0;
|
|
459
|
+
try {
|
|
460
|
+
const parsed = new Date(fs.readFileSync(marker, 'utf8').trim()).getTime();
|
|
461
|
+
if (!isNaN(parsed)) last = parsed;
|
|
462
|
+
} catch (_) {}
|
|
463
|
+
if (Date.now() - last < CODEGRAPH_UPGRADE_THROTTLE_MS) return; // checked recently
|
|
464
|
+
try {
|
|
465
|
+
fs.mkdirSync(markerDir, { recursive: true });
|
|
466
|
+
fs.writeFileSync(marker, new Date().toISOString());
|
|
467
|
+
} catch (_) {}
|
|
468
|
+
process.stdout.write(
|
|
469
|
+
`[refacil-sdd-ai] CodeGraph CLI is outdated (v${codegraph.installedVersion()} < ${codegraph.MIN_VERSION}) — ` +
|
|
470
|
+
'upgrading in background. Restart your session once it finishes.\n',
|
|
471
|
+
);
|
|
472
|
+
codegraph.upgrade({ background: true });
|
|
473
|
+
} catch (_) {
|
|
474
|
+
// Tolerant — never block session startup
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
|
|
444
478
|
function checkUpdate() {
|
|
445
479
|
const root = resolveWorkspaceRoot();
|
|
446
480
|
|
|
@@ -596,18 +630,26 @@ function checkUpdate() {
|
|
|
596
630
|
'[refacil-sdd-ai] CodeGraph is enabled but the CLI is not installed. ' +
|
|
597
631
|
'Run /refacil:update to install and configure it.\n',
|
|
598
632
|
);
|
|
599
|
-
} else if (!codegraph.isInitialized(root)) {
|
|
600
|
-
// Not indexed: start automatically in the background, no question needed
|
|
601
|
-
codegraph.init(root);
|
|
602
|
-
process.stdout.write('[refacil-sdd-ai] CodeGraph: building index in background (~30s).\n');
|
|
603
|
-
} else if (codegraph.isStale(root)) {
|
|
604
|
-
// Index exists but new commits since last init: refresh silently
|
|
605
|
-
codegraph.init(root);
|
|
606
|
-
process.stdout.write('[refacil-sdd-ai] CodeGraph: refreshing index (new commits detected).\n');
|
|
607
633
|
} else {
|
|
608
|
-
//
|
|
609
|
-
//
|
|
610
|
-
|
|
634
|
+
// Installed: keep the global CLI current. Throttled background upgrade so
|
|
635
|
+
// existing users move past the version floor without blocking session start.
|
|
636
|
+
maybeUpgradeCodegraph();
|
|
637
|
+
if (!codegraph.isInitialized(root)) {
|
|
638
|
+
// Not indexed: start automatically in the background, no question needed
|
|
639
|
+
codegraph.init(root);
|
|
640
|
+
process.stdout.write('[refacil-sdd-ai] CodeGraph: building index in background (~30s).\n');
|
|
641
|
+
} else if (!codegraph.hasAutoSync() && codegraph.isStale(root)) {
|
|
642
|
+
// Older codegraph without the daemon file watcher: new commits since
|
|
643
|
+
// last init → refresh silently ourselves. On WATCHER_VERSION and up the
|
|
644
|
+
// daemon auto-syncs + catches up on startup, so this branch is skipped
|
|
645
|
+
// to avoid a redundant double reindex.
|
|
646
|
+
codegraph.init(root);
|
|
647
|
+
process.stdout.write('[refacil-sdd-ai] CodeGraph: refreshing index (new commits detected).\n');
|
|
648
|
+
} else {
|
|
649
|
+
// Initialized and up to date. If the timestamp file is absent (index was built
|
|
650
|
+
// externally, not through our tools), write it now so isStale() has a reference.
|
|
651
|
+
codegraph.touchTimestamp(root);
|
|
652
|
+
}
|
|
611
653
|
}
|
|
612
654
|
}
|
|
613
655
|
} catch (_) {
|
|
@@ -1371,12 +1413,20 @@ switch (command) {
|
|
|
1371
1413
|
if (!codegraph.isInstalled()) {
|
|
1372
1414
|
process.stdout.write('[refacil-sdd-ai] Installing @colbymchenry/codegraph globally...\n');
|
|
1373
1415
|
try {
|
|
1374
|
-
execSync('npm install -g @colbymchenry/codegraph', { stdio: 'inherit', timeout: 120000 });
|
|
1416
|
+
execSync('npm install -g @colbymchenry/codegraph@latest', { stdio: 'inherit', timeout: 120000 });
|
|
1375
1417
|
process.stdout.write('[refacil-sdd-ai] @colbymchenry/codegraph installed.\n');
|
|
1376
1418
|
} catch (err) {
|
|
1377
1419
|
process.stderr.write(`[refacil-sdd-ai] Failed to install codegraph: ${err.message}\n`);
|
|
1378
1420
|
process.exit(1);
|
|
1379
1421
|
}
|
|
1422
|
+
} else if (codegraph.isOutdated()) {
|
|
1423
|
+
// Existing global install is below the version floor — upgrade synchronously
|
|
1424
|
+
// so the rest of setup runs against the new binary.
|
|
1425
|
+
process.stdout.write(
|
|
1426
|
+
`[refacil-sdd-ai] Upgrading @colbymchenry/codegraph (installed v${codegraph.installedVersion()} < ${codegraph.MIN_VERSION})...\n`,
|
|
1427
|
+
);
|
|
1428
|
+
codegraph.upgrade({ background: false });
|
|
1429
|
+
process.stdout.write('[refacil-sdd-ai] @colbymchenry/codegraph upgraded.\n');
|
|
1380
1430
|
}
|
|
1381
1431
|
codegraph.registerMcp(selectedIDEs);
|
|
1382
1432
|
if (!codegraph.isInitialized(projectRoot)) {
|
package/lib/codegraph.js
CHANGED
|
@@ -262,6 +262,138 @@ function touchTimestamp(repoPath) {
|
|
|
262
262
|
} catch (_) {}
|
|
263
263
|
}
|
|
264
264
|
|
|
265
|
+
// ── Version floor + global upgrade ────────────────────────────────────────────
|
|
266
|
+
|
|
267
|
+
/**
|
|
268
|
+
* Minimum codegraph CLI version we want users to run. Existing global installs
|
|
269
|
+
* below this floor are upgraded automatically (throttled) by checkUpdate, and
|
|
270
|
+
* synchronously by `codegraph setup`.
|
|
271
|
+
*
|
|
272
|
+
* 0.9.9 dropped the native better-sqlite3 build (which pulled the deprecated
|
|
273
|
+
* prebuild-install) in favour of prebuilt per-platform binaries.
|
|
274
|
+
*/
|
|
275
|
+
const MIN_VERSION = '0.9.9';
|
|
276
|
+
|
|
277
|
+
/**
|
|
278
|
+
* Version at/above which the codegraph MCP daemon keeps the index fresh on its
|
|
279
|
+
* own across sessions, making our SessionStart stale-reindex redundant.
|
|
280
|
+
*
|
|
281
|
+
* The live file watcher (auto-sync while running) has existed since ~0.8.0, but
|
|
282
|
+
* that alone does NOT cover our case: isStale() fires on commits made WHILE the
|
|
283
|
+
* daemon/IDE was closed (a pull, a branch switch), which a live-only watcher
|
|
284
|
+
* misses. The capability that covers it is catch-up-on-connect — added in 0.9.5
|
|
285
|
+
* ("the MCP server now catches up on connect, reconciling anything that changed
|
|
286
|
+
* while it wasn't running"), shipped alongside the persistent per-project daemon.
|
|
287
|
+
* So 0.9.5 is the real floor. Below it (older installs not yet upgraded) we keep
|
|
288
|
+
* triggering `codegraph index` ourselves. See hasAutoSync.
|
|
289
|
+
*/
|
|
290
|
+
const WATCHER_VERSION = '0.9.5';
|
|
291
|
+
|
|
292
|
+
/**
|
|
293
|
+
* Return the installed global codegraph version string (e.g. "0.9.9"), or null
|
|
294
|
+
* when codegraph is not installed or the version cannot be determined.
|
|
295
|
+
* Never throws.
|
|
296
|
+
* @returns {string|null}
|
|
297
|
+
*/
|
|
298
|
+
function installedVersion() {
|
|
299
|
+
try {
|
|
300
|
+
const { spawnSync } = require('child_process');
|
|
301
|
+
// shell: true is required on Windows where npm binaries are installed as .cmd files
|
|
302
|
+
const result = spawnSync('codegraph', ['--version'], {
|
|
303
|
+
encoding: 'utf8',
|
|
304
|
+
stdio: ['ignore', 'pipe', 'ignore'],
|
|
305
|
+
timeout: 5000,
|
|
306
|
+
shell: true,
|
|
307
|
+
});
|
|
308
|
+
if (result.status !== 0 || !result.stdout) return null;
|
|
309
|
+
const match = result.stdout.trim().match(/\d+\.\d+\.\d+/);
|
|
310
|
+
return match ? match[0] : null;
|
|
311
|
+
} catch (_) {
|
|
312
|
+
return null;
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
/**
|
|
317
|
+
* Compare two dotted numeric versions. Returns true when `version` is strictly
|
|
318
|
+
* lower than `floor`. Malformed / non-numeric segments are treated as 0.
|
|
319
|
+
* Pure and total — never throws. Exported for testing.
|
|
320
|
+
* @param {string} version
|
|
321
|
+
* @param {string} floor
|
|
322
|
+
* @returns {boolean}
|
|
323
|
+
*/
|
|
324
|
+
function isVersionBelow(version, floor) {
|
|
325
|
+
const parse = (v) => String(v).split('.').map((n) => parseInt(n, 10) || 0);
|
|
326
|
+
const a = parse(version);
|
|
327
|
+
const b = parse(floor);
|
|
328
|
+
for (let i = 0; i < 3; i++) {
|
|
329
|
+
const x = a[i] || 0;
|
|
330
|
+
const y = b[i] || 0;
|
|
331
|
+
if (x < y) return true;
|
|
332
|
+
if (x > y) return false;
|
|
333
|
+
}
|
|
334
|
+
return false;
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
/**
|
|
338
|
+
* True when codegraph is installed but its version is below the floor.
|
|
339
|
+
* Returns false when not installed or the version cannot be parsed — callers
|
|
340
|
+
* handle the "missing" case separately, and an unknown version never triggers
|
|
341
|
+
* a disruptive upgrade.
|
|
342
|
+
* @param {string} [floor] - override the version floor (defaults to MIN_VERSION)
|
|
343
|
+
* @returns {boolean}
|
|
344
|
+
*/
|
|
345
|
+
function isOutdated(floor) {
|
|
346
|
+
const current = installedVersion();
|
|
347
|
+
if (!current) return false;
|
|
348
|
+
return isVersionBelow(current, floor || MIN_VERSION);
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
/**
|
|
352
|
+
* True when the installed codegraph's MCP daemon keeps the graph fresh on its
|
|
353
|
+
* own (file watcher + startup catch-up, WATCHER_VERSION and up) — in which case
|
|
354
|
+
* our SessionStart stale-reindex would just duplicate the daemon's work and
|
|
355
|
+
* should be skipped. Returns false for older versions (no watcher → keep doing
|
|
356
|
+
* the manual reindex) and when the version can't be determined (be conservative
|
|
357
|
+
* and keep the manual refresh). Never throws.
|
|
358
|
+
* @returns {boolean}
|
|
359
|
+
*/
|
|
360
|
+
function hasAutoSync() {
|
|
361
|
+
const current = installedVersion();
|
|
362
|
+
if (!current) return false; // unknown version → keep the manual refresh (safe)
|
|
363
|
+
return !isVersionBelow(current, WATCHER_VERSION);
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
/**
|
|
367
|
+
* Upgrade the global codegraph CLI to the latest published version.
|
|
368
|
+
* Never throws.
|
|
369
|
+
* @param {object} [opts]
|
|
370
|
+
* @param {boolean} [opts.background=true] - true: detached fire-and-forget (does not block);
|
|
371
|
+
* false: synchronous with inherited stdio (used by setup).
|
|
372
|
+
*/
|
|
373
|
+
function upgrade(opts) {
|
|
374
|
+
const background = !opts || opts.background !== false;
|
|
375
|
+
try {
|
|
376
|
+
if (background) {
|
|
377
|
+
const { spawn } = require('child_process');
|
|
378
|
+
const child = spawn('npm install -g @colbymchenry/codegraph@latest', [], {
|
|
379
|
+
detached: true,
|
|
380
|
+
stdio: 'ignore',
|
|
381
|
+
shell: true, // required for Windows .cmd binaries
|
|
382
|
+
windowsHide: true, // suppress console window pop-ups on Windows
|
|
383
|
+
});
|
|
384
|
+
child.unref();
|
|
385
|
+
} else {
|
|
386
|
+
const { execSync } = require('child_process');
|
|
387
|
+
execSync('npm install -g @colbymchenry/codegraph@latest', {
|
|
388
|
+
stdio: 'inherit',
|
|
389
|
+
timeout: 120000,
|
|
390
|
+
});
|
|
391
|
+
}
|
|
392
|
+
} catch (_) {
|
|
393
|
+
// Swallow: upgrade is best-effort, must never break the caller
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
|
|
265
397
|
module.exports = {
|
|
266
398
|
isInstalled,
|
|
267
399
|
isInitialized,
|
|
@@ -270,4 +402,11 @@ module.exports = {
|
|
|
270
402
|
touchTimestamp,
|
|
271
403
|
mcpEntry,
|
|
272
404
|
registerMcp,
|
|
405
|
+
MIN_VERSION,
|
|
406
|
+
WATCHER_VERSION,
|
|
407
|
+
installedVersion,
|
|
408
|
+
isVersionBelow,
|
|
409
|
+
isOutdated,
|
|
410
|
+
hasAutoSync,
|
|
411
|
+
upgrade,
|
|
273
412
|
};
|
package/lib/spec-sync.js
CHANGED
|
@@ -98,7 +98,13 @@ function parseCriteriaBlocks(markdown) {
|
|
|
98
98
|
};
|
|
99
99
|
|
|
100
100
|
for (const line of lines) {
|
|
101
|
-
|
|
101
|
+
// Tolerant heading match so archive's spec-sync never trips on cosmetic
|
|
102
|
+
// format variance the proposer/agents produce in the wild:
|
|
103
|
+
// - heading level: h2 (## CA-01) OR h3+ (### CA-A01) — level is ignored
|
|
104
|
+
// - criterion id: numeric (CA-01), feature-prefixed (CA-A01, CA-G01) or
|
|
105
|
+
// suffixed (CA-12b) — any [A-Za-z0-9] run after the CA-/CR- prefix
|
|
106
|
+
// The CA-/CR- token is the real signal; the heading level and id shape are not.
|
|
107
|
+
const m = line.match(/^#{2,6}\s+((?:CA|CR)-[A-Za-z0-9]+):\s*(.+)$/i);
|
|
102
108
|
if (m) {
|
|
103
109
|
pushCurrent();
|
|
104
110
|
current = { id: m[1].toUpperCase(), title: m[2].trim(), lines: [] };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "refacil-sdd-ai",
|
|
3
|
-
"version": "5.3.
|
|
3
|
+
"version": "5.3.3",
|
|
4
4
|
"description": "SDD-AI: Specification-Driven Development with AI — development methodology using AI with Claude Code, Cursor, OpenCode and Codex",
|
|
5
5
|
"bin": {
|
|
6
6
|
"refacil-sdd-ai": "./bin/cli.js"
|
|
@@ -47,7 +47,6 @@
|
|
|
47
47
|
"ws": "^8.18.0"
|
|
48
48
|
},
|
|
49
49
|
"optionalDependencies": {
|
|
50
|
-
"@clack/prompts": "^0.9.0"
|
|
51
|
-
"@colbymchenry/codegraph": "0.7.9"
|
|
50
|
+
"@clack/prompts": "^0.9.0"
|
|
52
51
|
}
|
|
53
52
|
}
|