@y0shua1ee/dsh-auto-memory 0.0.0-stage → 0.2.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/lib/tool.js ADDED
@@ -0,0 +1,230 @@
1
+ import { INDEX_FILE, MemoryError, MemoryStore, VIRTUAL_ROOT } from "./store.js";
2
+
3
+ const COMMANDS = ["view", "create", "str_replace", "insert", "delete", "rename"];
4
+
5
+ function formatBytes(bytes) {
6
+ if (bytes < 1024) return `${bytes} B`;
7
+ if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
8
+ return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
9
+ }
10
+
11
+ function requireString(value, name) {
12
+ if (typeof value !== "string") {
13
+ throw new MemoryError(`${name} is required and must be a string`, "MEMORY_INVALID_ARGUMENT");
14
+ }
15
+ return value;
16
+ }
17
+
18
+ function renderListing(relativePath, entries) {
19
+ if (entries.length === 0) {
20
+ return relativePath.length === 0
21
+ ? `${VIRTUAL_ROOT} is empty. Create a topic file with memory create, then index it in ${VIRTUAL_ROOT}/${INDEX_FILE}.`
22
+ : `${VIRTUAL_ROOT}/${relativePath} is empty.`;
23
+ }
24
+ const files = entries.filter((entry) => entry.kind === "file");
25
+ const bytes = files.reduce((total, entry) => total + entry.bytes, 0);
26
+ const heading = relativePath.length === 0 ? VIRTUAL_ROOT : `${VIRTUAL_ROOT}/${relativePath}`;
27
+ const lines = [`${heading} (${files.length} ${files.length === 1 ? "file" : "files"}, ${formatBytes(bytes)})`];
28
+ for (const entry of entries) {
29
+ const suffix = entry.kind === "directory" ? "/" : ` (${formatBytes(entry.bytes)})`;
30
+ lines.push(`- ${entry.path}${suffix}`);
31
+ }
32
+ return lines.join("\n");
33
+ }
34
+
35
+ function indexNote({ indexed, indexWarning, action }) {
36
+ if (indexWarning !== undefined) return indexWarning;
37
+ if (indexed) return `Updated MEMORY.md (${action}).`;
38
+ return `MEMORY.md already links this file (${action}).`;
39
+ }
40
+
41
+ async function viewToolOutput(store, relativePath, numbered) {
42
+ const result = await store.view(relativePath);
43
+ if (result.kind === "directory") {
44
+ return {
45
+ ok: true,
46
+ output: renderListing(relativePath, result.entries),
47
+ path: relativePath.length === 0 ? VIRTUAL_ROOT : `${VIRTUAL_ROOT}/${relativePath}`,
48
+ entries: result.entries
49
+ };
50
+ }
51
+ const output = numbered
52
+ ? result.content
53
+ .split("\n")
54
+ .map((line, index) => `${String(index + 1).padStart(4, " ")}: ${line}`)
55
+ .join("\n")
56
+ : result.content;
57
+ return {
58
+ ok: true,
59
+ output,
60
+ path: `${VIRTUAL_ROOT}/${relativePath}`,
61
+ bytes: result.bytes,
62
+ lines: result.lines
63
+ };
64
+ }
65
+
66
+ /**
67
+ * Build the `memory` tool definition options.
68
+ *
69
+ * The returned object is passed to `defineTool` by the plugin entry. Keeping it
70
+ * free of `defineTool` imports lets the command semantics be tested without a
71
+ * running harness.
72
+ *
73
+ * @param {object} input - tool wiring.
74
+ * @param {(agent: object, signal: AbortSignal) => Promise<object>} input.projectFor - resolve the calling session's project.
75
+ * @returns {object} tool definition options.
76
+ */
77
+ export function buildMemoryToolOptions({ projectFor }) {
78
+ return {
79
+ name: "memory",
80
+ description:
81
+ "Read and maintain this project's persistent memory, stored outside the repository under ~/.dsh/auto-memory. " +
82
+ "Paths are virtual: /memories is the directory and /memories/MEMORY.md is the index loaded into future sessions. " +
83
+ "Use view to list or read, create for a new .md file, str_replace to change exactly one occurrence, insert to add lines, delete to remove a file, and rename to move one. " +
84
+ "Save durable preferences, corrections, decisions, constraints, and pitfalls; never save secrets or transient task state.",
85
+ parameters: {
86
+ command: {
87
+ type: "string",
88
+ required: true,
89
+ enum: COMMANDS,
90
+ description: "Operation to perform."
91
+ },
92
+ path: {
93
+ type: "string",
94
+ description: `Virtual path such as ${VIRTUAL_ROOT}/${INDEX_FILE} or ${VIRTUAL_ROOT}/testing.md. Required for every command except a root view.`
95
+ },
96
+ file_text: {
97
+ type: "string",
98
+ description: "For create: the complete file content. Creating MEMORY.md with empty text writes the standard index header."
99
+ },
100
+ old_str: {
101
+ type: "string",
102
+ description: "For str_replace: text that must occur exactly once in the file."
103
+ },
104
+ new_str: {
105
+ type: "string",
106
+ description: "For str_replace: replacement text; omit or pass an empty string to delete the matched text."
107
+ },
108
+ insert_line: {
109
+ type: "number",
110
+ description: "For insert: zero-based line index at which to insert; 0 inserts before the first line."
111
+ },
112
+ insert_text: {
113
+ type: "string",
114
+ description: "For insert: text to insert as one or more lines."
115
+ },
116
+ new_path: {
117
+ type: "string",
118
+ description: "For rename: the destination virtual path."
119
+ },
120
+ numbered: {
121
+ type: "boolean",
122
+ description: "For view of a file: prefix each line with its 1-based line number."
123
+ }
124
+ },
125
+ output: {
126
+ schema: {
127
+ type: "object",
128
+ additionalProperties: false,
129
+ properties: {
130
+ ok: { type: "boolean", required: true },
131
+ output: { type: "string", required: true },
132
+ path: { type: "string" },
133
+ changed: { type: "boolean" },
134
+ bytes: { type: "number" },
135
+ lines: { type: "number" },
136
+ entries: {
137
+ type: "array",
138
+ items: {
139
+ type: "object",
140
+ additionalProperties: false,
141
+ properties: {
142
+ path: { type: "string", required: true },
143
+ kind: { type: "string", required: true, enum: ["file", "directory"] },
144
+ bytes: { type: "number", required: true }
145
+ }
146
+ }
147
+ }
148
+ }
149
+ },
150
+ render: (_args, value) => [{ type: "text", text: value.output }]
151
+ },
152
+ presentCall: (args) => ({
153
+ card: "generic",
154
+ title: args.command === "view" ? "Read project memory" : `${args.command} project memory`,
155
+ kind: args.command === "view" ? "read" : "other",
156
+ rawInput: args.path ?? VIRTUAL_ROOT
157
+ }),
158
+ execute: async (args, exec) => {
159
+ const agent = exec?.agent;
160
+ if (agent === undefined) {
161
+ throw new MemoryError("memory requires a calling agent", "MEMORY_NO_AGENT");
162
+ }
163
+ const project = await projectFor(agent, exec?.signal);
164
+ const store = project.store;
165
+ const command = args.command;
166
+ if (command === "view") {
167
+ return viewToolOutput(store, MemoryStore.parseVirtualPath(args.path ?? VIRTUAL_ROOT), args.numbered === true);
168
+ }
169
+ const relativePath = MemoryStore.parseVirtualPath(requireString(args.path, "path"));
170
+ if (command === "create") {
171
+ const fileText = args.file_text === undefined ? "" : requireString(args.file_text, "file_text");
172
+ const result = await store.create(relativePath, fileText);
173
+ const output = [
174
+ `Created ${result.path} (${formatBytes(result.bytes)}, ${result.lines} lines).`,
175
+ result.indexWarning ??
176
+ (relativePath === INDEX_FILE
177
+ ? "This is the index injected into future sessions."
178
+ : indexNote({ ...result, action: "created" }))
179
+ ].join("\n");
180
+ return { ok: true, output, path: result.path, changed: true, bytes: result.bytes, lines: result.lines };
181
+ }
182
+ if (command === "str_replace") {
183
+ const oldStr = requireString(args.old_str, "old_str");
184
+ const newStr = args.new_str === undefined ? "" : requireString(args.new_str, "new_str");
185
+ const result = await store.strReplace(relativePath, oldStr, newStr);
186
+ return {
187
+ ok: true,
188
+ output: `Edited ${result.path} (${formatBytes(result.bytes)}, ${result.lines} lines).`,
189
+ path: result.path,
190
+ changed: true,
191
+ bytes: result.bytes,
192
+ lines: result.lines
193
+ };
194
+ }
195
+ if (command === "insert") {
196
+ if (!Number.isSafeInteger(args.insert_line)) {
197
+ throw new MemoryError("insert_line must be a non-negative integer", "MEMORY_INVALID_ARGUMENT");
198
+ }
199
+ const insertText = requireString(args.insert_text, "insert_text");
200
+ const result = await store.insert(relativePath, args.insert_line, insertText);
201
+ return {
202
+ ok: true,
203
+ output: `Inserted text at line ${result.insertedAt} in ${result.path} (${formatBytes(result.bytes)}, ${result.lines} lines).`,
204
+ path: result.path,
205
+ changed: true,
206
+ bytes: result.bytes,
207
+ lines: result.lines
208
+ };
209
+ }
210
+ if (command === "delete") {
211
+ const result = await store.delete(relativePath);
212
+ const output = [
213
+ `Deleted ${result.path}.`,
214
+ result.indexWarning ?? (result.indexed ? "Removed its MEMORY.md index line." : "No MEMORY.md index line referenced it.")
215
+ ].join("\n");
216
+ return { ok: true, output, path: result.path, changed: true };
217
+ }
218
+ if (command === "rename") {
219
+ const newPath = requireString(args.new_path, "new_path");
220
+ const result = await store.rename(relativePath, MemoryStore.parseVirtualPath(newPath));
221
+ const output = [
222
+ `Renamed ${result.previousPath} to ${result.path}.`,
223
+ result.indexWarning ?? (result.indexed ? "Updated its MEMORY.md link." : "No MEMORY.md link referenced it.")
224
+ ].join("\n");
225
+ return { ok: true, output, path: result.path, changed: true };
226
+ }
227
+ throw new MemoryError(`unknown command ${JSON.stringify(command)}`, "MEMORY_INVALID_ARGUMENT");
228
+ }
229
+ };
230
+ }
package/package.json CHANGED
@@ -1,6 +1,77 @@
1
1
  {
2
2
  "name": "@y0shua1ee/dsh-auto-memory",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
3
+ "version": "0.2.1",
4
+ "description": "Persistent project memory and Claude Code-compatible rules for DeepSeek Harness.",
5
+ "type": "module",
6
+ "main": "lib/index.js",
7
+ "exports": {
8
+ ".": "./lib/index.js",
9
+ "./cordis.patch.yml": "./cordis.patch.yml",
10
+ "./package.json": "./package.json"
11
+ },
12
+ "files": [
13
+ "lib",
14
+ "cordis.patch.yml",
15
+ "assets/icon.svg",
16
+ "README.md",
17
+ "LICENSE"
18
+ ],
19
+ "license": "MIT",
20
+ "keywords": [
21
+ "dsh",
22
+ "deepseek-harness",
23
+ "cordis",
24
+ "plugin",
25
+ "memory",
26
+ "auto-memory",
27
+ "rules",
28
+ "claude-code",
29
+ "agent"
30
+ ],
31
+ "peerDependencies": {
32
+ "@deepseek-ai/dsh-home-paths": "0.2.1-rc.3",
33
+ "@deepseek-ai/dsh-llm": "0.2.1-rc.3",
34
+ "@deepseek-ai/dsh-tools": "0.2.1-rc.3",
35
+ "@deepseek-ai/schemastery": "~3.18.5"
36
+ },
37
+ "peerDependenciesMeta": {
38
+ "@deepseek-ai/dsh-home-paths": {
39
+ "optional": true
40
+ },
41
+ "@deepseek-ai/dsh-llm": {
42
+ "optional": true
43
+ },
44
+ "@deepseek-ai/dsh-tools": {
45
+ "optional": true
46
+ },
47
+ "@deepseek-ai/schemastery": {
48
+ "optional": true
49
+ }
50
+ },
51
+ "author": "Ares Lee",
52
+ "homepage": "https://github.com/y0shua1ee/dsh-auto-memory#readme",
53
+ "repository": {
54
+ "type": "git",
55
+ "url": "git+https://github.com/y0shua1ee/dsh-auto-memory.git"
56
+ },
57
+ "bugs": {
58
+ "url": "https://github.com/y0shua1ee/dsh-auto-memory/issues"
59
+ },
60
+ "icon": "assets/icon.svg",
61
+ "engines": {
62
+ "node": ">=22"
63
+ },
64
+ "dsh": {
65
+ "bundle": {
66
+ "patch": "./cordis.patch.yml"
67
+ }
68
+ },
69
+ "publishConfig": {
70
+ "access": "public"
71
+ },
72
+ "scripts": {
73
+ "test": "node --test test/*.test.mjs",
74
+ "test:integration": "node --import ./test/register.mjs --test test/*.test.mjs",
75
+ "extract-runtime": "node scripts/extract-runtime.mjs"
76
+ }
6
77
  }