@zenera/cli 1.1.24 → 1.1.26

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 (199) hide show
  1. package/README.md +65 -0
  2. package/dist/ask.js +28 -5
  3. package/dist/banner.js +2 -2
  4. package/dist/batch.d.ts +6 -0
  5. package/dist/batch.js +76 -1
  6. package/dist/catalog.d.ts +31 -1
  7. package/dist/catalog.js +140 -7
  8. package/dist/commands/index.js +2 -0
  9. package/dist/commands/inspect.d.ts +52 -2
  10. package/dist/commands/inspect.js +220 -31
  11. package/dist/commands/memory.js +156 -32
  12. package/dist/commands/meta.js +333 -24
  13. package/dist/commands/models.js +97 -12
  14. package/dist/commands/open.js +4 -4
  15. package/dist/commands/run.js +38 -4
  16. package/dist/commands/sandbox.js +148 -16
  17. package/dist/commands/view.d.ts +8 -0
  18. package/dist/commands/view.js +114 -0
  19. package/dist/engine.d.ts +8 -1
  20. package/dist/engine.js +7 -0
  21. package/dist/eventlog.d.ts +64 -0
  22. package/dist/eventlog.js +170 -0
  23. package/dist/liveness.d.ts +7 -0
  24. package/dist/liveness.js +35 -3
  25. package/dist/main.js +0 -0
  26. package/dist/meta/editor.d.ts +9 -0
  27. package/dist/meta/editor.js +43 -0
  28. package/dist/meta/finetune/analyze-brief.d.ts +26 -0
  29. package/dist/meta/finetune/analyze-brief.js +131 -0
  30. package/dist/meta/finetune/apply-brief.d.ts +23 -0
  31. package/dist/meta/finetune/apply-brief.js +247 -0
  32. package/dist/meta/finetune/brief.d.ts +45 -0
  33. package/dist/meta/finetune/brief.js +158 -0
  34. package/dist/meta/finetune/children.d.ts +8 -0
  35. package/dist/meta/finetune/children.js +76 -0
  36. package/dist/meta/finetune/command.d.ts +30 -0
  37. package/dist/meta/finetune/command.js +516 -0
  38. package/dist/meta/finetune/cost.d.ts +46 -0
  39. package/dist/meta/finetune/cost.js +105 -0
  40. package/dist/meta/finetune/dataset/anchors.d.ts +41 -0
  41. package/dist/meta/finetune/dataset/anchors.js +239 -0
  42. package/dist/meta/finetune/dataset/changes.d.ts +36 -0
  43. package/dist/meta/finetune/dataset/changes.js +319 -0
  44. package/dist/meta/finetune/dataset/command.d.ts +17 -0
  45. package/dist/meta/finetune/dataset/command.js +1011 -0
  46. package/dist/meta/finetune/dataset/drift.d.ts +33 -0
  47. package/dist/meta/finetune/dataset/drift.js +71 -0
  48. package/dist/meta/finetune/dataset/export.d.ts +26 -0
  49. package/dist/meta/finetune/dataset/export.js +51 -0
  50. package/dist/meta/finetune/dataset/index.d.ts +2 -0
  51. package/dist/meta/finetune/dataset/index.js +2 -0
  52. package/dist/meta/finetune/dataset/sample.d.ts +50 -0
  53. package/dist/meta/finetune/dataset/sample.js +197 -0
  54. package/dist/meta/finetune/dataset/schema.d.ts +33 -0
  55. package/dist/meta/finetune/dataset/schema.js +216 -0
  56. package/dist/meta/finetune/dataset/store.d.ts +35 -0
  57. package/dist/meta/finetune/dataset/store.js +177 -0
  58. package/dist/meta/finetune/dataset/types.d.ts +127 -0
  59. package/dist/meta/finetune/dataset/types.js +39 -0
  60. package/dist/meta/finetune/failure.d.ts +15 -0
  61. package/dist/meta/finetune/failure.js +26 -0
  62. package/dist/meta/finetune/feedback.d.ts +57 -0
  63. package/dist/meta/finetune/feedback.js +104 -0
  64. package/dist/meta/finetune/guard.d.ts +29 -0
  65. package/dist/meta/finetune/guard.js +104 -0
  66. package/dist/meta/finetune/improvements.d.ts +98 -0
  67. package/dist/meta/finetune/improvements.js +501 -0
  68. package/dist/meta/finetune/index.d.ts +2 -0
  69. package/dist/meta/finetune/index.js +2 -0
  70. package/dist/meta/finetune/journal.d.ts +35 -0
  71. package/dist/meta/finetune/journal.js +135 -0
  72. package/dist/meta/finetune/live.d.ts +84 -0
  73. package/dist/meta/finetune/live.js +272 -0
  74. package/dist/meta/finetune/loop.d.ts +3 -0
  75. package/dist/meta/finetune/loop.js +163 -0
  76. package/dist/meta/finetune/memories.d.ts +47 -0
  77. package/dist/meta/finetune/memories.js +154 -0
  78. package/dist/meta/finetune/recheck.d.ts +58 -0
  79. package/dist/meta/finetune/recheck.js +311 -0
  80. package/dist/meta/finetune/report.d.ts +40 -0
  81. package/dist/meta/finetune/report.js +1397 -0
  82. package/dist/meta/finetune/retry.d.ts +24 -0
  83. package/dist/meta/finetune/retry.js +96 -0
  84. package/dist/meta/finetune/sampler.d.ts +18 -0
  85. package/dist/meta/finetune/sampler.js +47 -0
  86. package/dist/meta/finetune/system.d.ts +27 -0
  87. package/dist/meta/finetune/system.js +132 -0
  88. package/dist/meta/finetune/train.d.ts +43 -0
  89. package/dist/meta/finetune/train.js +425 -0
  90. package/dist/meta/finetune/tui.d.ts +16 -0
  91. package/dist/meta/finetune/tui.js +316 -0
  92. package/dist/meta/finetune/tuning.d.ts +193 -0
  93. package/dist/meta/finetune/tuning.js +308 -0
  94. package/dist/meta/finetune/usage.d.ts +66 -0
  95. package/dist/meta/finetune/usage.js +260 -0
  96. package/dist/meta/finetune/view.d.ts +9 -0
  97. package/dist/meta/finetune/view.js +27 -0
  98. package/dist/meta/host.d.ts +15 -0
  99. package/dist/meta/host.js +21 -0
  100. package/dist/meta/index.d.ts +11 -0
  101. package/dist/meta/index.js +11 -0
  102. package/dist/meta/launch.d.ts +81 -0
  103. package/dist/meta/launch.js +286 -0
  104. package/dist/meta/model.d.ts +44 -0
  105. package/dist/meta/model.js +119 -0
  106. package/dist/meta/prompts.d.ts +18 -0
  107. package/dist/meta/prompts.js +61 -0
  108. package/dist/meta/relay.d.ts +20 -0
  109. package/dist/meta/relay.js +123 -0
  110. package/dist/meta/tokens.d.ts +52 -0
  111. package/dist/meta/tokens.js +226 -0
  112. package/dist/meta/window.d.ts +61 -0
  113. package/dist/meta/window.js +198 -0
  114. package/dist/meta/wire.d.ts +42 -0
  115. package/dist/meta/wire.js +129 -0
  116. package/dist/meta.d.ts +71 -1
  117. package/dist/meta.js +303 -6
  118. package/dist/modelref.d.ts +16 -0
  119. package/dist/modelref.js +41 -0
  120. package/dist/narrate.js +5 -2
  121. package/dist/podman.d.ts +26 -0
  122. package/dist/podman.js +83 -3
  123. package/dist/projects.d.ts +0 -5
  124. package/dist/projects.js +2 -15
  125. package/dist/resources.d.ts +215 -0
  126. package/dist/resources.js +856 -0
  127. package/dist/sandbox.d.ts +2 -0
  128. package/dist/sandbox.js +10 -3
  129. package/dist/scaffold.d.ts +15 -7
  130. package/dist/scaffold.js +24 -67
  131. package/dist/session.js +4 -29
  132. package/dist/term.d.ts +2 -0
  133. package/dist/term.js +11 -2
  134. package/dist/trace.d.ts +5 -0
  135. package/dist/trace.js +107 -7
  136. package/dist/tui/app.js +19 -5
  137. package/dist/tui/links.d.ts +27 -0
  138. package/dist/tui/links.js +142 -0
  139. package/dist/tui/models.d.ts +35 -0
  140. package/dist/tui/models.js +447 -0
  141. package/dist/tui/sandbox.d.ts +10 -0
  142. package/dist/tui/sandbox.js +131 -0
  143. package/dist/tui/wrap.d.ts +5 -0
  144. package/dist/tui/wrap.js +12 -0
  145. package/dist/usage.d.ts +78 -0
  146. package/dist/usage.js +51 -0
  147. package/package.json +4 -2
  148. package/templates/editor/.github/copilot-instructions.md +60 -145
  149. package/templates/editor/.github/prompts/analyze.prompt.md +33 -0
  150. package/templates/editor/.github/prompts/dataset.prompt.md +108 -0
  151. package/templates/editor/.github/prompts/finetune-apply.prompt.md +270 -0
  152. package/templates/editor/.github/prompts/finetune.prompt.md +691 -198
  153. package/templates/editor/.github/prompts/new-agent.prompt.md +3 -1
  154. package/templates/editor/.github/prompts/new-skill.prompt.md +2 -0
  155. package/templates/editor/.github/prompts/project-review.prompt.md +5 -4
  156. package/templates/editor/.github/skills/zen-analyze-run/SKILL.md +588 -0
  157. package/templates/editor/.github/skills/zen-cli/SKILL.md +9 -1
  158. package/templates/editor/.github/skills/zen-cli/references/dataset.md +34 -0
  159. package/templates/editor/.github/skills/zen-cli/references/finetune.md +137 -0
  160. package/templates/editor/.github/skills/zen-cli/references/inspect.md +63 -11
  161. package/templates/editor/.github/skills/zen-cli/references/memory.md +33 -10
  162. package/templates/editor/.github/skills/zen-cli/references/meta.md +61 -0
  163. package/templates/editor/.github/skills/zen-cli/references/models.md +5 -1
  164. package/templates/editor/.github/skills/zen-cli/references/projects.md +4 -6
  165. package/templates/editor/.github/skills/zen-cli/references/rag.md +24 -6
  166. package/templates/editor/.github/skills/zen-cli/references/sandbox.md +35 -14
  167. package/templates/editor/.github/skills/zen-code-python/SKILL.md +123 -0
  168. package/templates/editor/.github/skills/zen-code-python/references/code_python.md +48 -0
  169. package/templates/editor/.github/skills/zen-dataset/SKILL.md +253 -0
  170. package/templates/editor/.github/skills/zen-finetune/SKILL.md +1492 -660
  171. package/templates/editor/.github/skills/zen-finetune/references/readme-template.md +268 -0
  172. package/templates/editor/.github/skills/zen-finetune/scripts/batch.mjs +390 -0
  173. package/templates/editor/.github/skills/zen-finetune/scripts/difficult.mjs +243 -0
  174. package/templates/editor/.github/skills/zen-finetune/scripts/memory.mjs +280 -0
  175. package/templates/editor/.github/skills/zen-finetune/scripts/next.mjs +398 -0
  176. package/templates/editor/.github/skills/zen-finetune/scripts/report.mjs +797 -0
  177. package/templates/editor/.github/skills/zen-finetune/scripts/select.mjs +354 -0
  178. package/templates/editor/.github/skills/zen-finetune/scripts/usage.mjs +722 -0
  179. package/templates/editor/.github/skills/zen-inspect/SKILL.md +74 -26
  180. package/templates/editor/.github/skills/zen-inspect-ask/SKILL.md +271 -0
  181. package/templates/editor/.github/skills/zen-instructions/SKILL.md +4 -12
  182. package/templates/editor/.github/skills/zen-memory/SKILL.md +82 -53
  183. package/templates/editor/.github/skills/zen-rag-docs/SKILL.md +20 -8
  184. package/templates/editor/.github/skills/zen-review/SKILL.md +8 -31
  185. package/templates/editor/.github/skills/zen-review/scripts/check-paths.sh +5 -5
  186. package/templates/editor/.github/skills/zen-review/scripts/review.sh +3 -12
  187. package/templates/editor/.github/skills/zen-sandbox/SKILL.md +9 -4
  188. package/templates/editor/.github/skills/zen-sandbox-capacity/SKILL.md +2 -2
  189. package/templates/editor/.github/skills/zen-spec-sync/SKILL.md +2 -4
  190. package/templates/editor/.github/skills/zen-topology/SKILL.md +283 -0
  191. package/templates/editor/.vscode/settings.json +1 -0
  192. package/templates/project/agents/files-instructions.md +29 -20
  193. package/templates/project/agents/fork-instructions.md +78 -28
  194. package/templates/project/agents/memory-instructions.md +222 -494
  195. package/templates/project/agents/tools-instructions.md +14 -4
  196. package/templates/project/gitignore +2 -0
  197. package/templates/editor/.github/skills/zen-finetune/scripts/collect.sh +0 -432
  198. package/templates/editor/.github/skills/zen-finetune/scripts/sample.sh +0 -199
  199. package/templates/editor/.github/skills/zen-memory/scripts/check-instructions.sh +0 -116
