dsh-continual-evolve 0.14.2 → 0.15.1
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/README.md +35 -23
- package/README.zh.md +35 -23
- package/client/client.js +32 -43
- package/lib/card-routes.d.ts +3 -3
- package/lib/card-routes.js +38 -50
- package/lib/extraction-prompt.d.ts +52 -0
- package/lib/extraction-prompt.js +139 -0
- package/lib/extraction-surface.d.ts +66 -0
- package/lib/extraction-surface.js +186 -0
- package/lib/extraction.d.ts +56 -0
- package/lib/extraction.js +280 -0
- package/lib/import-md.d.ts +15 -0
- package/lib/import-md.js +140 -0
- package/lib/index.d.ts +56 -27
- package/lib/index.js +120 -65
- package/lib/memory-guide.d.ts +22 -14
- package/lib/memory-guide.js +33 -76
- package/lib/memory-rules.d.ts +56 -0
- package/lib/memory-rules.js +152 -0
- package/lib/memory-section.d.ts +26 -26
- package/lib/memory-section.js +48 -82
- package/lib/memory-snapshot.d.ts +43 -59
- package/lib/memory-snapshot.js +101 -141
- package/lib/memory-tools.d.ts +34 -0
- package/lib/memory-tools.js +142 -0
- package/lib/store-apply.d.ts +46 -0
- package/lib/store-apply.js +177 -0
- package/lib/store.d.ts +99 -0
- package/lib/store.js +241 -0
- package/locale/en.json +1 -1
- package/locale/zh.json +1 -1
- package/package.json +8 -6
- package/lib/known-workspaces.d.ts +0 -22
- package/lib/known-workspaces.js +0 -42
- package/lib/workspace-catalog.d.ts +0 -30
- package/lib/workspace-catalog.js +0 -149
package/lib/memory-section.d.ts
CHANGED
|
@@ -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
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
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
|
|
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
|
|
36
|
-
*
|
|
37
|
-
*
|
|
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
|
|
45
|
+
/** Include the when-to-save guide (default true). */
|
|
45
46
|
guide?: boolean;
|
|
46
|
-
/**
|
|
47
|
-
|
|
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
|
|
51
|
-
*
|
|
52
|
-
*
|
|
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
|
|
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
|
/**
|
package/lib/memory-section.js
CHANGED
|
@@ -1,25 +1,21 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* The
|
|
3
|
-
*
|
|
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
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
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
|
|
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 {
|
|
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
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
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
|
|
103
|
-
*
|
|
104
|
-
*
|
|
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
|
|
121
|
-
*
|
|
122
|
-
*
|
|
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
|
|
128
|
-
const maxChars = opts
|
|
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
|
|
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 (
|
|
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}
|
|
105
|
+
parts.push(`(索引超出 ${maxChars} 字符预算,已截断——其余记忆用 \`memory_read\` 关键词检索)`);
|
|
144
106
|
}
|
|
145
107
|
}
|
|
146
108
|
else {
|
|
147
|
-
parts.push(`(索引超出 ${maxChars}
|
|
109
|
+
parts.push(`(索引超出 ${maxChars} 字符预算,一行都放不下——记忆用 \`memory_read\` 关键词检索)`);
|
|
148
110
|
}
|
|
149
111
|
if (!guide) {
|
|
150
|
-
return
|
|
112
|
+
return records.length === 0 ? "" : parts.join("\n\n");
|
|
151
113
|
}
|
|
152
|
-
return [
|
|
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
|
package/lib/memory-snapshot.d.ts
CHANGED
|
@@ -1,82 +1,66 @@
|
|
|
1
|
-
|
|
2
|
-
export
|
|
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
|
-
|
|
11
|
-
|
|
5
|
+
id: string;
|
|
6
|
+
title: string;
|
|
12
7
|
description: string;
|
|
13
8
|
type: string;
|
|
14
|
-
/**
|
|
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
|
-
|
|
22
|
+
/** Active rows (the list the card renders). */
|
|
29
23
|
files: MemoryFileInfo[];
|
|
30
|
-
/**
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
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
|
-
*
|
|
50
|
-
*
|
|
51
|
-
*
|
|
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
|
|
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
|
|
44
|
+
export type MemoryEntryContent = {
|
|
63
45
|
ok: true;
|
|
64
|
-
|
|
46
|
+
id: string;
|
|
65
47
|
content: string;
|
|
66
|
-
|
|
67
|
-
changed: boolean;
|
|
48
|
+
updatedAtMs: number;
|
|
68
49
|
} | {
|
|
69
50
|
ok: false;
|
|
70
|
-
reason: "
|
|
51
|
+
reason: "absent" | "too-large";
|
|
71
52
|
};
|
|
72
53
|
/**
|
|
73
|
-
* Read one memory
|
|
74
|
-
*
|
|
75
|
-
*
|
|
76
|
-
*
|
|
77
|
-
*
|
|
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
|
|
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
|