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 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
- // Initialized and up to date. If the timestamp file is absent (index was built
609
- // externally, not through our tools), write it now so isStale() has a reference.
610
- codegraph.touchTimestamp(root);
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
- const m = line.match(/^##\s+((?:CA|CR)-\d+):\s*(.+)$/i);
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.1",
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
  }