@geraldmaron/construct 1.0.23 → 1.0.24

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.
Files changed (52) hide show
  1. package/README.md +0 -2
  2. package/bin/construct +15 -215
  3. package/lib/embed/inbox.mjs +6 -3
  4. package/lib/embed/recommendation-store.mjs +7 -289
  5. package/lib/embed/reconcile.mjs +2 -2
  6. package/lib/hooks/config-protection.mjs +4 -4
  7. package/lib/hooks/session-reflect.mjs +5 -1
  8. package/lib/intake/git-queue.mjs +195 -0
  9. package/lib/intake/queue.mjs +9 -16
  10. package/lib/mcp/tools/storage.mjs +2 -3
  11. package/lib/mcp-catalog.json +3 -3
  12. package/lib/mcp-manager.mjs +59 -3
  13. package/lib/observation-store.mjs +38 -166
  14. package/lib/orchestration/runtime.mjs +3 -2
  15. package/lib/reconcile/index.mjs +0 -2
  16. package/lib/service-manager.mjs +38 -256
  17. package/lib/setup.mjs +26 -426
  18. package/lib/status.mjs +3 -6
  19. package/lib/storage/admin.mjs +48 -325
  20. package/lib/storage/backend.mjs +10 -57
  21. package/lib/storage/hybrid-query.mjs +15 -196
  22. package/lib/storage/sync.mjs +36 -177
  23. package/lib/storage/vector-client.mjs +256 -235
  24. package/lib/strategy-store.mjs +35 -286
  25. package/lib/worker/entrypoint.mjs +6 -14
  26. package/package.json +6 -5
  27. package/platforms/claude/settings.template.json +0 -7
  28. package/scripts/sync-specialists.mjs +46 -12
  29. package/specialists/prompts/cx-qa.md +1 -1
  30. package/specialists/registry.json +0 -8
  31. package/templates/docs/construct_guide.md +1 -1
  32. package/db/schema/001_init.sql +0 -40
  33. package/db/schema/002_pgvector.sql +0 -182
  34. package/db/schema/003_intake.sql +0 -47
  35. package/db/schema/003_observation_reconciliation.sql +0 -14
  36. package/db/schema/004_recommendations.sql +0 -46
  37. package/db/schema/005_strategy.sql +0 -21
  38. package/db/schema/006_graph.sql +0 -24
  39. package/db/schema/007_tags.sql +0 -30
  40. package/db/schema/008_skill_usage.sql +0 -24
  41. package/db/schema/009_scheduler.sql +0 -14
  42. package/db/schema/010_cx_scores.sql +0 -51
  43. package/lib/intake/postgres-queue.mjs +0 -240
  44. package/lib/reconcile/postgres-namespace.mjs +0 -102
  45. package/lib/services/local-postgres.mjs +0 -15
  46. package/lib/storage/backup.mjs +0 -347
  47. package/lib/storage/migrations.mjs +0 -187
  48. package/lib/storage/postgres-backup.mjs +0 -124
  49. package/lib/storage/sql-store.mjs +0 -45
  50. package/lib/storage/store-version.mjs +0 -115
  51. package/lib/storage/unified-storage.mjs +0 -550
  52. package/lib/storage/vector-store.mjs +0 -100
package/README.md CHANGED
@@ -269,7 +269,6 @@ construct/
269
269
  ├── commands Command prompt assets
270
270
  ├── config
271
271
  ├── dashboard
272
- ├── db
273
272
  ├── deploy
274
273
  ├── docs Architecture notes, runbooks, and documentation contract
275
274
  ├── examples
@@ -282,7 +281,6 @@ construct/
282
281
  ├── rules Coding and quality standards
283
282
  ├── schemas
284
283
  ├── scripts
285
- ├── services
286
284
  ├── skills Reusable domain knowledge files
287
285
  ├── specialists
288
286
  ├── templates
package/bin/construct CHANGED
@@ -67,9 +67,7 @@ import { readCostLog, summarizeCostData, formatCostReport, clearCostLog } from '
67
67
  import { readEfficiencyLog, summarizeEfficiencyData, formatEfficiencyReport } from '../lib/efficiency.mjs';