@@ -1,15 +1,15 @@
1
- import { ALL_AGENTS, buildMemoryReport, createEmbedder, GREP_FIELDS, grepMemory, hostPath, memoryDir, MemoryError, MemoryIndex, MemoryStore, MergeConflicts, mergeMemories, readProjectConfig, renderMemoryHtml, renderRecollection, } from '@zenera/neo';
1
+ import { ALL_AGENTS, buildMemoryReport, createEmbedder, diffMemories, GREP_FIELDS, grepMemory, hostPath, memoryDir, MemoryError, MemoryIndex, MemoryStore, MergeConflicts, mergeMemories, readProjectConfig, renderMemoryHtml, renderRecollection, } from '@zenera/neo';
2
2
  import { spawn } from 'node:child_process';
3
- import { existsSync } from 'node:fs';
3
+ import { existsSync, readFileSync } from 'node:fs';
4
4
  import { writeFile } from 'node:fs/promises';
5
- import { basename, isAbsolute, join, relative, resolve } from 'node:path';
5
+ import { basename, isAbsolute, join, resolve } from 'node:path';
6
6
  import { parse } from "../args.js";
7
7
  import { loadProjectEnv } from "../env.js";
8
8
  import { KeyStore } from "../keys.js";
9
9
  import { project as resolveProject } from "../resolve.js";
