@cleocode/cleo 2026.4.51 → 2026.4.52

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
@@ -9600,11 +9600,14 @@ var init_brain_schema = __esm({
9600
9600
  source: text("source").notNull(),
9601
9601
  /** Estimated tokens consumed by this retrieval. */
9602
9602
  tokensUsed: integer("tokens_used"),
9603
+ /** Session ID (soft FK to tasks.db sessions). Enables grouping retrievals by session for STDP analysis. */
9604
+ sessionId: text("session_id"),
9603
9605
  createdAt: text("created_at").notNull().default(sql2`(datetime('now'))`)
9604
9606
  },
9605
9607
  (table) => [
9606
9608
  index("idx_retrieval_log_created").on(table.createdAt),
9607
- index("idx_retrieval_log_source").on(table.source)
9609
+ index("idx_retrieval_log_source").on(table.source),
9610
+ index("idx_retrieval_log_session").on(table.sessionId)
9608
9611
  ]
9609
9612
  );
9610
9613
  brainPlasticityEvents = sqliteTable(
@@ -14952,7 +14955,7 @@ function clearEmbeddingProvider() {
14952
14955
  currentProvider = null;
14953
14956
  }
14954
14957
  async function embedText(text3) {
14955
- if (!currentProvider || !currentProvider.isAvailable()) return null;
14958
+ if (!currentProvider?.isAvailable()) return null;
14956
14959
  return currentProvider.embed(text3);
14957
14960
  }
14958
14961
  function isEmbeddingAvailable() {
@@ -22823,7 +22826,7 @@ function parseIdPrefix(id) {
22823
22826
  return null;
22824
22827
  }
22825
22828
  async function searchSimilar(query, projectRoot, limit) {
22826
- if (!query || !query.trim()) return [];
22829
+ if (!query?.trim()) return [];
22827
22830
  if (!isEmbeddingAvailable()) return [];
22828
22831
  const maxResults = limit ?? 10;
22829
22832
  const queryVector = await embedText(query);
@@ -50370,7 +50373,7 @@ async function computeLastSession(projectRoot, scopeFilter) {
50370
50373
  const accessor = await getAccessor(projectRoot);
50371
50374
  const allSessions = await accessor.loadSessions();
50372
50375
  const session = allSessions.find((s3) => s3.id === sessionId);
50373
- if (!session || !session.endedAt) return null;
50376
+ if (!session?.endedAt) return null;
50374
50377
  let duration3 = 0;
50375
50378
  if (session.startedAt) {
50376
50379
  duration3 = Math.round(
@@ -51557,10 +51560,17 @@ async function searchBrainCompact(projectRoot, params) {
51557
51560
  setImmediate(() => {
51558
51561
  incrementCitationCounts(projectRoot, returnedIds).catch(() => {
51559
51562
  });
51560
- logRetrieval(projectRoot, query, returnedIds, "find-rrf", results2.length * 50).catch(
51561
- () => {
51562
- }
51563
- );
51563
+ getCurrentSessionId(projectRoot).then((sessionId) => {
51564
+ return logRetrieval(
51565
+ projectRoot,
51566
+ query,
51567
+ returnedIds,
51568
+ "find-rrf",
51569
+ results2.length * 50,
51570
+ sessionId
51571
+ );
51572
+ }).catch(() => {
51573
+ });
51564
51574
  });
51565
51575
  }
51566
51576
  return { results: results2, total: results2.length, tokensEstimated: results2.length * 50 };
@@ -51623,7 +51633,16 @@ async function searchBrainCompact(projectRoot, params) {
51623
51633
  setImmediate(() => {
51624
51634
  incrementCitationCounts(projectRoot, returnedIds).catch(() => {
51625
51635
  });
51626
- logRetrieval(projectRoot, query, returnedIds, "find", results.length * 50).catch(() => {
51636
+ getCurrentSessionId(projectRoot).then((sessionId) => {
51637
+ return logRetrieval(
51638
+ projectRoot,
51639
+ query,
51640
+ returnedIds,
51641
+ "find",
51642
+ results.length * 50,
51643
+ sessionId
51644
+ );
51645
+ }).catch(() => {
51627
51646
  });
51628
51647
  });
51629
51648
  }
@@ -51811,13 +51830,16 @@ async function fetchBrainEntries(projectRoot, params) {
51811
51830
  setImmediate(() => {
51812
51831
  incrementCitationCounts(projectRoot, fetchedIds).catch(() => {
51813
51832
  });
51814
- logRetrieval(
51815
- projectRoot,
51816
- fetchedIds.join(","),
51817
- fetchedIds,
51818
- "fetch",
51819
- results.length * 500
51820
- ).catch(() => {
51833
+ getCurrentSessionId(projectRoot).then((sessionId) => {
51834
+ return logRetrieval(
51835
+ projectRoot,
51836
+ fetchedIds.join(","),
51837
+ fetchedIds,
51838
+ "fetch",
51839
+ results.length * 500,
51840
+ sessionId
51841
+ );
51842
+ }).catch(() => {
51821
51843
  });
51822
51844
  });
51823
51845
  }
@@ -52235,6 +52257,15 @@ async function retrieveWithBudget(projectRoot, query, tokenBudget = 500, options
52235
52257
  excluded
52236
52258
  };
52237
52259
  }
52260
+ async function getCurrentSessionId(projectRoot) {
52261
+ try {
52262
+ const { sessionStatus: sessionStatus3 } = await Promise.resolve().then(() => (init_sessions2(), sessions_exports2));
52263
+ const session = await sessionStatus3(projectRoot);
52264
+ return session?.id;
52265
+ } catch {
52266
+ return void 0;
52267
+ }
52268
+ }
52238
52269
  async function incrementCitationCounts(projectRoot, ids) {
52239
52270
  if (ids.length === 0) return;
52240
52271
  const { getBrainDb: getBrainDb2, getBrainNativeDb: getBrainNativeDb2 } = await Promise.resolve().then(() => (init_brain_sqlite(), brain_sqlite_exports));
@@ -52261,13 +52292,13 @@ async function incrementCitationCounts(projectRoot, ids) {
52261
52292
  }
52262
52293
  }
52263
52294
  }
52264
- async function logRetrieval(projectRoot, query, entryIds, source, tokensUsed) {
52295
+ async function logRetrieval(projectRoot, query, entryIds, source, tokensUsed, sessionId) {
52265
52296
  if (entryIds.length === 0) return;
52266
52297
  const { getBrainDb: getBrainDb2, getBrainNativeDb: getBrainNativeDb2 } = await Promise.resolve().then(() => (init_brain_sqlite(), brain_sqlite_exports));
52267
52298
  await getBrainDb2(projectRoot);
52268
52299
  const nativeDb = getBrainNativeDb2();
52269
52300
  if (!nativeDb) return;
52270
- const createSql = "CREATE TABLE IF NOT EXISTS brain_retrieval_log (id INTEGER PRIMARY KEY AUTOINCREMENT,query TEXT NOT NULL,entry_ids TEXT NOT NULL,entry_count INTEGER NOT NULL,source TEXT NOT NULL,tokens_used INTEGER,created_at TEXT NOT NULL DEFAULT (datetime('now')))";
52301
+ const createSql = "CREATE TABLE IF NOT EXISTS brain_retrieval_log (id INTEGER PRIMARY KEY AUTOINCREMENT,query TEXT NOT NULL,entry_ids TEXT NOT NULL,entry_count INTEGER NOT NULL,source TEXT NOT NULL,tokens_used INTEGER,session_id TEXT,created_at TEXT NOT NULL DEFAULT (datetime('now')))";
52271
52302
  try {
52272
52303
  nativeDb.prepare(createSql).run();
52273
52304
  } catch {
@@ -52275,8 +52306,15 @@ async function logRetrieval(projectRoot, query, entryIds, source, tokensUsed) {
52275
52306
  }
52276
52307
  try {
52277
52308
  nativeDb.prepare(
52278
- "INSERT INTO brain_retrieval_log (query, entry_ids, entry_count, source, tokens_used) VALUES (?, ?, ?, ?, ?)"
52279
- ).run(query, entryIds.join(","), entryIds.length, source, tokensUsed ?? null);
52309
+ "INSERT INTO brain_retrieval_log (query, entry_ids, entry_count, source, tokens_used, session_id) VALUES (?, ?, ?, ?, ?, ?)"
52310
+ ).run(
52311
+ query,
52312
+ entryIds.join(","),
52313
+ entryIds.length,
52314
+ source,
52315
+ tokensUsed ?? null,
52316
+ sessionId ?? null
52317
+ );
52280
52318
  } catch {
52281
52319
  }
52282
52320
  }
@@ -58851,7 +58889,7 @@ function prepareSpawnMulti(skillNames, tokenValues, cwd) {
58851
58889
  const skillName = skillNames[i];
58852
58890
  const isPrimary = i === 0;
58853
58891
  const skill = findSkill(skillName, cwd);
58854
- if (!skill || !skill.content) {
58892
+ if (!skill?.content) {
58855
58893
  continue;
58856
58894
  }
58857
58895
  let content = isPrimary ? skill.content : loadProgressive(skill.content);
@@ -74367,7 +74405,7 @@ async function injectProtocol(skillContent, taskId, tokenValues, cwd, tier) {
74367
74405
  }
74368
74406
  async function orchestratorSpawnSkill(taskId, skillName, tokenValues, cwd, tier) {
74369
74407
  const skill = findSkill(skillName, cwd);
74370
- if (!skill || !skill.content) {
74408
+ if (!skill?.content) {
74371
74409
  throw new CleoError(4 /* NOT_FOUND */, `Skill not found: ${skillName}`, {
74372
74410
  fix: `Check skills directory for ${skillName}/SKILL.md`
74373
74411
  });
@@ -74937,7 +74975,7 @@ async function buildPrompt(taskId, templateName = "TASK-EXECUTOR", cwd, tier) {
74937
74975
  throw new CleoError(4 /* NOT_FOUND */, `Task ${taskId} not found`);
74938
74976
  }
74939
74977
  const skill = findSkill(templateName, cwd);
74940
- if (!skill || !skill.content) {
74978
+ if (!skill?.content) {
74941
74979
  const { canonical } = mapSkillName(templateName);
74942
74980
  throw new CleoError(4 /* NOT_FOUND */, `Skill template ${templateName} not found`, {
74943
74981
  fix: `Expected at skills/${canonical}/SKILL.md`
@@ -77732,7 +77770,7 @@ async function analyzeArchive(opts, accessor) {
77732
77770
  const acc = accessor ?? await getAccessor(opts.cwd);
77733
77771
  const data = await acc.loadArchive();
77734
77772
  const reportType = opts.report ?? "summary";
77735
- if (!data || !data.archivedTasks?.length) {
77773
+ if (!data?.archivedTasks?.length) {
77736
77774
  return {
77737
77775
  report: reportType,
77738
77776
  filters: null,
@@ -85793,16 +85831,12 @@ function allEpicChildrenVerified(epicId, tasks2) {
85793
85831
  if (children.length === 0) return false;
85794
85832
  const incomplete = children.filter((t) => t.status !== "done");
85795
85833
  if (incomplete.length > 0) return false;
85796
- const unverified = children.filter(
85797
- (t) => t.status === "done" && (!t.verification || !t.verification.passed)
85798
- );
85834
+ const unverified = children.filter((t) => t.status === "done" && !t.verification?.passed);
85799
85835
  return unverified.length === 0;
85800
85836
  }
85801
85837
  function allSiblingsVerified(parentId, tasks2) {
85802
85838
  const siblings = tasks2.filter((t) => t.parentId === parentId);
85803
- const unverifiedDone = siblings.filter(
85804
- (t) => t.status === "done" && (!t.verification || !t.verification.passed)
85805
- );
85839
+ const unverifiedDone = siblings.filter((t) => t.status === "done" && !t.verification?.passed);
85806
85840
  const incomplete = siblings.filter(
85807
85841
  (t) => t.status === "pending" || t.status === "active" || t.status === "blocked"
85808
85842
  );
@@ -87208,7 +87242,7 @@ var init_init = __esm({
87208
87242
  // packages/core/src/sessions/context-alert.ts
87209
87243
  import { existsSync as existsSync109, readFileSync as readFileSync80, writeFileSync as writeFileSync20 } from "node:fs";
87210
87244
  import { join as join109 } from "node:path";
87211
- function getCurrentSessionId(cwd) {
87245
+ function getCurrentSessionId2(cwd) {
87212
87246
  if (process.env.CLEO_SESSION) return process.env.CLEO_SESSION;
87213
87247
  const sessionFile = join109(getCleoDir(cwd), ".current-session");
87214
87248
  if (existsSync109(sessionFile)) {
@@ -87242,7 +87276,7 @@ function createCliMeta(operation, duration_ms = 0) {
87242
87276
  duration_ms,
87243
87277
  timestamp: (/* @__PURE__ */ new Date()).toISOString()
87244
87278
  };
87245
- const sessionId = getCurrentSessionId();
87279
+ const sessionId = getCurrentSessionId2();
87246
87280
  if (sessionId) {
87247
87281
  meta3["sessionId"] = sessionId;
87248
87282
  }
@@ -90912,6 +90946,160 @@ var init_brain_backfill = __esm({
90912
90946
  }
90913
90947
  });
90914
90948
 
90949
+ // packages/core/src/memory/brain-export.ts
90950
+ async function exportBrainAsGexf(projectRoot) {
90951
+ const db = await getBrainDb(projectRoot);
90952
+ let nodes = [];
90953
+ let edges = [];
90954
+ try {
90955
+ nodes = await db.select({
90956
+ id: brainPageNodes.id,
90957
+ nodeType: brainPageNodes.nodeType,
90958
+ label: brainPageNodes.label,
90959
+ qualityScore: brainPageNodes.qualityScore,
90960
+ contentHash: brainPageNodes.contentHash,
90961
+ lastActivityAt: brainPageNodes.lastActivityAt,
90962
+ metadataJson: brainPageNodes.metadataJson,
90963
+ createdAt: brainPageNodes.createdAt,
90964
+ updatedAt: brainPageNodes.updatedAt
90965
+ }).from(brainPageNodes);
90966
+ } catch {
90967
+ nodes = [];
90968
+ }
90969
+ try {
90970
+ const rawEdges = await db.select({
90971
+ fromId: brainPageEdges.fromId,
90972
+ toId: brainPageEdges.toId,
90973
+ edgeType: brainPageEdges.edgeType,
90974
+ weight: brainPageEdges.weight,
90975
+ createdAt: brainPageEdges.createdAt
90976
+ }).from(brainPageEdges);
90977
+ edges = rawEdges.map((e) => ({ ...e, provenance: null }));
90978
+ } catch {
90979
+ edges = [];
90980
+ }
90981
+ const gexf = buildGexfDocument(nodes, edges);
90982
+ return {
90983
+ success: true,
90984
+ format: "gexf",
90985
+ nodeCount: nodes.length,
90986
+ edgeCount: edges.length,
90987
+ content: gexf,
90988
+ generatedAt: (/* @__PURE__ */ new Date()).toISOString()
90989
+ };
90990
+ }
90991
+ async function exportBrainAsJson(projectRoot) {
90992
+ const db = await getBrainDb(projectRoot);
90993
+ let nodes = [];
90994
+ let edges = [];
90995
+ try {
90996
+ nodes = await db.select({
90997
+ id: brainPageNodes.id,
90998
+ nodeType: brainPageNodes.nodeType,
90999
+ label: brainPageNodes.label,
91000
+ qualityScore: brainPageNodes.qualityScore,
91001
+ contentHash: brainPageNodes.contentHash,
91002
+ lastActivityAt: brainPageNodes.lastActivityAt,
91003
+ metadataJson: brainPageNodes.metadataJson,
91004
+ createdAt: brainPageNodes.createdAt,
91005
+ updatedAt: brainPageNodes.updatedAt
91006
+ }).from(brainPageNodes);
91007
+ } catch {
91008
+ nodes = [];
91009
+ }
91010
+ try {
91011
+ const rawEdges = await db.select({
91012
+ fromId: brainPageEdges.fromId,
91013
+ toId: brainPageEdges.toId,
91014
+ edgeType: brainPageEdges.edgeType,
91015
+ weight: brainPageEdges.weight,
91016
+ createdAt: brainPageEdges.createdAt
91017
+ }).from(brainPageEdges);
91018
+ edges = rawEdges.map((e) => ({ ...e, provenance: null }));
91019
+ } catch {
91020
+ edges = [];
91021
+ }
91022
+ return {
91023
+ success: true,
91024
+ format: "json",
91025
+ nodeCount: nodes.length,
91026
+ edgeCount: edges.length,
91027
+ nodes,
91028
+ edges,
91029
+ generatedAt: (/* @__PURE__ */ new Date()).toISOString()
91030
+ };
91031
+ }
91032
+ function buildGexfDocument(nodes, edges) {
91033
+ const lines = [
91034
+ '<?xml version="1.0" encoding="UTF-8"?>',
91035
+ '<gexf xmlns="http://www.gexf.net/1.3draft" version="1.3">',
91036
+ ' <meta lastmodifieddate="' + (/* @__PURE__ */ new Date()).toISOString() + '">',
91037
+ " <creator>CLEO Brain Export (T626-M6)</creator>",
91038
+ " <description>Living brain knowledge graph (brain_page_nodes + brain_page_edges)</description>",
91039
+ " </meta>",
91040
+ ' <graph mode="static" defaultedgetype="directed">'
91041
+ ];
91042
+ lines.push(' <attributes class="node">');
91043
+ lines.push(' <attribute id="node_type" title="Node Type" type="string"/>');
91044
+ lines.push(' <attribute id="quality_score" title="Quality Score" type="double"/>');
91045
+ lines.push(' <attribute id="content_hash" title="Content Hash" type="string"/>');
91046
+ lines.push(' <attribute id="last_activity_at" title="Last Activity" type="string"/>');
91047
+ lines.push(' <attribute id="created_at" title="Created At" type="string"/>');
91048
+ lines.push(" </attributes>");
91049
+ lines.push(' <attributes class="edge">');
91050
+ lines.push(' <attribute id="edge_type" title="Edge Type" type="string"/>');
91051
+ lines.push(' <attribute id="provenance" title="Provenance" type="string"/>');
91052
+ lines.push(' <attribute id="created_at" title="Created At" type="string"/>');
91053
+ lines.push(" </attributes>");
91054
+ lines.push(" <nodes>");
91055
+ for (const node of nodes) {
91056
+ lines.push(` <node id="${escapeXml(node.id)}" label="${escapeXml(node.label)}">`);
91057
+ lines.push(" <attvalues>");
91058
+ lines.push(` <attvalue for="node_type" value="${escapeXml(node.nodeType)}"/>`);
91059
+ lines.push(` <attvalue for="quality_score" value="${node.qualityScore ?? 0.5}"/>`);
91060
+ if (node.contentHash) {
91061
+ lines.push(` <attvalue for="content_hash" value="${escapeXml(node.contentHash)}"/>`);
91062
+ }
91063
+ lines.push(
91064
+ ` <attvalue for="last_activity_at" value="${escapeXml(node.lastActivityAt)}"/>`
91065
+ );
91066
+ lines.push(` <attvalue for="created_at" value="${escapeXml(node.createdAt)}"/>`);
91067
+ lines.push(" </attvalues>");
91068
+ lines.push(" </node>");
91069
+ }
91070
+ lines.push(" </nodes>");
91071
+ lines.push(" <edges>");
91072
+ for (let i = 0; i < edges.length; i++) {
91073
+ const edge = edges[i];
91074
+ const weight = edge.weight ?? 1;
91075
+ lines.push(
91076
+ ` <edge id="${i}" source="${escapeXml(edge.fromId)}" target="${escapeXml(edge.toId)}" weight="${weight}">`
91077
+ );
91078
+ lines.push(" <attvalues>");
91079
+ lines.push(` <attvalue for="edge_type" value="${escapeXml(edge.edgeType)}"/>`);
91080
+ if (edge.provenance) {
91081
+ lines.push(` <attvalue for="provenance" value="${escapeXml(edge.provenance)}"/>`);
91082
+ }
91083
+ lines.push(` <attvalue for="created_at" value="${escapeXml(edge.createdAt)}"/>`);
91084
+ lines.push(" </attvalues>");
91085
+ lines.push(" </edge>");
91086
+ }
91087
+ lines.push(" </edges>");
91088
+ lines.push(" </graph>");
91089
+ lines.push("</gexf>");
91090
+ return lines.join("\n");
91091
+ }
91092
+ function escapeXml(text3) {
91093
+ return text3.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&apos;");
91094
+ }
91095
+ var init_brain_export = __esm({
91096
+ "packages/core/src/memory/brain-export.ts"() {
91097
+ "use strict";
91098
+ init_brain_schema();
91099
+ init_brain_sqlite();
91100
+ }
91101
+ });
91102
+
90915
91103
  // packages/core/src/memory/brain-maintenance.ts
90916
91104
  async function runBrainMaintenance(projectRoot, options) {
90917
91105
  const {
@@ -91321,7 +91509,7 @@ Use --source <path> to specify a custom location.`
91321
91509
  }
91322
91510
  try {
91323
91511
  for (const row of batch) {
91324
- if (!row.learned || !row.learned.trim()) {
91512
+ if (!row.learned?.trim()) {
91325
91513
  continue;
91326
91514
  }
91327
91515
  const learnId = `CML-${row.id}`;
@@ -98701,7 +98889,7 @@ function measureDependencyDepth(taskId, taskMap, visited = /* @__PURE__ */ new S
98701
98889
  if (visited.has(taskId)) return 0;
98702
98890
  visited.add(taskId);
98703
98891
  const task = taskMap.get(taskId);
98704
- if (!task || !task.depends || task.depends.length === 0) return 0;
98892
+ if (!task?.depends || task.depends.length === 0) return 0;
98705
98893
  let maxDepth = 0;
98706
98894
  for (const depId of task.depends) {
98707
98895
  const depth = 1 + measureDependencyDepth(depId, taskMap, visited);
@@ -99055,7 +99243,7 @@ async function coreTaskUnarchive(projectRoot, taskId, params) {
99055
99243
  throw new Error(`Task '${taskId}' already exists in active tasks`);
99056
99244
  }
99057
99245
  const archive = await accessor.loadArchive();
99058
- if (!archive || !archive.archivedTasks) {
99246
+ if (!archive?.archivedTasks) {
99059
99247
  throw new Error("No archive file found");
99060
99248
  }
99061
99249
  const taskIndex = archive.archivedTasks.findIndex((t) => t.id === taskId);
@@ -113440,6 +113628,8 @@ __export(internal_exports, {
113440
113628
  ensureSqliteDb: () => ensureSqliteDb,
113441
113629
  estimateContext: () => estimateContext,
113442
113630
  executeTransfer: () => executeTransfer,
113631
+ exportBrainAsGexf: () => exportBrainAsGexf,
113632
+ exportBrainAsJson: () => exportBrainAsJson,
113443
113633
  exportSnapshot: () => exportSnapshot,
113444
113634
  exportTasks: () => exportTasks,
113445
113635
  exportTasksPackage: () => exportTasksPackage,
@@ -113509,7 +113699,7 @@ __export(internal_exports, {
113509
113699
  getConfigValue: () => getConfigValue,
113510
113700
  getContextDrift: () => getContextDrift,
113511
113701
  getCriticalPath: () => getCriticalPath,
113512
- getCurrentSessionId: () => getCurrentSessionId,
113702
+ getCurrentSessionId: () => getCurrentSessionId2,
113513
113703
  getDashboard: () => getDashboard,
113514
113704
  getDb: () => getDb,
113515
113705
  getDecisionLog: () => getDecisionLog,
@@ -114017,6 +114207,7 @@ var init_internal = __esm({
114017
114207
  init_stages();
114018
114208
  init_tessera_engine();
114019
114209
  init_brain_backfill();
114210
+ init_brain_export();
114020
114211
  init_brain_lifecycle();
114021
114212
  init_brain_maintenance();
114022
114213
  init_brain_purge();
@@ -136332,6 +136523,47 @@ Recent Events (newest first, limit=${limit})`);
136332
136523
  process.exit(1);
136333
136524
  }
136334
136525
  });
136526
+ brain.command("export").description("Export brain graph as GEXF (Gephi) or JSON format").option(
136527
+ "--format <format>",
136528
+ "Export format: gexf (Gephi standard) or json (flat arrays)",
136529
+ "gexf"
136530
+ ).option("--output <file>", "Write to file instead of stdout (optional)").action(async (opts) => {
136531
+ const root = getProjectRoot();
136532
+ const format = opts.format ?? "gexf";
136533
+ if (format !== "gexf" && format !== "json") {
136534
+ console.error(`Invalid format: ${format}. Use 'gexf' or 'json'.`);
136535
+ process.exit(1);
136536
+ }
136537
+ try {
136538
+ let content;
136539
+ let nodeCount;
136540
+ let edgeCount;
136541
+ if (format === "gexf") {
136542
+ const result = await exportBrainAsGexf(root);
136543
+ content = result.content;
136544
+ nodeCount = result.nodeCount;
136545
+ edgeCount = result.edgeCount;
136546
+ } else {
136547
+ const result = await exportBrainAsJson(root);
136548
+ content = JSON.stringify(result, null, 2);
136549
+ nodeCount = result.nodeCount;
136550
+ edgeCount = result.edgeCount;
136551
+ }
136552
+ if (opts.output) {
136553
+ const fs11 = await import("node:fs");
136554
+ fs11.writeFileSync(opts.output, content, "utf-8");
136555
+ console.log(
136556
+ `Exported to ${opts.output}: ${nodeCount} nodes, ${edgeCount} edges (${format.toUpperCase()})`
136557
+ );
136558
+ } else {
136559
+ console.log(content);
136560
+ }
136561
+ } catch (err) {
136562
+ const message = err instanceof Error ? err.message : String(err);
136563
+ console.error(`Brain export failed: ${message}`);
136564
+ process.exit(1);
136565
+ }
136566
+ });
136335
136567
  }
136336
136568
 
136337
136569
  // packages/cleo/src/cli/commands/briefing.ts
@@ -139643,6 +139875,175 @@ var NODE_KIND_PRIORITY = {
139643
139875
  file: 40,
139644
139876
  folder: 41
139645
139877
  };
139878
+ function generateGexf(nodes, relations) {
139879
+ const nodeById = /* @__PURE__ */ new Map();
139880
+ for (const n of nodes) {
139881
+ nodeById.set(String(n["id"]), n);
139882
+ }
139883
+ const kindColors = {
139884
+ function: "#3498db",
139885
+ // blue
139886
+ method: "#2980b9",
139887
+ // darker blue
139888
+ class: "#e74c3c",
139889
+ // red
139890
+ interface: "#e67e22",
139891
+ // orange
139892
+ file: "#95a5a6",
139893
+ // gray
139894
+ folder: "#34495e",
139895
+ // dark gray
139896
+ community: "#9b59b6",
139897
+ // purple
139898
+ process: "#1abc9c",
139899
+ // teal
139900
+ import: "#f39c12",
139901
+ // amber
139902
+ default: "#7f8c8d"
139903
+ // medium gray
139904
+ };
139905
+ const getNodeColor = (kind) => {
139906
+ return kindColors[kind] ?? kindColors["default"];
139907
+ };
139908
+ let gexf = '<?xml version="1.0" encoding="UTF-8"?>\n';
139909
+ gexf += '<gexf xmlns="http://www.gexf.net/1.2draft" xmlns:viz="http://www.gexf.net/1.2draft/viz" version="1.2">\n';
139910
+ gexf += ' <meta lastmodifieddate="' + (/* @__PURE__ */ new Date()).toISOString().split("T")[0] + '">\n';
139911
+ gexf += " <creator>CLEO nexus export</creator>\n";
139912
+ gexf += " <description>Code intelligence graph from CLEO nexus</description>\n";
139913
+ gexf += " </meta>\n";
139914
+ gexf += ' <graph mode="static" defaultedgetype="directed">\n';
139915
+ gexf += ' <attributes class="node">\n';
139916
+ gexf += ' <attribute id="kind" title="Node Kind" type="string" />\n';
139917
+ gexf += ' <attribute id="filePath" title="File Path" type="string" />\n';
139918
+ gexf += ' <attribute id="language" title="Language" type="string" />\n';
139919
+ gexf += ' <attribute id="startLine" title="Start Line" type="integer" />\n';
139920
+ gexf += ' <attribute id="endLine" title="End Line" type="integer" />\n';
139921
+ gexf += ' <attribute id="isExported" title="Is Exported" type="boolean" />\n';
139922
+ gexf += ' <attribute id="projectId" title="Project ID" type="string" />\n';
139923
+ gexf += " </attributes>\n";
139924
+ gexf += ' <attributes class="edge">\n';
139925
+ gexf += ' <attribute id="relationType" title="Relation Type" type="string" />\n';
139926
+ gexf += ' <attribute id="confidence" title="Confidence" type="double" />\n';
139927
+ gexf += ' <attribute id="reason" title="Reason" type="string" />\n';
139928
+ gexf += " </attributes>\n";
139929
+ gexf += " <nodes>\n";
139930
+ for (const node of nodes) {
139931
+ const nodeId = String(node["id"]).replace(/[<>"'&]/g, (c) => {
139932
+ const map2 = {
139933
+ "<": "&lt;",
139934
+ ">": "&gt;",
139935
+ '"': "&quot;",
139936
+ "'": "&apos;",
139937
+ "&": "&amp;"
139938
+ };
139939
+ return map2[c];
139940
+ });
139941
+ const label = String(node["label"] ?? node["id"]);
139942
+ const kind = String(node["kind"] ?? "unknown");
139943
+ const color = getNodeColor(kind);
139944
+ gexf += ` <node id="${nodeId}" label="${escapeXml2(label)}">
139945
+ `;
139946
+ gexf += ` <viz:color r="${hexToRgb(color).r}" g="${hexToRgb(color).g}" b="${hexToRgb(color).b}" />
139947
+ `;
139948
+ gexf += " <attvalues>\n";
139949
+ gexf += ` <attvalue id="kind" value="${escapeXml2(kind)}" />
139950
+ `;
139951
+ if (node["filePath"]) {
139952
+ gexf += ` <attvalue id="filePath" value="${escapeXml2(String(node["filePath"]))}" />
139953
+ `;
139954
+ }
139955
+ if (node["language"]) {
139956
+ gexf += ` <attvalue id="language" value="${escapeXml2(String(node["language"]))}" />
139957
+ `;
139958
+ }
139959
+ if (node["startLine"] != null) {
139960
+ gexf += ` <attvalue id="startLine" value="${node["startLine"]}" />
139961
+ `;
139962
+ }
139963
+ if (node["endLine"] != null) {
139964
+ gexf += ` <attvalue id="endLine" value="${node["endLine"]}" />
139965
+ `;
139966
+ }
139967
+ if (node["isExported"] != null) {
139968
+ gexf += ` <attvalue id="isExported" value="${node["isExported"] ? "true" : "false"}" />
139969
+ `;
139970
+ }
139971
+ if (node["projectId"]) {
139972
+ gexf += ` <attvalue id="projectId" value="${escapeXml2(String(node["projectId"]))}" />
139973
+ `;
139974
+ }
139975
+ gexf += " </attvalues>\n";
139976
+ gexf += " </node>\n";
139977
+ }
139978
+ gexf += " </nodes>\n";
139979
+ gexf += " <edges>\n";
139980
+ for (let i = 0; i < relations.length; i++) {
139981
+ const rel = relations[i];
139982
+ const sourceId = String(rel["sourceId"]).replace(/[<>"'&]/g, (c) => {
139983
+ const map2 = {
139984
+ "<": "&lt;",
139985
+ ">": "&gt;",
139986
+ '"': "&quot;",
139987
+ "'": "&apos;",
139988
+ "&": "&amp;"
139989
+ };
139990
+ return map2[c];
139991
+ });
139992
+ const targetId = String(rel["targetId"]).replace(/[<>"'&]/g, (c) => {
139993
+ const map2 = {
139994
+ "<": "&lt;",
139995
+ ">": "&gt;",
139996
+ '"': "&quot;",
139997
+ "'": "&apos;",
139998
+ "&": "&amp;"
139999
+ };
140000
+ return map2[c];
140001
+ });
140002
+ if (!nodeById.has(String(rel["sourceId"])) || !nodeById.has(String(rel["targetId"]))) {
140003
+ continue;
140004
+ }
140005
+ const confidence = typeof rel["confidence"] === "number" ? rel["confidence"] : 1;
140006
+ const relationType = String(rel["type"] ?? "unknown");
140007
+ const reason = rel["reason"] ? String(rel["reason"]) : "";
140008
+ gexf += ` <edge id="e${i}" source="${sourceId}" target="${targetId}" weight="${confidence}">
140009
+ `;
140010
+ gexf += " <attvalues>\n";
140011
+ gexf += ` <attvalue id="relationType" value="${escapeXml2(relationType)}" />
140012
+ `;
140013
+ gexf += ` <attvalue id="confidence" value="${confidence}" />
140014
+ `;
140015
+ if (reason) {
140016
+ gexf += ` <attvalue id="reason" value="${escapeXml2(reason)}" />
140017
+ `;
140018
+ }
140019
+ gexf += " </attvalues>\n";
140020
+ gexf += " </edge>\n";
140021
+ }
140022
+ gexf += " </edges>\n";
140023
+ gexf += " </graph>\n";
140024
+ gexf += "</gexf>\n";
140025
+ return gexf;
140026
+ }
140027
+ function escapeXml2(str) {
140028
+ return String(str).replace(/[<>"'&]/g, (c) => {
140029
+ const map2 = {
140030
+ "<": "&lt;",
140031
+ ">": "&gt;",
140032
+ '"': "&quot;",
140033
+ "'": "&apos;",
140034
+ "&": "&amp;"
140035
+ };
140036
+ return map2[c];
140037
+ });
140038
+ }
140039
+ function hexToRgb(hex3) {
140040
+ const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex3);
140041
+ return result ? {
140042
+ r: parseInt(result[1], 16),
140043
+ g: parseInt(result[2], 16),
140044
+ b: parseInt(result[3], 16)
140045
+ } : { r: 127, g: 140, b: 141 };
140046
+ }
139646
140047
  function sortMatchingNodes(nodes, symbolName) {
139647
140048
  const lowerSymbol = symbolName.toLowerCase();
139648
140049
  return [...nodes].sort((a, b2) => {
@@ -140877,6 +141278,84 @@ function registerNexusCommand(program) {
140877
141278
  process.exitCode = 1;
140878
141279
  }
140879
141280
  });
141281
+ nexus.command("export").description("Export nexus graph to GEXF (Gephi) or JSON format").option("--format <format>", "Output format: gexf, json", "gexf").option("--output <file>", "Output file path (stdout if omitted)").option("--project <id>", "Filter by project ID (exports all projects if omitted)").action(async (opts) => {
141282
+ const startTime = Date.now();
141283
+ const format = opts["format"] ?? "gexf";
141284
+ const outputFile = opts["output"];
141285
+ const projectFilter = opts["project"];
141286
+ try {
141287
+ const { getNexusDb: getNexusDb2, nexusSchema } = await import("@cleocode/core/store/nexus-sqlite");
141288
+ const db = await getNexusDb2();
141289
+ let allNodes = [];
141290
+ let allRelations = [];
141291
+ try {
141292
+ allNodes = db.select().from(nexusSchema.nexusNodes).all();
141293
+ allRelations = db.select().from(nexusSchema.nexusRelations).all();
141294
+ } catch {
141295
+ }
141296
+ const nodes = projectFilter ? allNodes.filter((n) => n["projectId"] === projectFilter) : allNodes;
141297
+ const relations = projectFilter ? allRelations.filter((r) => r["projectId"] === projectFilter) : allRelations;
141298
+ let output = "";
141299
+ if (format === "json") {
141300
+ output = JSON.stringify(
141301
+ {
141302
+ nodes: nodes.map((n) => ({
141303
+ id: n["id"],
141304
+ kind: n["kind"],
141305
+ label: n["label"],
141306
+ name: n["name"],
141307
+ filePath: n["filePath"],
141308
+ language: n["language"],
141309
+ isExported: n["isExported"],
141310
+ startLine: n["startLine"],
141311
+ endLine: n["endLine"],
141312
+ projectId: n["projectId"]
141313
+ })),
141314
+ edges: relations.map((r) => ({
141315
+ id: r["id"],
141316
+ source: r["sourceId"],
141317
+ target: r["targetId"],
141318
+ type: r["type"],
141319
+ confidence: r["confidence"],
141320
+ reason: r["reason"]
141321
+ }))
141322
+ },
141323
+ null,
141324
+ 2
141325
+ );
141326
+ } else if (format === "gexf") {
141327
+ output = generateGexf(nodes, relations);
141328
+ } else {
141329
+ process.stderr.write(
141330
+ `[nexus] Error: Unknown format '${format}'. Supported: gexf, json
141331
+ `
141332
+ );
141333
+ process.exitCode = 1;
141334
+ return;
141335
+ }
141336
+ if (outputFile) {
141337
+ const { writeFileSync: writeFileSync26 } = await import("node:fs");
141338
+ writeFileSync26(outputFile, output, "utf-8");
141339
+ process.stdout.write(
141340
+ `[nexus] Exported to ${outputFile} (${nodes.length} nodes, ${relations.length} edges)
141341
+ `
141342
+ );
141343
+ } else {
141344
+ process.stdout.write(output);
141345
+ if (!output.endsWith("\n")) process.stdout.write("\n");
141346
+ }
141347
+ const durationMs = Date.now() - startTime;
141348
+ if (outputFile) {
141349
+ process.stderr.write(`[nexus] Export completed in ${durationMs}ms
141350
+ `);
141351
+ }
141352
+ } catch (err) {
141353
+ const msg = err instanceof Error ? err.message : String(err);
141354
+ process.stderr.write(`[nexus] Error: ${msg}
141355
+ `);
141356
+ process.exitCode = 1;
141357
+ }
141358
+ });
140880
141359
  nexus.command("diff").description(
140881
141360
  "Compare NEXUS index state between two git commits \u2014 shows new/removed relations and broken call chains"
140882
141361
  ).option("--before <sha>", 'Git SHA or ref for the "before" snapshot (default: HEAD~1)').option("--after <sha>", 'Git SHA or ref for the "after" snapshot (default: HEAD)', "HEAD").option("--path <dir>", "Repository directory to analyze (default: cwd)").option("--json", "Output result as JSON (LAFS envelope format)").option("--project-id <id>", "Override the project ID (default: auto-detected from path)").action(async (opts) => {