dsh-continual-evolve 0.14.2 → 0.15.0

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.
@@ -1,3 +1,4 @@
1
+ import type { MemoryStore } from "./store.js";
1
2
  /** Name of the injected section (unique — duplicate names throw upstream). */
2
3
  export declare const MEMORY_SECTION_NAME = "evolve:memory-index";
3
4
  /** Default prompt-section order (empty 1–499 slot; upstream named slots start at 500). */
@@ -6,23 +7,14 @@ export declare const DEFAULT_MEMORY_SECTION_ORDER = 400;
6
7
  export declare const DEFAULT_MAX_CHARS = 6000;
7
8
  /** Sessions whose frozen section text is kept before LRU eviction. */
8
9
  export declare const DEFAULT_FROZEN_SESSIONS = 32;
9
- /** The assembling agent's validated absolute cwd, or undefined when absent. */
10
- export declare function cwdOf(agent: unknown): string | undefined;
11
- /** Absolute memory directory for a workspace root. */
12
- export declare function memoryDirFor(cwd: string): string;
13
- /**
14
- * Ensure `.evolve/` is ignored when the workspace is a git repository, so
15
- * the memory store never shows up in git status. Best-effort hygiene: a
16
- * non-git workspace (no `.git` entry at the cwd) is left untouched — no
17
- * `.gitignore` is created — and any write failure is swallowed.
18
- */
19
- export declare function ensureGitIgnored(cwd: string): void;
20
10
  /**
21
- * Ensure the store exists (creating the directory and a starter index on
22
- * first use), then return the trimmed index text. Any filesystem failure
23
- * degrades to "" — injection must never break an assembly.
11
+ * Legacy MD-era store location. Only the migration path (`import-md.ts`) and
12
+ * the not-yet-migrated card projection still resolve it; the injection path
13
+ * queries the central database.
24
14
  */
25
- export declare function readMemoryIndex(dir: string, bootstrap?: boolean): string;
15
+ export declare function memoryDirFor(cwd: string): string;
16
+ /** The assembling agent's validated absolute cwd, or undefined when absent. */
17
+ export declare function cwdOf(agent: unknown): string | undefined;
26
18
  /** What fitting the index into the budget produced. */
