@cleocode/cleo 2026.4.56 → 2026.4.57

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/dist/cli/index.js CHANGED
@@ -9508,6 +9508,12 @@ var init_brain_schema = __esm({
9508
9508
  // observation → references → symbol
9509
9509
  "modified_by",
9510
9510
  // file → modified_by → session
9511
+ "code_reference",
9512
+ // memory node → code_reference → nexus symbol/file (T645)
9513
+ "affects",
9514
+ // observation → affects → symbol/file (impact tracking)
9515
+ "mentions",
9516
+ // observation → mentions → symbol name (weak reference)
9511
9517
  // Plasticity (Hebbian + STDP co-retrieval)
9512
9518
  "co_retrieved"
9513
9519
  // A → co_retrieved → B (Hebbian: frequently retrieved together)
@@ -45910,7 +45916,9 @@ var init_edge_types = __esm({
45910
45916
  // Observation → symbol name mention
45911
45917
  MENTIONS: "mentions",
45912
45918
  // Observation → symbol/file structural link
45913
- DOCUMENTS: "documents"
45919
+ DOCUMENTS: "documents",
45920
+ // Memory node → nexus symbol/file (T645)
45921
+ CODE_REFERENCE: "code_reference"
45914
45922
  };
45915
45923
  }
45916
45924
  });
@@ -141320,6 +141328,495 @@ function registerNexusCommand(program) {
141320
141328
  );