10
- import { ago, bold, bytes, CliError, confirm, count, cyan, dim, EXIT, green, isInteractive, json, note, table, usageError, write, writeAll, yellow, } from "../term.js";
11
- const USAGE = 'zen memory [stats|ls|search|grep|show|export|merge|forget] [args] [options]';
12
- const SUBCOMMANDS = ['stats', 'ls', 'search', 'grep', 'show', 'export', 'merge', 'forget'];
10
+ import { ago, bold, bytes, CliError, confirm, count, cyan, dim, EXIT, green, isInteractive, json, note, plain, table, usageError, write, writeAll, yellow, } from "../term.js";
11
+ const USAGE = 'zen memory [stats|ls|search|grep|show|diff|export|merge|forget] [args] [options]';
12
+ const SUBCOMMANDS = ['stats', 'ls', 'search', 'grep', 'show', 'diff', 'export', 'merge', 'forget'];
13
13
  /** Enough to see the shape of it; the rest is a number. */
14
14
  const LISTED = 30;
15
15
  // ---------------------------------------------------------------------------
@@ -42,12 +42,14 @@ export const memory = {
42
42
  ' search <text> Recall it, the way an agent does. Ranked.',
43
43
  ' grep <pattern> Every node containing it, with the matching lines.',
44
44
  ' show <id> One node in full, with what it links to.',
45
+ ' diff <base> What this memory holds that <base> did not.',
45
46
  ' export [file] The whole graph as one HTML page.',
46
47
  ' merge <dir...> Fold other memories into this one.',
47
48
  ' forget <id...> Remove nodes, their vectors and their files.',
48
49
  '',
49
50
  ' --project <name|dir> Which project. Defaults to the one you are in.',
50
51
  ' --dir <dir> Read this memory directory instead of the project’s.',
52
+ ' Run from inside a memory directory, that one is the default.',
51
53
  ' --kind <name> Only this kind of node.',
52
54
  ' --audience <name> Only nodes committed under this label.',
53
55
  ' --files Only nodes that remember a file.',
@@ -104,6 +106,12 @@ export const memory = {
104
106
  ' zen memory merge <batch-dir>/*/memory',
105
107
  '',
106
108
  'which is where `zen run batch` leaves them.',
109
+ '',
110
+ '`diff` is the question after a run that started from a copy: what it',
111
+ 'added, which of those `merge` would fold onto a node <base> already held',
112
+ '(a re-commit), what it revised, and which nodes `memory_load` read:',
113
+ '',
114
+ ' zen memory diff <candidate> --dir <batch-dir>/<id>/memory',
107
115
  ],
108
116
  run: async (ctx) => {
109
117
  const { values, positionals } = parse(ctx.args, {
@@ -156,6 +164,8 @@ export const memory = {
156
164
  return await grep(opened, rest, values, ctx.json);
157
165
  case 'show':
158
166
  return show(opened, rest, ctx.json);
167
+ case 'diff':
168
+ return await diff(opened, rest, ctx.cwd, ctx.json);
159
169
  case 'export':
160
170
  return await write_(opened, rest, values, ctx.cwd, ctx.json);
161
171
  case 'merge':
@@ -189,6 +199,11 @@ export const memory = {
189
199
  */
190
200
  async function open(cwd, want, at, opts = {}) {
191
201
  const { create = false, lock = true } = opts;
202
+ // Standing inside a memory (a merged or batch copy) means that memory, not
203
+ // the project's own one a few directories up.
204
+ if (!at && !want && isMemoryDir(cwd)) {
205
+ at = cwd;
206
+ }
192
207
  if (at) {
193
208
  const dir = resolve(cwd, at);
194
209
  if (!create && !existsSync(join(dir, 'manifest.json'))) {
@@ -212,6 +227,19 @@ async function open(cwd, want, at, opts = {}) {
212
227
  at: { root: project.dir, config },
213
228
  };
214
229
  }
230
+ /** A rag index also has a manifest and a graph.json; only a memory's manifest counts nodes and edges. */
231
+ function isMemoryDir(dir) {
232
+ if (!existsSync(join(dir, 'graph.json'))) {
233
+ return false;
234
+ }
235
+ try {
236
+ const m = JSON.parse(readFileSync(join(dir, 'manifest.json'), 'utf8'));
237
+ return typeof m?.nodes === 'number' && typeof m?.edges === 'number';
238
+ }
239
+ catch {
240
+ return false;
241
+ }
242
+ }
215
243
  // ---------------------------------------------------------------------------
216
244
  // stats
217
245
  // ---------------------------------------------------------------------------
@@ -309,23 +337,22 @@ function list(o, flags, asJson) {
309
337
  .filter((n) => !flags.stale || stale.has(n.id))
310
338
  .sort((a, b) => b.createdAt.localeCompare(a.createdAt));
311
339
  if (asJson) {
312
- json({ total: all.length, nodes: all.slice(0, limit) });
340
+ json({ total: all.length, nodes: all.slice(0, limit).map((n) => onHost(o, n)) });
313
341
  return;
314
342
  }
315
343
  if (!all.length) {
316
344
  note('nothing matches');
317
345
  return;
318
346
  }
319
- writeAll(table(all
347
+ writeAll(textLast(all
320
348
  .slice(0, limit)
321
349
  .map((n) => [
322
350
  cyan(n.id),
323
- n.kind,
324
- n.file ? dim('⎘ ' + n.file.format) : '',
325
- clip(n.text, 56),
326
- dim(ago(n.createdAt)),
327
- stale.has(n.id) ? yellow('superseded') : '',
328
- ])));
351
+ n.kind + (n.file ? dim(' ⎘ ' + n.file.format) : ''),
352
+ n.audience.join(','),
353
+ dim(`r${n.revision}`),
354
+ dim(ago(n.createdAt)) + (stale.has(n.id) ? ' ' + yellow('superseded') : ''),
355
+ ]), all.slice(0, limit).map((n) => n.text)));
329
356
  if (all.length > limit) {
330
357
  write();
331
358
  note(dim(`${all.length - limit} more — raise --limit, or use \`zen memory export\``));
@@ -552,7 +579,7 @@ async function grep(o, rest, flags, asJson) {
552
579
  hits: m.hits,
553
580
  more: m.more,
554
581
  })),
555
- unsearched: res.skipped,
582
+ unsearched: res.skipped.map((s) => ({ ...s, path: hostPath(o.dir, s) })),
556
583
  });
557
584
  return;
558
585
  }
@@ -562,7 +589,7 @@ async function grep(o, rest, flags, asJson) {
562
589
  }
563
590
  if (!res.found) {
564
591
  note(`nothing matches ${pattern}`);
565
- unsearched(res.skipped);
592
+ unsearched(o, res.skipped);
566
593
  return;
567
594
  }
568
595
  for (const m of res.matches) {
@@ -570,7 +597,7 @@ async function grep(o, rest, flags, asJson) {
570
597
  ' ' +
571
598
  m.node.kind +
572
599
  (m.stale ? ' ' + yellow('superseded') : '') +
573
- (m.node.file ? ' ' + dim(m.node.file.path) : ''));
600
+ (m.node.file ? ' ' + dim(hostPath(o.dir, m.node.file)) : ''));
574
601
  writeAll(table(m.hits.map((h) => [` ${dim(h.where + ':' + h.line)}`, clip(h.text, 120)])));
575
602
  if (m.more) {
576
603
  write(dim(` … ${count(m.more, 'more line')} in this node`));
@@ -580,19 +607,19 @@ async function grep(o, rest, flags, asJson) {
580
607
  if (res.truncated) {
581
608
  note(dim(`${res.found - res.matches.length} more nodes — raise --limit`));
582
609
  }
583
- unsearched(res.skipped);
610
+ unsearched(o, res.skipped);
584
611
  }
585
612
  /**
586
613
  * Named rather than counted. A file that was not read is a hole in an answer
587
614
  * whose whole value is that it has none, so the ids have to be printable.
588
615
  */
589
- function unsearched(skipped) {
616
+ function unsearched(o, skipped) {
590
617
  if (!skipped.length) {
591
618
  return;
592
619
  }
593
620
  write();
594
621
  note(yellow(`${count(skipped.length, 'file')} not searched`));
595
- writeAll(table(skipped.map((s) => [` ${cyan(s.id)}`, dim(s.reason), s.path])));
622
+ writeAll(table(skipped.map((s) => [` ${cyan(s.id)}`, dim(s.reason), hostPath(o.dir, s)])));
596
623
  }
597
624
  // ---------------------------------------------------------------------------
598
625
  // show
@@ -608,7 +635,11 @@ function show(o, ids, asJson) {
608
635
  }
609
636
  const links = o.store.graph.neighbors(id);
610
637
  if (asJson) {
611
- json({ node, links, file: node.file ? hostPath(o.dir, node.file) : undefined });
638
+ json({
639
+ node: onHost(o, node),
640
+ links,
641
+ file: node.file ? hostPath(o.dir, node.file) : undefined,
642
+ });
612
643
  return;
613
644
  }
614
645
  write(bold(node.id) + ' ' + cyan(node.kind));
@@ -653,6 +684,82 @@ function show(o, ids, asJson) {
653
684
  })));
654
685
  }
655
686
  // ---------------------------------------------------------------------------
687
+ // diff
688
+ // ---------------------------------------------------------------------------
689
+ async function diff(o, rest, cwd, asJson) {
690
+ if (rest.length !== 1) {
691
+ throw usageError('diff takes exactly one base memory', 'zen memory diff <base> [--dir <dir>]');
692
+ }
693
+ const at = resolve(cwd, rest[0]);
694
+ if (!existsSync(join(at, 'manifest.json'))) {
695
+ throw usageError(`${at} is not a memory`, 'no manifest.json in it');
696
+ }
697
+ const base = await MemoryStore.open(at, { lock: false });
698
+ try {
699
+ const d = diffMemories(base, o.store);
700
+ const mine = (n) => onHost(o, n);
701
+ const theirs = (n) => n.file ? { ...n, file: { ...n.file, path: hostPath(at, n.file) } } : n;
702
+ if (asJson) {
703
+ json({
704
+ ...d,
705
+ added: d.added.map((a) => ({ ...a, node: mine(a.node) })),
706
+ revised: d.revised.map((r) => ({ before: theirs(r.before), after: mine(r.after) })),
707
+ used: d.used.map((u) => ({ ...u, node: mine(u.node) })),
708
+ removed: d.removed.map(theirs),
709
+ });
710
+ return;
711
+ }
712
+ const twins = d.added.filter((a) => a.twin).length;
713
+ write(bold(o.dir) + dim(` against ${at} · nearest by ${d.by}`));
714
+ write();
715
+ write(`${bold('added')} ${d.added.length}` +
716
+ (twins ? ` · ${yellow(`${twins} already held`)}` : '') +
717
+ ` · ${bold('revised')} ${d.revised.length}` +
718
+ ` · ${bold('used')} ${d.used.length}` +
719
+ ` · ${bold('removed')} ${d.removed.length}` +
720
+ ` · ${bold('edges')} +${d.edges}`);
721
+ if (d.added.length) {
722
+ write();
723
+ write(dim('added'));
724
+ writeAll(textLast(d.added.map((a) => [
725
+ ` ${cyan(a.node.id)}`,
726
+ a.node.kind,
727
+ a.node.audience.join(','),
728
+ a.nearest
729
+ ? `${a.twin ? yellow('twin of') : dim('near')} ${a.nearest.id} ${dim(a.nearest.score.toFixed(2))}`
730
+ : dim('nothing near'),
731
+ ]), d.added.map((a) => a.node.text)));
732
+ }
733
+ if (d.revised.length) {
734
+ write();
735
+ write(dim('revised'));
736
+ writeAll(textLast(d.revised.map((r) => [
737
+ ` ${cyan(r.after.id)}`,
738
+ r.after.kind,
739
+ dim(`r${r.before.revision} → r${r.after.revision}`),
740
+ ]), d.revised.map((r) => r.after.text)));
741
+ }
742
+ if (d.used.length) {
743
+ write();
744
+ write(dim('used — returned by memory_load'));
745
+ writeAll(textLast(d.used.map((u) => [` ${cyan(u.node.id)}`, u.node.kind, `${u.loads}×`]), d.used.map((u) => u.node.text)));
746
+ }
747
+ if (d.removed.length) {
748
+ write();
749
+ write(dim('removed'));
750
+ writeAll(textLast(d.removed.map((n) => [` ${cyan(n.id)}`, n.kind]), d.removed.map((n) => n.text)));
751
+ }
752
+ if (d.supersedes.length) {
753
+ write();
754
+ write(dim('supersedes'));
755
+ writeAll(table(d.supersedes.map((s) => [` ${cyan(s.source)}`, '→', cyan(s.target)])));
756
+ }
757
+ }
758
+ finally {
759
+ base.release();
760
+ }
761
+ }
762
+ // ---------------------------------------------------------------------------
656
763
  // export
657
764
  // ---------------------------------------------------------------------------
658
765
  async function write_(o, rest, flags, cwd, asJson) {
@@ -661,13 +768,20 @@ async function write_(o, rest, flags, cwd, asJson) {
661
768
  }
662
769
  const named = rest[0] ?? flags.out ?? 'memory.html';
663
770
  const target = isAbsolute(named) ? named : resolve(cwd, named);
664
- const report = await buildMemoryReport(o.store, { title: `${o.project} · memory` });
771
+ const built = await buildMemoryReport(o.store, { title: `${o.project} · memory` });
772
+ const report = { ...built, nodes: built.nodes.map((n) => onHost(o, n)) };
665
773
  await writeFile(target, renderMemoryHtml(report), 'utf8');
666
774
  if (asJson) {
667
- json({ path: target, nodes: report.nodes.length, edges: report.edges.length });
775
+ json({
776
+ path: target,
777
+ from: o.dir,
778
+ nodes: report.nodes.length,
779
+ edges: report.edges.length,
780
+ });
668
781
  }
669
782
  else {
670
- note(`${green('wrote')} ${target} ${dim(`· ${count(report.nodes.length, 'node')}, ${count(report.edges.length, 'edge')}`)}`);
783
+ note(`${green('wrote')} ${target} ${dim(`· ${count(report.nodes.length, 'node')}, ${count(report.edges.length, 'edge')}` +
784
+ ` · from ${o.dir}`)}`);
671
785
  }
672
786
  if (flags.open) {
673
787
  reveal(target);
@@ -720,7 +834,7 @@ async function merge(o, dirs, flags, cwd, asJson) {
720
834
  }
721
835
  catch (err) {
722
836
  if (err instanceof MergeConflicts) {
723
- throw diverged(err, cwd, asJson);
837
+ throw diverged(err, asJson);
724
838
  }
725
839
  throw err;
726
840
  }
@@ -731,7 +845,7 @@ async function merge(o, dirs, flags, cwd, asJson) {
731
845
  writeAll(table([
732
846
  [dim('memory'), dim('nodes'), dim('new'), dim('shared'), dim('folded'), dim('files')],
733
847
  ...report.sources.map((s) => [
734
- near(s.dir, cwd),
848
+ s.dir,
735
849
  String(s.nodes),
736
850
  s.added ? green(String(s.added)) : '0',
737
851
  String(s.shared),
@@ -752,7 +866,7 @@ async function merge(o, dirs, flags, cwd, asJson) {
752
866
  * point of stopping is that a person decides, and they cannot decide from a
753
867
  * count.
754
868
  */
755
- function diverged(err, cwd, asJson) {
869
+ function diverged(err, asJson) {
756
870
  if (asJson) {
757
871
  json({ error: err.message, conflicts: err.conflicts });
758
872
  }
@@ -761,7 +875,7 @@ function diverged(err, cwd, asJson) {
761
875
  cyan(c.id),
762
876
  dim(`r${c.mine} ↔ r${c.theirs}`),
763
877
  clip(c.text, 48),
764
- dim(near(c.dir, cwd)),
878
+ dim(c.dir),
765
879
  ])));
766
880
  write();
767
881
  }
@@ -818,9 +932,19 @@ function clip(text, n) {
818
932
  const one = text.replace(/\s+/g, ' ').trim();
819
933
  return one.length > n ? one.slice(0, n - 1) + '…' : one;
820
934
  }
821
- /** A path as it was probably typed: relative when it is below here, absolute when it is not. */
822
- function near(dir, cwd) {
823
- const rel = relative(cwd, dir);
824
- return rel && !rel.startsWith('..') ? rel : dir;
935
+ /**
936
+ * A table whose last column is the text: cut to what the terminal has left,
937
+ * and whole when nobody is looking at a terminal — a pipe or an agent wants all of it.
938
+ */
939
+ function textLast(rows, texts) {
940
+ const widths = rows[0]?.map((_, c) => Math.max(...rows.map((r) => plain(r[c] ?? '').length)));
941
+ const room = process.stdout.isTTY
942
+ ? Math.max(24, (process.stdout.columns || 120) - (widths ?? []).reduce((t, w) => t + w + 2, 0))
943
+ : Infinity;
944
+ return table(rows.map((r, i) => [...r, clip(texts[i] ?? '', room)]));
945
+ }
946
+ /** The stored `/memory/<id>.<ext>` is the agent's mount path; a terminal needs the host one. */
947
+ function onHost(o, n) {
948
+ return n.file ? { ...n, file: { ...n.file, path: hostPath(o.dir, n.file) } } : n;
825
949
  }
826
950
  //# sourceMappingURL=memory.js.map