27
19
  export interface IndexFit {
28
20
  /** Index text actually shown (possibly truncated to a line boundary). */
@@ -32,28 +24,36 @@ export interface IndexFit {
32
24
  }
33
25
  /**
34
26
  * Whole-line-preserving truncation of the index to `maxChars` characters.
35
- * Only index rows that fit whole are shown — a partial markdown row would
36
- * corrupt the line the model reads. When not even one row fits, `text` is
37
- * "" and `dropped` carries the full length.
27
+ * Only index rows that fit whole are shown — a partial row would corrupt the
28
+ * line the model reads. When not even one row fits, `text` is "" and
29
+ * `dropped` carries the full length.
38
30
  */
39
31
  export declare function fitIndex(index: string, maxChars: number): IndexFit;
32
+ /** One index line: hook only — bodies are a `memory_read` call away. */
33
+ export declare function indexLineOf(record: {
34
+ id: string;
35
+ title: string;
36
+ description: string;
37
+ type: string;
38
+ }): string;
40
39
  /** Options for {@link memorySectionText}. */
41
40
  export interface MemorySectionOptions {
41
+ /** The opened store; undefined while it is still loading or failed. */
42
+ store: MemoryStore | undefined;
42
43
  /** Hard character budget for the injected index (default 6000). */
43
44
  maxChars?: number;
44
- /** Include the when_to_save guide (default true). */
45
+ /** Include the when-to-save guide (default true). */
45
46
  guide?: boolean;
46
- /** Skip store bootstrap (tests). */
47
- bootstrap?: boolean;
47
+ /** Whether `memory_write`/`memory_read` are actually registered on this host. */
48
+ toolsAvailable?: boolean;
48
49
  }
49
50
  /**
50
- * Compose the full memory section for one assembling agent: the guide
51
- * wrapped around the budget-bounded index, with the absolute store path
52
- * handed out so the model can read and write the files directly. With the
53
- * guide off and an empty index the result is "" — the prompt renderer then
51
+ * Compose the full memory section for one assembling agent. Any store
52
+ * failure degrades to "" — injection must never break an assembly. With the
53
+ * guide off and an empty store the result is "" — the prompt renderer then
54
54
  * drops the section, so an empty workspace costs zero tokens.
55
55
  */
56
- export declare function memorySectionText(agent: unknown, opts?: MemorySectionOptions): string;
56
+ export declare function memorySectionText(agent: unknown, opts: MemorySectionOptions): string;
57
57
  /** A per-session frozen view of the memory section text. */
58
58
  export interface FrozenMemorySection {
59
59
  /**
@@ -1,25 +1,21 @@
1
1
  /**
2
- * The whole runtime of the plugin after the 2026-10-04 ZCode-alignment
3
- * teardown: the session-start memory section.
2
+ * The session-start memory section: a store query rendered into the system
3
+ * prompt (the v0.15 SQLite single-store redesign, ADR
4
+ * `2026-10-06-sqlite-single-store`).
4
5
  *
5
- * The store is plain markdown inside the workspace — `<cwd>/.evolve/memory/`
6
- * with a `MEMORY.md` index and one file per fact — so the model reads and
7
- * writes it with its native file tools (DSH fences only out-of-workspace
8
- * writes; dot-directories inside the workspace pass both ways). This module
9
- * hands out the absolute path, injects the index content, and wraps the
10
- * when-to-save guide around it. There are no tools, no scopes, and no
11
- * governance: the files are the store, and a bad memory is a visible file
12
- * the user deletes.
6
+ * The injected index is a query result, not a file: one line per active
7
+ * memory of the assembling agent's workspace, feedback > user > reference,
8
+ * hooks only (title + description). Bodies live in the database and are
9
+ * fetched on demand with `memory_read`; there is no MEMORY.md to maintain
10
+ * and nothing for the model to write directly.
13
11
  *
14
12
  * Session-freeze contract (the prompt-cache guard): the section is computed
15
13
  * once per agent id and reused byte-for-byte afterwards, so the system
16
14
  * prompt stays stable within a session and cache reads keep hitting. A
17
- * memory written mid-session becomes visible in the NEXT session; the model
18
- * can always read the directory directly for the immediate need.
15
+ * memory written mid-session becomes visible in the NEXT session.
19
16
  */
20
- import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
21
17
  import { isAbsolute, join, resolve } from "node:path";
22
- import { MEMORY_GUIDE_RULES, memoryGuideIntro } from "./memory-guide.js";
18
+ import { memoryGuideIntro, MEMORY_GUIDE_RULES } from "./memory-guide.js";
23
19
  /** Name of the injected section (unique — duplicate names throw upstream). */
24
20
  export const MEMORY_SECTION_NAME = "evolve:memory-index";
25
21
  /** Default prompt-section order (empty 1–499 slot; upstream named slots start at 500). */
@@ -28,12 +24,14 @@ export const DEFAULT_MEMORY_SECTION_ORDER = 400;
28
24
  export const DEFAULT_MAX_CHARS = 6000;
29
25
  /** Sessions whose frozen section text is kept before LRU eviction. */
30
26
  export const DEFAULT_FROZEN_SESSIONS = 32;
31
- const MEMORY_DIR_SEGMENTS = [".evolve", "memory"];
32
- const INDEX_FILE = "MEMORY.md";
33
- /** Starter index written on first use; the model maintains every line after. */
34
- const STARTER_INDEX = "# 记忆索引\n\n<!-- 一行一条:- [标题](文件名.md) — 一句话相关性钩子;写新记忆后在此追加一行 -->\n";
35
- /** Appended to the workspace .gitignore on bootstrap (git workspaces only). */
36
- const GITIGNORE_ENTRY = "# workspace memory (dsh-continual-evolve)\n.evolve/\n";
27
+ /**
28
+ * Legacy MD-era store location. Only the migration path (`import-md.ts`) and
29
+ * the not-yet-migrated card projection still resolve it; the injection path
30
+ * queries the central database.
31
+ */
32
+ export function memoryDirFor(cwd) {
33
+ return join(resolve(cwd), ".evolve", "memory");
34
+ }
37
35
  /** The assembling agent's validated absolute cwd, or undefined when absent. */
38
36
  export function cwdOf(agent) {
39
37
  try {
@@ -52,56 +50,11 @@ export function cwdOf(agent) {
52
50
  return undefined;
53
51
  }
54
52
  }
55
- /** Absolute memory directory for a workspace root. */
56
- export function memoryDirFor(cwd) {
57
- return join(resolve(cwd), ...MEMORY_DIR_SEGMENTS);
58
- }
59
- /**
60
- * Ensure `.evolve/` is ignored when the workspace is a git repository, so
61
- * the memory store never shows up in git status. Best-effort hygiene: a
62
- * non-git workspace (no `.git` entry at the cwd) is left untouched — no
63
- * `.gitignore` is created — and any write failure is swallowed.
64
- */
65
- export function ensureGitIgnored(cwd) {
66
- try {
67
- if (!existsSync(join(cwd, ".git"))) {
68
- return;
69
- }
70
- const gitignore = join(cwd, ".gitignore");
71
- const existing = existsSync(gitignore) ? readFileSync(gitignore, "utf8") : "";
72
- if (existing.split("\n").some((line) => line.trim() === ".evolve/")) {
73
- return;
74
- }
75
- const prefix = existing.length > 0 && !existing.endsWith("\n") ? "\n" : "";
76
- writeFileSync(gitignore, `${existing}${prefix}${GITIGNORE_ENTRY}`, "utf8");
77
- }
78
- catch {
79
- // Never blocks the store; worst case is a visible .evolve/ in git status.
80
- }
81
- }
82
- /**
83
- * Ensure the store exists (creating the directory and a starter index on
84
- * first use), then return the trimmed index text. Any filesystem failure
85
- * degrades to "" — injection must never break an assembly.
86
- */
87
- export function readMemoryIndex(dir, bootstrap = true) {
88
- try {
89
- if (bootstrap && !existsSync(dir)) {
90
- mkdirSync(dir, { recursive: true });
91
- writeFileSync(join(dir, INDEX_FILE), STARTER_INDEX, "utf8");
92
- ensureGitIgnored(resolve(dir, "..", ".."));
93
- }
94
- return readFileSync(join(dir, INDEX_FILE), "utf8").trim();
95
- }
96
- catch {
97
- return "";
98
- }
99
- }
100
53
  /**
101
54
  * Whole-line-preserving truncation of the index to `maxChars` characters.
102
- * Only index rows that fit whole are shown — a partial markdown row would
103
- * corrupt the line the model reads. When not even one row fits, `text` is
104
- * "" and `dropped` carries the full length.
55
+ * Only index rows that fit whole are shown — a partial row would corrupt the
56
+ * line the model reads. When not even one row fits, `text` is "" and
57
+ * `dropped` carries the full length.
105
58
  */
106
59
  export function fitIndex(index, maxChars) {
107
60
  const budget = Math.max(0, maxChars);
@@ -116,40 +69,53 @@ export function fitIndex(index, maxChars) {
116
69
  const text = cut.slice(0, lastLine);
117
70
  return { text, dropped: index.length - text.length };
118
71
  }
72
+ /** One index line: hook only — bodies are a `memory_read` call away. */
73
+ export function indexLineOf(record) {
74
+ return `- [${record.id}] ${record.title} — ${record.description}(${record.type})`;
75
+ }
119
76
  /**
120
- * Compose the full memory section for one assembling agent: the guide
121
- * wrapped around the budget-bounded index, with the absolute store path
122
- * handed out so the model can read and write the files directly. With the
123
- * guide off and an empty index the result is "" — the prompt renderer then
77
+ * Compose the full memory section for one assembling agent. Any store
78
+ * failure degrades to "" — injection must never break an assembly. With the
79
+ * guide off and an empty store the result is "" — the prompt renderer then
124
80
  * drops the section, so an empty workspace costs zero tokens.
125
81
  */
126
82
  export function memorySectionText(agent, opts) {
127
- const guide = opts?.guide ?? true;
128
- const maxChars = opts?.maxChars ?? DEFAULT_MAX_CHARS;
83
+ const guide = opts.guide ?? true;
84
+ const maxChars = opts.maxChars ?? DEFAULT_MAX_CHARS;
129
85
  const cwd = cwdOf(agent);
130
- if (!cwd) {
86
+ if (!cwd || opts.store === undefined) {
87
+ return "";
88
+ }
89
+ let records;
90
+ try {
91
+ records = opts.store.list(cwd);
92
+ }
93
+ catch {
131
94
  return "";
132
95
  }
133
- const dir = memoryDirFor(cwd);
134
- const index = readMemoryIndex(dir, opts?.bootstrap ?? true);
96
+ const index = records.map(indexLineOf).join("\n");
135
97
  const { text, dropped } = fitIndex(index, maxChars);
136
98
  const parts = [];
137
- if (index.length === 0) {
99
+ if (records.length === 0) {
138
100
  parts.push("<memories>\n(暂无记忆)\n</memories>");
139
101
  }
140
102
  else if (text.length > 0) {
141
103
  parts.push(`<memories>\n${text}\n</memories>`);
142
104
  if (dropped > 0) {
143
- parts.push(`(索引超出 ${maxChars} 字符预算,已截断——其余记忆直接读取 \`${dir}/\` 目录)`);
105
+ parts.push(`(索引超出 ${maxChars} 字符预算,已截断——其余记忆用 \`memory_read\` 关键词检索)`);
144
106
  }
145
107
  }
146
108
  else {
147
- parts.push(`(索引超出 ${maxChars} 字符预算,一行都放不下——记忆直接读取 \`${dir}/\` 目录)`);
109
+ parts.push(`(索引超出 ${maxChars} 字符预算,一行都放不下——记忆用 \`memory_read\` 关键词检索)`);
148
110
  }
149
111
  if (!guide) {
150
- return index.length === 0 ? "" : parts.join("\n\n");
112
+ return records.length === 0 ? "" : parts.join("\n\n");
151
113
  }
152
- return [memoryGuideIntro(dir), ...parts, MEMORY_GUIDE_RULES].join("\n\n");
114
+ return [
115
+ memoryGuideIntro(opts.store.path, cwd, opts.toolsAvailable ?? true),
116
+ ...parts,
117
+ MEMORY_GUIDE_RULES,
118
+ ].join("\n\n");
153
119
  }
154
120
  /**
155
121
  * Freeze the section text per agent id. The frozen value is what keeps the
@@ -1,82 +1,66 @@
1
- /** The index file name (same store contract as the injection path). */
2
- export declare const MEMORY_SNAPSHOT_INDEX_FILE = "MEMORY.md";
3
- /**
4
- * Preview size cap for the file content endpoint — files above it answer
5
- * `too-large` instead of their body (ZCode's memory viewer uses the same 5 MiB).
6
- */
7
- export declare const MEMORY_FILE_PREVIEW_LIMIT: number;
8
- /** One memory file with its frontmatter fields ("" when absent). */
1
+ import { MEMORY_FILE_PREVIEW_LIMIT, type MemoryStore } from "./store.js";
2
+ export { MEMORY_FILE_PREVIEW_LIMIT };
3
+ /** One memory row the card renders. */
9
4
  export interface MemoryFileInfo {
10
- file: string;
11
- name: string;
5
+ id: string;
6
+ title: string;
12
7
  description: string;
13
8
  type: string;
14
- /** File modification time in epoch milliseconds (the card's "updated" line). */
9
+ /** Row status; quarantined rows render in the patrol section, not here. */
10
+ status: string;
11
+ /** Update time in epoch milliseconds (the card's "updated" line). */
15
12
  updatedAt: number;
16
13
  }
17
- /** One parsed index row: `- [title](file.md) — hook`. */
18
- export interface MemoryIndexRow {
19
- title: string;
20
- file: string;
21
- }
22
14
  /** The full read-only snapshot the card renders for one workspace root. */
23
15
  export interface MemorySnapshot {
24
16
  root: string;
25
- memoryDir: string;
26
17
  exists: boolean;
18
+ /** Set when the store query failed; null otherwise. */
19
+ readError: string | null;
20
+ /** Active row count (the number the card's stats line shows). */
27
21
  fileCount: number;
28
- indexEntryCount: number;
22
+ /** Active rows (the list the card renders). */
29
23
  files: MemoryFileInfo[];
30
- /** Index rows whose referenced file no longer exists on disk. */
31
- missingFiles: string[];
32
- /** .md files on disk that no index row references. */
33
- unindexedFiles: string[];
34
- /** Set when the store exists but could not be read; null otherwise. */
35
- readError: string | null;
24
+ /** Rows quarantined by patrol — displayed as 巡检异常, never in the list. */
25
+ quarantined: {
26
+ id: string;
27
+ reason: string;
28
+ }[];
29
+ /** The last patrol pass this process recorded (null before the first). */
30
+ lastPatrol: {
31
+ ts: string;
32
+ orphanFtsRows: number;
33
+ quarantined: number;
34
+ } | null;
36
35
  }
37
- /** Minimal frontmatter fields the card shows. */
38
- export interface MemoryFrontmatter {
39
- name: string;
40
- description: string;
41
- type: string;
42
- }
43
- /**
44
- * Parse `- [title](file.md) …` rows out of an index text. Anything else
45
- * (headings, the bootstrap hint comment, blank lines) is ignored.
46
- */
47
- export declare function parseMemoryIndexRows(indexText: string): MemoryIndexRow[];
48
36
  /**
49
- * Read the card-relevant frontmatter fields (`name`, `description`, `type`)
50
- * from a leading `---` block. A missing or malformed block yields "" fields —
51
- * the body text remains the source of truth the user opens in the editor.
37
+ * The read-only snapshot for one workspace partition. A workspace without
38
+ * rows reports `exists: false` (the card shows the empty state); a failing
39
+ * query reports `readError` instead of throwing — the card must observe,
40
+ * never break.
52
41
  */
53
- export declare function parseMemoryFrontmatter(text: string): MemoryFrontmatter;
54
- /**
55
- * The read-only memory snapshot for one workspace root. A missing store
56
- * reports `exists: false` (the card shows the empty state); a store that
57
- * exists but fails to read reports `readError` instead of throwing — the
58
- * card must observe, never break.
59
- */
60
- export declare function memorySnapshot(root: string): MemorySnapshot;
42
+ export declare function memorySnapshot(store: MemoryStore, root: string): MemorySnapshot;
61
43
  /** The content endpoint's outcomes: the body, or why there is none. */
62
- export type MemoryFileContent = {
44
+ export type MemoryEntryContent = {
63
45
  ok: true;
64
- file: string;
46
+ id: string;
65
47
  content: string;
66
- mtimeMs: number;
67
- changed: boolean;
48
+ updatedAtMs: number;
68
49
  } | {
69
50
  ok: false;
70
- reason: "outside" | "absent" | "too-large";
51
+ reason: "absent" | "too-large";
71
52
  };
72
53
  /**
73
- * Read one memory file's body for the card's content preview — the viewer
74
- * side of the same read-only fence as `memorySnapshot`. The requested file
75
- * name must be a plain entry of the workspace's memory directory (no path
76
- * separators, no traversal, resolved location stays inside the directory);
77
- * anything else answers `outside` without touching the filesystem, so the
78
- * endpoint cannot become a path oracle. Oversized files answer `too-large`
79
- * with the body unread.
54
+ * Read one memory record's body for the card's content preview — keyed by
55
+ * id, never by file name (the store is a database; there are no files to
56
+ * path-check). An unknown id answers `absent`; bodies are capped at 64KB by
57
+ * the schema, so `too-large` can only fire if the preview limit were ever
58
+ * lowered below that.
80
59
  */
81
- export declare function memoryFileContent(root: string, file: string): MemoryFileContent;
60
+ export declare function memoryEntryContent(store: MemoryStore, root: string, id: string): MemoryEntryContent;
61
+ /** The workspace rows the card lists: active partitions whose directory survives. */
62
+ export declare function listCardWorkspaces(store: MemoryStore): {
63
+ root: string;
64
+ label: string;
65
+ }[];
82
66
  //# sourceMappingURL=memory-snapshot.d.ts.map