141321
141329
  } else {
141322
141330
  process.stderr.write(`[nexus] Error: ${msg}
141331
+ `);
141332
+ }
141333
+ process.exitCode = 1;
141334
+ }
141335
+ });
141336
+ projects.command("scan").description(
141337
+ "Walk filesystem roots to discover .cleo/ directories not registered in the global nexus registry"
141338
+ ).option(
141339
+ "--roots <paths>",
141340
+ "Comma-separated search roots (default: ~/code,~/projects,/mnt/projects)"
141341
+ ).option("--max-depth <n>", "Maximum directory traversal depth (default: 4)", parseInt, 4).option("--auto-register", "Register all discovered unregistered projects automatically").option("--include-existing", "Also report projects that are already registered").option("--json", "Output as JSON (LAFS envelope format)").action(async (opts) => {
141342
+ const startTime = Date.now();
141343
+ const jsonOutput = !!opts["json"];
141344
+ const autoRegister = !!opts["autoRegister"];
141345
+ const includeExisting = !!opts["includeExisting"];
141346
+ const maxDepth = Math.max(1, Math.min(opts["maxDepth"] ?? 4, 20));
141347
+ const { homedir: homedir8 } = await import("node:os");
141348
+ const home = homedir8();
141349
+ const defaultRoots = [path10.join(home, "code"), path10.join(home, "projects"), "/mnt/projects"];
141350
+ const rawRoots = typeof opts["roots"] === "string" && opts["roots"].trim().length > 0 ? opts["roots"].split(",").map((r) => r.trim()).filter((r) => r.length > 0).map(
141351
+ (r) => r.startsWith("~") ? path10.join(home, r.slice(1)) : path10.resolve(r)
141352
+ ) : defaultRoots;
141353
+ const { existsSync: existsSync135, readdirSync: readdirSync42, statSync: statSync22 } = await import("node:fs");
141354
+ const { Dirent } = await import("node:fs");
141355
+ const roots = rawRoots.filter((r) => {
141356
+ try {
141357
+ return existsSync135(r) && statSync22(r).isDirectory();
141358
+ } catch {
141359
+ return false;
141360
+ }
141361
+ });
141362
+ if (!jsonOutput) {
141363
+ process.stdout.write(
141364
+ `[nexus] Scanning ${roots.length} root(s) up to depth ${maxDepth}:
141365
+ ` + roots.map((r) => ` ${r}`).join("\n") + "\n"
141366
+ );
141367
+ }
141368
+ const SKIP_DIRS = /* @__PURE__ */ new Set([
141369
+ "node_modules",
141370
+ ".git",
141371
+ "target",
141372
+ // Rust build
141373
+ "dist",
141374
+ "build",
141375
+ ".svelte-kit",
141376
+ ".next",
141377
+ ".cache",
141378
+ "coverage",
141379
+ ".turbo",
141380
+ ".nx",
141381
+ "__pycache__",
141382
+ ".venv",
141383
+ "venv",
141384
+ ".tox",
141385
+ "vendor"
141386
+ ]);
141387
+ function getDevice(p2) {
141388
+ try {
141389
+ return statSync22(p2).dev;
141390
+ } catch {
141391
+ return -1;
141392
+ }
141393
+ }
141394
+ function walkForCleo(dir, depth, rootDev) {
141395
+ if (depth > maxDepth) return [];
141396
+ let entries;
141397
+ try {
141398
+ entries = readdirSync42(dir, { withFileTypes: true });
141399
+ } catch {
141400
+ return [];
141401
+ }
141402
+ const found = [];
141403
+ for (const entry of entries) {
141404
+ if (!entry.isDirectory()) continue;
141405
+ if (entry.isSymbolicLink()) continue;
141406
+ const fullPath = path10.join(dir, entry.name);
141407
+ if (entry.name === ".cleo") {
141408
+ found.push(dir);
141409
+ continue;
141410
+ }
141411
+ if (SKIP_DIRS.has(entry.name)) continue;
141412
+ const childDev = getDevice(fullPath);
141413
+ if (childDev !== rootDev && childDev !== -1) continue;
141414
+ const nested = walkForCleo(fullPath, depth + 1, rootDev);
141415
+ for (const n of nested) found.push(n);
141416
+ }
141417
+ return found;
141418
+ }
141419
+ const allCandidates = [];
141420
+ for (const root of roots) {
141421
+ const rootDev = getDevice(root);
141422
+ const found = walkForCleo(root, 0, rootDev);
141423
+ for (const f2 of found) allCandidates.push(f2);
141424
+ }
141425
+ const candidates = [...new Set(allCandidates)];
141426
+ let registeredPaths = /* @__PURE__ */ new Set();
141427
+ try {
141428
+ const { nexusList: listProjects } = await Promise.resolve().then(() => (init_internal(), internal_exports));
141429
+ const projects2 = await listProjects();
141430
+ for (const p2 of projects2) {
141431
+ registeredPaths.add(path10.resolve(p2.path));
141432
+ }
141433
+ } catch {
141434
+ registeredPaths = /* @__PURE__ */ new Set();
141435
+ }
141436
+ const unregistered = [];
141437
+ const registered = [];
141438
+ for (const candidate of candidates) {
141439
+ const resolved = path10.resolve(candidate);
141440
+ if (registeredPaths.has(resolved)) {
141441
+ registered.push(resolved);
141442
+ } else {
141443
+ unregistered.push(resolved);
141444
+ }
141445
+ }
141446
+ const tally = {
141447
+ total: candidates.length,
141448
+ unregistered: unregistered.length,
141449
+ registered: registered.length
141450
+ };
141451
+ const autoRegistered = [];
141452
+ const autoRegisterErrors = [];
141453
+ if (autoRegister && unregistered.length > 0) {
141454
+ const { nexusRegister: doRegister } = await Promise.resolve().then(() => (init_internal(), internal_exports));
141455
+ for (const projectPath of unregistered) {
141456
+ try {
141457
+ await doRegister(projectPath);
141458
+ autoRegistered.push(projectPath);
141459
+ } catch (err) {
141460
+ autoRegisterErrors.push({
141461
+ path: projectPath,
141462
+ error: err instanceof Error ? err.message : String(err)
141463
+ });
141464
+ }
141465
+ }
141466
+ }
141467
+ try {
141468
+ const { getNexusDb: getNexusDb2 } = await import("@cleocode/core/store/nexus-sqlite");
141469
+ const { nexusAuditLog: auditTable } = await import("@cleocode/core/store/nexus-schema");
141470
+ const { randomUUID: randomUUID15 } = await import("node:crypto");
141471
+ const db = await getNexusDb2();
141472
+ await db.insert(auditTable).values({
141473
+ id: randomUUID15(),
141474
+ action: "projects.scan",
141475
+ domain: "nexus",
141476
+ operation: "projects.scan",
141477
+ success: 1,
141478
+ detailsJson: JSON.stringify({
141479
+ roots,
141480
+ found: candidates.length,
141481
+ unregistered: unregistered.length,
141482
+ registered: registered.length,
141483
+ autoRegistered: autoRegistered.length
141484
+ })
141485
+ });
141486
+ } catch {
141487
+ }
141488
+ const durationMs = Date.now() - startTime;
141489
+ if (jsonOutput) {
141490
+ const data = {
141491
+ roots,
141492
+ unregistered,
141493
+ tally
141494
+ };
141495
+ if (includeExisting) data["registered"] = registered;
141496
+ if (autoRegister) {
141497
+ data["autoRegistered"] = autoRegistered;
141498
+ data["autoRegisterErrors"] = autoRegisterErrors;
141499
+ }
141500
+ process.stdout.write(
141501
+ JSON.stringify(
141502
+ {
141503
+ success: true,
141504
+ data,
141505
+ meta: {
141506
+ operation: "nexus.projects.scan",
141507
+ duration_ms: durationMs,
141508
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
141509
+ }
141510
+ },
141511
+ null,
141512
+ 2
141513
+ ) + "\n"
141514
+ );
141515
+ } else {
141516
+ process.stdout.write(
141517
+ `
141518
+ [nexus] Scan complete \u2014 ${tally.total} project(s) found (${tally.unregistered} unregistered, ${tally.registered} registered)
141519
+ `
141520
+ );
141521
+ if (unregistered.length > 0) {
141522
+ process.stdout.write("\n Unregistered:\n");
141523
+ for (const p2 of unregistered) {
141524
+ process.stdout.write(` ${p2}
141525
+ `);
141526
+ }
141527
+ if (!autoRegister) {
141528
+ process.stdout.write(
141529
+ "\n Tip: run with --auto-register to register all of the above.\n"
141530
+ );
141531
+ }
141532
+ }
141533
+ if (includeExisting && registered.length > 0) {
141534
+ process.stdout.write("\n Already registered:\n");
141535
+ for (const p2 of registered) {
141536
+ process.stdout.write(` ${p2}
141537
+ `);
141538
+ }
141539
+ }
141540
+ if (autoRegister) {
141541
+ process.stdout.write(
141542
+ `
141543
+ Auto-registered: ${autoRegistered.length} project(s)` + (autoRegisterErrors.length > 0 ? `, ${autoRegisterErrors.length} failed` : "") + "\n"
141544
+ );
141545
+ for (const e of autoRegisterErrors) {
141546
+ process.stdout.write(` FAILED ${e.path}: ${e.error}
141547
+ `);
141548
+ }
141549
+ }
141550
+ }
141551
+ });
141552
+ projects.command("clean").description(
141553
+ "Bulk purge project_registry rows matching path criteria (requires at least one filter flag)"
141554
+ ).option("--dry-run", "List matching projects without deleting anything").option("--pattern <regex>", "JS regex matched against project_path").option("--include-temp", "Preset: match paths containing a .temp/ segment").option(
141555
+ "--include-tests",
141556
+ "Preset: match paths containing tmp/test/fixture/scratch/sandbox segments"
141557
+ ).option("--unhealthy", 'Also match rows where health_status is "unhealthy"').option("--never-indexed", "Also match rows where last_indexed IS NULL").option("--yes", "Skip confirmation prompt (still shows preview)").option("--json", "Output as JSON (LAFS envelope format)").action(async (opts) => {
141558
+ const startTime = Date.now();
141559
+ const jsonOutput = !!opts["json"];
141560
+ const dryRun = !!opts["dryRun"];
141561
+ const skipPrompt = !!opts["yes"];
141562
+ const patternRaw = opts["pattern"];
141563
+ const includeTemp = !!opts["includeTemp"];
141564
+ const includeTests = !!opts["includeTests"];
141565
+ const matchUnhealthy = !!opts["unhealthy"];
141566
+ const matchNeverIndexed = !!opts["neverIndexed"];
141567
+ const hasCriteria = patternRaw !== void 0 || includeTemp || includeTests || matchUnhealthy || matchNeverIndexed;
141568
+ if (!hasCriteria) {
141569
+ const errMsg = "No filter criteria provided. Refusing to purge all projects without explicit criteria.\nUse at least one of: --pattern <regex>, --include-temp, --include-tests, --unhealthy, --never-indexed";
141570
+ if (jsonOutput) {
141571
+ process.stdout.write(
141572
+ JSON.stringify(
141573
+ {
141574
+ success: false,
141575
+ error: { code: "E_NO_CRITERIA", message: errMsg },
141576
+ meta: {
141577
+ operation: "nexus.projects.clean",
141578
+ duration_ms: Date.now() - startTime,
141579
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
141580
+ }
141581
+ },
141582
+ null,
141583
+ 2
141584
+ ) + "\n"
141585
+ );
141586
+ } else {
141587
+ process.stderr.write(`[nexus] Error: ${errMsg}
141588
+ `);
141589
+ }
141590
+ process.exitCode = 6;
141591
+ return;
141592
+ }
141593
+ let patternRegex = null;
141594
+ if (patternRaw !== void 0) {
141595
+ try {
141596
+ patternRegex = new RegExp(patternRaw);
141597
+ } catch (err) {
141598
+ const msg = `Invalid --pattern regex: ${err instanceof Error ? err.message : String(err)}`;
141599
+ if (jsonOutput) {
141600
+ process.stdout.write(
141601
+ JSON.stringify(
141602
+ {
141603
+ success: false,
141604
+ error: { code: "E_INVALID_PATTERN", message: msg },
141605
+ meta: {
141606
+ operation: "nexus.projects.clean",
141607
+ duration_ms: Date.now() - startTime,
141608
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
141609
+ }
141610
+ },
141611
+ null,
141612
+ 2
141613
+ ) + "\n"
141614
+ );
141615
+ } else {
141616
+ process.stderr.write(`[nexus] Error: ${msg}
141617
+ `);
141618
+ }
141619
+ process.exitCode = 6;
141620
+ return;
141621
+ }
141622
+ }
141623
+ const TEMP_RE = /(^|\/)\.temp(\/|$)/;
141624
+ const TESTS_RE = /(^|\/)(tmp|test|fixture|scratch|sandbox)(\/|$)/;
141625
+ function matchesCriteria(projectPath, healthStatus, lastIndexed) {
141626
+ if (patternRegex?.test(projectPath)) return true;
141627
+ if (includeTemp && TEMP_RE.test(projectPath)) return true;
141628
+ if (includeTests && TESTS_RE.test(projectPath)) return true;
141629
+ if (matchUnhealthy && healthStatus === "unhealthy") return true;
141630
+ if (matchNeverIndexed && lastIndexed === null) return true;
141631
+ return false;
141632
+ }
141633
+ try {
141634
+ const { getNexusDb: getNexusDb2 } = await import("@cleocode/core/store/nexus-sqlite");
141635
+ const { projectRegistry: regTable, nexusAuditLog: auditTable } = await import("@cleocode/core/store/nexus-schema");
141636
+ const { randomUUID: randomUUID15 } = await import("node:crypto");
141637
+ const { inArray: inArray8 } = await import("drizzle-orm");
141638
+ const db = await getNexusDb2();
141639
+ const allRows = await db.select({
141640
+ projectId: regTable.projectId,
141641
+ projectPath: regTable.projectPath,
141642
+ healthStatus: regTable.healthStatus,
141643
+ lastIndexed: regTable.lastIndexed
141644
+ }).from(regTable);
141645
+ const matches = allRows.filter(
141646
+ (row) => matchesCriteria(row.projectPath, row.healthStatus, row.lastIndexed)
141647
+ );
141648
+ const totalCount = allRows.length;
141649
+ const matchCount = matches.length;
141650
+ const samplePaths = matches.slice(0, 10).map((r) => r.projectPath);
141651
+ if (!jsonOutput) {
141652
+ process.stdout.write(
141653
+ `[nexus] Clean preview \u2014 ${matchCount} project(s) of ${totalCount} total match criteria:
141654
+ `
141655
+ );
141656
+ if (matchCount === 0) {
141657
+ process.stdout.write(" (no matches)\n");
141658
+ } else {
141659
+ for (const p2 of samplePaths) {
141660
+ process.stdout.write(` ${p2}
141661
+ `);
141662
+ }
141663
+ if (matchCount > 10) {
141664
+ process.stdout.write(` ... and ${matchCount - 10} more
141665
+ `);
141666
+ }
141667
+ }
141668
+ }
141669
+ if (matchCount === 0) {
141670
+ const durationMs2 = Date.now() - startTime;
141671
+ if (jsonOutput) {
141672
+ process.stdout.write(
141673
+ JSON.stringify(
141674
+ {
141675
+ success: true,
141676
+ data: {
141677
+ dryRun,
141678
+ matched: 0,
141679
+ purged: 0,
141680
+ remaining: totalCount,
141681
+ sample: []
141682
+ },
141683
+ meta: {
141684
+ operation: "nexus.projects.clean",
141685
+ duration_ms: durationMs2,
141686
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
141687
+ }
141688
+ },
141689
+ null,
141690
+ 2
141691
+ ) + "\n"
141692
+ );
141693
+ }
141694
+ return;
141695
+ }
141696
+ if (dryRun) {
141697
+ const durationMs2 = Date.now() - startTime;
141698
+ if (jsonOutput) {
141699
+ process.stdout.write(
141700
+ JSON.stringify(
141701
+ {
141702
+ success: true,
141703
+ data: {
141704
+ dryRun: true,
141705
+ matched: matchCount,
141706
+ purged: 0,
141707
+ remaining: totalCount,
141708
+ sample: samplePaths
141709
+ },
141710
+ meta: {
141711
+ operation: "nexus.projects.clean",
141712
+ duration_ms: durationMs2,
141713
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
141714
+ }
141715
+ },
141716
+ null,
141717
+ 2
141718
+ ) + "\n"
141719
+ );
141720
+ } else {
141721
+ process.stdout.write(
141722
+ `[nexus] Dry-run \u2014 ${matchCount} project(s) would be purged. Rerun without --dry-run to delete.
141723
+ `
141724
+ );
141725
+ }
141726
+ return;
141727
+ }
141728
+ if (!skipPrompt) {
141729
+ const { createInterface: createInterface3 } = await import("node:readline");
141730
+ const rl = createInterface3({ input: process.stdin, output: process.stdout });
141731
+ const confirmed = await new Promise((resolve17) => {
141732
+ rl.question(
141733
+ `
141734
+ [nexus] Delete ${matchCount} project(s) from the registry? [y/N] `,
141735
+ (answer) => {
141736
+ rl.close();
141737
+ resolve17(answer.trim().toLowerCase() === "y");
141738
+ }
141739
+ );
141740
+ });
141741
+ if (!confirmed) {
141742
+ process.stdout.write("[nexus] Aborted \u2014 no projects deleted.\n");
141743
+ return;
141744
+ }
141745
+ }
141746
+ const idsToDelete = matches.map((r) => r.projectId);
141747
+ await db.delete(regTable).where(inArray8(regTable.projectId, idsToDelete));
141748
+ const remaining = totalCount - matchCount;
141749
+ try {
141750
+ await db.insert(auditTable).values({
141751
+ id: randomUUID15(),
141752
+ action: "projects.clean",
141753
+ domain: "nexus",
141754
+ operation: "projects.clean",
141755
+ success: 1,
141756
+ detailsJson: JSON.stringify({
141757
+ pattern: patternRaw ?? null,
141758
+ presets: {
141759
+ includeTemp,
141760
+ includeTests,
141761
+ matchUnhealthy,
141762
+ matchNeverIndexed
141763
+ },
141764
+ count: matchCount,
141765
+ sample: samplePaths
141766
+ })
141767
+ });
141768
+ } catch {
141769
+ }
141770
+ const durationMs = Date.now() - startTime;
141771
+ if (jsonOutput) {
141772
+ process.stdout.write(
141773
+ JSON.stringify(
141774
+ {
141775
+ success: true,
141776
+ data: {
141777
+ dryRun: false,
141778
+ matched: matchCount,
141779
+ purged: matchCount,
141780
+ remaining,
141781
+ sample: samplePaths
141782
+ },
141783
+ meta: {
141784
+ operation: "nexus.projects.clean",
141785
+ duration_ms: durationMs,
141786
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
141787
+ }
141788
+ },
141789
+ null,
141790
+ 2
141791
+ ) + "\n"
141792
+ );
141793
+ } else {
141794
+ process.stdout.write(
141795
+ `[nexus] Purged ${matchCount} project(s). ${remaining} project(s) remaining in registry.
141796
+ `
141797
+ );
141798
+ }
141799
+ } catch (err) {
141800
+ const msg = err instanceof Error ? err.message : String(err);
141801
+ const durationMs = Date.now() - startTime;
141802
+ if (jsonOutput) {
141803
+ process.stdout.write(
141804
+ JSON.stringify(
141805
+ {
141806
+ success: false,
141807
+ error: { code: "E_CLEAN_FAILED", message: msg },
141808
+ meta: {
141809
+ operation: "nexus.projects.clean",
141810
+ duration_ms: durationMs,
141811
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
141812
+ }
141813
+ },
141814
+ null,
141815
+ 2
141816
+ ) + "\n"
141817
+ );
141818
+ } else {
141819
+ process.stderr.write(`[nexus] Error: ${msg}
141323
141820
  `);
141324
141821
  }
141325
141822
  process.exitCode = 1;