68
68
  import { resolveColors } from '../lib/term-format.mjs';
69
69
  import { buildHybridSearchResultsAsync } from '../lib/storage/hybrid-query.mjs';
70
- import { createSqlClient, closeSqlClient, readVectorConfig } from '../lib/storage/backend.mjs';
71
- import { describeVectorStore } from '../lib/storage/vector-store.mjs';
72
- import { describeSqlStore } from '../lib/storage/sql-store.mjs';
70
+ import { createSqlClient, closeSqlClient } from '../lib/storage/backend.mjs';
73
71
  import { syncFileStateToSql } from '../lib/storage/sync.mjs';
74
72
  import { deleteIngestedArtifacts, getStorageStatus, inferProjectName, resetStorage } from '../lib/storage/admin.mjs';
75
73
  import { runPressureRelease } from '../lib/runtime-pressure.mjs';
@@ -822,35 +820,10 @@ async function cmdDoctor() {
822
820
  const researchLintResults = lintResearchRepo({ rootDir: process.cwd() });
823
821
  add('Research artifacts meet minimum evidence structure', researchLintResults.every((entry) => entry.errors.length === 0), true);
824
822
 
825
- const sqlConfig = describeSqlStore(process.env);
826
- const sqlClient = createSqlClient(process.env);
827
- // Track SQL reachability so the schema-migration check can cascade correctly
828
- // rather than emitting a redundant second warning for the same offline server.
829
- let sqlReachable = false;
830
- if (sqlConfig.configured && sqlClient) {
831
- try {
832
- await sqlClient`select 1 as ok`;
833
- sqlReachable = true;
834
- add('SQL backend reachable', true);
835
- } catch {
836
- // In solo mode the local vector index is the active storage path, so an
837
- // unreachable Postgres is expected unless the user has started it. Emit a
838
- // contextual message rather than a bare ⚠ so the fix is obvious.
839
- const { getDeploymentMode } = await import('../lib/deployment-mode.mjs');
840
- const mode = getDeploymentMode(process.env) ?? 'solo';
841
- const hint = mode === 'solo'
842
- ? ' (solo mode — run `construct dev` to start Postgres)'
843
- : ' — check DATABASE_URL and that the server is running';
844
- add(`SQL backend unreachable${hint}`, false, true);
845
- } finally {
846
- await closeSqlClient(sqlClient);
847
- }
848
- } else {
849
- add('SQL backend configured', sqlConfig.fallbackAvailable, true);
850
- }
851
-
852
- const vectorConfig = describeVectorStore(process.env);
853
- add('Vector backend configured', Boolean(vectorConfig.endpoint || vectorConfig.indexPath || vectorConfig.mode === 'file'));
823
+ const { VectorClient } = await import('../lib/storage/vector-client.mjs');
824
+ const vectorClient = new VectorClient({ env: process.env });
825
+ const vectorOk = await vectorClient.isHealthy();
826
+ add('Vector backend (LanceDB) reachable', vectorOk);
854
827
 
855
828
  const { regenerateDocs } = await import('../lib/auto-docs.mjs');
856
829
  const { changed: stale } = await regenerateDocs({ rootDir: ROOT_DIR, check: true });
@@ -979,35 +952,6 @@ async function cmdDoctor() {
979
952
  add('Observation size', false, true);
980
953
  }
981
954
 
982
- // Only run the schema migration check when SQL is both configured AND
983
- // currently reachable. If the SQL check above already failed (sqlReachable
984
- // is false) this check will also fail with the same ECONNREFUSED and would
985
- // produce a redundant second warning about the same offline server.
986
- if (sqlConfig.configured && sqlReachable) {
987
- try {
988
- const migClient = createSqlClient(process.env);
989
- if (migClient) {
990
- const { describeMigrations } = await import('../lib/storage/migrations.mjs');
991
- const m = await describeMigrations(migClient);
992
- await closeSqlClient(migClient);
993
- const ok = !!m.ok && m.drift.length === 0;
994
- let label = 'Schema migrations applied + no drift';
995
- if (!ok && m.drift && m.drift.length > 0) {
996
- const repairable = m.drift.filter((d) => d.idempotent).length;
997
- const notRepairable = m.drift.length - repairable;
998
- if (notRepairable === 0) {
999
- label = `Schema migrations: ${m.drift.length} drifted (all idempotent — run \`construct storage repair-migrations --yes\`)`;
1000
- } else {
1001
- label = `Schema migrations: ${m.drift.length} drifted (${notRepairable} non-idempotent — write a new migration file)`;
1002
- }
1003
- }
1004
- add(label, ok, true);
1005
- }
1006
- } catch {
1007
- add('Schema migrations applied + no drift', false, true);
1008
- }
1009
- }
1010
-
1011
955
  try {
1012
956
  const { detectBeadsDrift } = await import('../lib/beads/drift.mjs');
1013
957
  const drift = detectBeadsDrift();
@@ -1037,24 +981,6 @@ async function cmdDoctor() {
1037
981
  add('cm available', false, true);
1038
982
  }
1039
983
 
1040
- try {
1041
- const dockerCli = spawnSync(process.platform === 'win32' ? 'where' : 'which', ['docker'], { stdio: 'pipe' });
1042
- let dockerOk = false;
1043
- let dockerNote = 'Docker not installed (optional — managed Postgres needs it)';
1044
- if (dockerCli.status === 0) {
1045
- const dockerInfo = spawnSync('docker', ['info'], { stdio: 'pipe' });
1046
- if (dockerInfo.status === 0) {
1047
- dockerOk = true;
1048
- dockerNote = 'Docker daemon reachable';
1049
- } else {
1050
- dockerNote = 'Docker CLI installed but daemon not reachable — run `construct up` on macOS to auto-start, or start Docker manually';
1051
- }
1052
- }
1053
- add(dockerOk ? 'Docker daemon reachable' : `Docker daemon reachable (${dockerNote})`, dockerOk, true);
1054
- } catch {
1055
- add('Docker daemon reachable', false, true);
1056
- }
1057
-
1058
984
  try {
1059
985
  const failuresPath = path.join(HOME, '.cx', 'hook-failures.jsonl');
1060
986
  if (fs.existsSync(failuresPath)) {
@@ -1569,49 +1495,11 @@ async function cmdStorage(args) {
1569
1495
  println(`Reconciled observations: re-embedded ${result.reembedded} of ${result.checked} (model ${result.model}).`);
1570
1496
  return;
1571
1497
  }
1572
- if (action === 'migrations') {
1573
- const { describeMigrations } = await import('../lib/storage/migrations.mjs');
1574
- const sqlClient = createSqlClient(process.env);
1575
- if (!sqlClient) { errorln('No DATABASE_URL configured.'); process.exit(1); }
1576
- try {
1577
- const report = await describeMigrations(sqlClient);
1578
- println(JSON.stringify(report, null, 2));
1579
- if (report.drift?.length > 0) process.exit(2);
1580
- } finally {
1581
- await closeSqlClient(sqlClient);
1582
- }
1583
- return;
1584
- }
1585
- if (action === 'repair-migrations') {
1586
- const confirm = rest.includes('--yes');
1587
- if (!confirm) {
1588
- errorln('This re-applies drifted migrations (idempotent only) and updates their recorded SHA.');
1589
- errorln('Destructive migrations (DROP / TRUNCATE / ALTER…DROP / DELETE) are refused.');
1590
- errorln('Re-run with --yes to proceed.');
1591
- process.exit(1);
1592
- }
1593
- const { runMigrations } = await import('../lib/storage/migrations.mjs');
1594
- const sqlClient = createSqlClient(process.env);
1595
- if (!sqlClient) { errorln('No DATABASE_URL configured.'); process.exit(1); }
1596
- try {
1597
- const result = await runMigrations(sqlClient, { repair: true });
1598
- println(JSON.stringify({
1599
- applied: result.applied,
1600
- repaired: result.repaired,
1601
- skipped: result.skipped,
1602
- drift: result.drift,
1603
- }, null, 2));
1604
- if (result.drift.length > 0) {
1605
- errorln('');
1606
- errorln('Refused to repair non-idempotent migrations:');
1607
- for (const d of result.drift) errorln(` ${d.filename}`);
1608
- process.exit(2);
1609
- }
1610
- info(`Repaired ${result.repaired.length} migration(s); applied ${result.applied.length} new.`);
1611
- } finally {
1612
- await closeSqlClient(sqlClient);
1613
- }
1614
- return;
1498
+ if (action === 'migrations' || action === 'repair-migrations') {
1499
+ // Postgres schema migrations were removed with the SQL backend; LanceDB
1500
+ // tables are created on demand and need no migration runner.
1501
+ errorln('construct storage ' + action + ' is no longer supported: Postgres was removed.');
1502
+ process.exit(1);
1615
1503
  }
1616
1504
  if (action === 'sync') {
1617
1505
  const result = await syncFileStateToSql(process.cwd(), { env: process.env, project: inferProjectName(process.cwd()) });
@@ -3543,6 +3431,7 @@ async function cmdOrchestrate(args) {
3543
3431
  host: flag('--host'),
3544
3432
  hostModel: flag('--host-model'),
3545
3433
  hostProvider: flag('--host-provider'),
3434
+ workerBackend: flag('--worker-backend'),
3546
3435
  fileCount: Number(flag('--file-count') || 0),
3547
3436
  moduleCount: Number(flag('--module-count') || 0),
3548
3437
  };
@@ -4566,99 +4455,10 @@ async function cmdLintContracts() {
4566
4455
  }
4567
4456
 
4568
4457
  async function cmdBackup(args) {
4569
- const sub = args[0];
4570
-
4571
- if (!sub || sub === 'create') {
4572
- const includeSecrets = args.includes('--include-secrets');
4573
- const noPrune = args.includes('--no-prune');
4574
- const keepArg = args.find((a) => a.startsWith('--keep='));
4575
- const keep = keepArg
4576
- ? Number(keepArg.split('=')[1])
4577
- : Number(process.env.CONSTRUCT_BACKUP_RETAIN || 10);
4578
- const { createBackup, pruneBackups } = await import('../lib/storage/backup.mjs');
4579
- println('Creating backup…');
4580
- try {
4581
- const result = await createBackup({ includeSecrets });
4582
- ok(`Backup created: ${result.path}`);
4583
- println(` Contents: ${result.manifest.contents.join(', ') || '(empty)'}`);
4584
- if (!includeSecrets) println(' Secrets redacted. Use --include-secrets to include them.');
4585
- if (!noPrune) {
4586
- const pruned = pruneBackups({ keep });
4587
- if (pruned.removed.length) {
4588
- println(` Auto-pruned ${pruned.removed.length} old backup(s); kept ${pruned.kept}.`);
4589
- }
4590
- }
4591
- } catch (err) {
4592
- errorln(`Backup failed: ${err.message}`);
4593
- process.exit(1);
4594
- }
4595
- return;
4596
- }
4597
-
4598
- if (sub === 'prune') {
4599
- const keepArg = args.find((a) => a.startsWith('--keep='));
4600
- const keep = keepArg
4601
- ? Number(keepArg.split('=')[1])
4602
- : Number(process.env.CONSTRUCT_BACKUP_RETAIN || 10);
4603
- const { pruneBackups } = await import('../lib/storage/backup.mjs');
4604
- const result = pruneBackups({ keep });
4605
- if (result.removed.length === 0) {
4606
- println(`Nothing to prune (have ${result.kept}, keeping ${keep}).`);
4607
- } else {
4608
- ok(`Pruned ${result.removed.length} backup(s); kept ${result.kept}.`);
4609
- for (const r of result.removed) println(` removed ${r}`);
4610
- }
4611
- return;
4612
- }
4613
-
4614
- if (sub === 'verify') {
4615
- const archivePath = args[1];
4616
- if (!archivePath) { errorln('Usage: construct backup verify <archive>'); process.exit(1); }
4617
- const { verifyBackup } = await import('../lib/storage/backup.mjs');
4618
- const result = await verifyBackup(archivePath);
4619
- if (result.ok) {
4620
- ok('Backup verified — all checksums match.');
4621
- } else {
4622
- errorln(`Backup verification failed:\n ${result.errors.join('\n ')}`);
4623
- process.exit(1);
4624
- }
4625
- return;
4626
- }
4627
-
4628
- if (sub === 'restore') {
4629
- const archivePath = args[1];
4630
- const yes = args.includes('--yes');
4631
- if (!archivePath) { errorln('Usage: construct backup restore <archive> [--yes]'); process.exit(1); }
4632
- if (!yes) {
4633
- println(`About to restore from: ${archivePath}`);
4634
- println('This will overwrite observations, sessions, config.env, and registry.json.');
4635
- println('Press Ctrl+C to cancel, or re-run with --yes to proceed.');
4636
- await new Promise((r) => setTimeout(r, 3000));
4637
- }
4638
- const { restoreBackup } = await import('../lib/storage/backup.mjs');
4639
- const result = await restoreBackup(archivePath, { yes });
4640
- if (result.ok) {
4641
- ok(`Restored: ${result.restored.join(', ')}`);
4642
- } else {
4643
- if (result.restored.length) println(`Partially restored: ${result.restored.join(', ')}`);
4644
- errorln(`Errors:\n ${result.errors.join('\n ')}`);
4645
- process.exit(1);
4646
- }
4647
- return;
4648
- }
4649
-
4650
- if (sub === 'list') {
4651
- const { listBackups } = await import('../lib/storage/backup.mjs');
4652
- const backups = listBackups();
4653
- if (!backups.length) { println('No backups found.'); return; }
4654
- for (const b of backups) {
4655
- const mb = (b.size / 1024 / 1024).toFixed(1);
4656
- println(` ${b.name} (${mb} MB) ${b.mtime.toISOString().slice(0, 19)}`);
4657
- }
4658
- return;
4659
- }
4660
-
4661
- errorln(`Unknown backup subcommand: ${sub}. Available: create, verify, restore, list, prune`);
4458
+ // Postgres-era backups were removed with the SQL backend (lib/storage/backup.mjs).
4459
+ // State is now Git-backed (.cx/) plus the local LanceDB index under .cx/lancedb.
4460
+ void args;
4461
+ errorln('construct backup is no longer supported: Postgres was removed. Commit .cx/ for Git-backed state; the LanceDB index lives under .cx/lancedb.');
4662
4462
  process.exit(1);
4663
4463
  }
4664
4464
 
@@ -241,7 +241,7 @@ function inferFileClassification(filePath) {
241
241
  * Summary = first non-empty line of extracted text (≤120 chars).
242
242
  * Content = first 1500 chars of extracted text for RAG searchability.
243
243
  */
244
- function recordInboxObservation(rootDir, { sourcePath, outputPath, characters, extractedText, category, knowledgeSubdir }) {
244
+ async function recordInboxObservation(rootDir, { sourcePath, outputPath, characters, extractedText, category, knowledgeSubdir }) {
245
245
  const lines = (extractedText ?? '').split('\n').map((l) => l.trim()).filter(Boolean);
246
246
  const firstLine = (lines[0] ?? '').slice(0, 120);
247
247
  const filename = sourcePath.split('/').pop();
@@ -251,7 +251,10 @@ function recordInboxObservation(rootDir, { sourcePath, outputPath, characters, e
251
251
  const content = (extractedText ?? '').slice(0, 1500);
252
252
  const tags = ['inbox', 'ingested-doc', category, `knowledge:${knowledgeSubdir}`];
253
253
 
254
- addObservation(rootDir, {
254
+ // Await the write so callers (poll) know ingestion is complete. Fire-and-
255
+ // forget here let the LanceDB write outlive poll() and race the caller's
256
+ // teardown, surfacing as "ENOTEMPTY: directory not empty" on .cx cleanup.
257
+ await addObservation(rootDir, {
255
258
  role: 'construct',
256
259
  category,
257
260
  summary,
@@ -414,7 +417,7 @@ export class InboxWatcher {
414
417
  const droppedInfo = fileResult.droppedInfo ?? [];
415
418
  writeExtractionWarning({ sourcePath: candidate.path, droppedInfo });
416
419
 
417
- recordInboxObservation(this.#rootDir, {
420
+ await recordInboxObservation(this.#rootDir, {
418
421
  sourcePath: candidate.path,
419
422
  outputPath: fileResult.outputPath,
420
423
  characters: fileResult.characters,