@roackb2/heddle 0.0.5 → 0.0.6

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 (65) hide show
  1. package/README.md +36 -5
  2. package/dist/src/__tests__/chat-compaction.test.js +39 -0
  3. package/dist/src/__tests__/chat-compaction.test.js.map +1 -1
  4. package/dist/src/__tests__/chat-format.test.js +14 -1
  5. package/dist/src/__tests__/chat-format.test.js.map +1 -1
  6. package/dist/src/__tests__/local-commands.test.js +65 -101
  7. package/dist/src/__tests__/local-commands.test.js.map +1 -1
  8. package/dist/src/__tests__/prompts.test.js +3 -0
  9. package/dist/src/__tests__/prompts.test.js.map +1 -1
  10. package/dist/src/__tests__/tools.test.js +78 -0
  11. package/dist/src/__tests__/tools.test.js.map +1 -1
  12. package/dist/src/cli/ask.d.ts.map +1 -1
  13. package/dist/src/cli/ask.js +5 -1
  14. package/dist/src/cli/ask.js.map +1 -1
  15. package/dist/src/cli/chat/App.d.ts.map +1 -1
  16. package/dist/src/cli/chat/App.js +5 -5
  17. package/dist/src/cli/chat/App.js.map +1 -1
  18. package/dist/src/cli/chat/components/SlashHintPanel.js +2 -30
  19. package/dist/src/cli/chat/components/SlashHintPanel.js.map +1 -1
  20. package/dist/src/cli/chat/hooks/useAgentRun.d.ts.map +1 -1
  21. package/dist/src/cli/chat/hooks/useAgentRun.js +28 -8
  22. package/dist/src/cli/chat/hooks/useAgentRun.js.map +1 -1
  23. package/dist/src/cli/chat/state/compaction.d.ts +4 -0
  24. package/dist/src/cli/chat/state/compaction.d.ts.map +1 -1
  25. package/dist/src/cli/chat/state/compaction.js +21 -5
  26. package/dist/src/cli/chat/state/compaction.js.map +1 -1
  27. package/dist/src/cli/chat/state/local-commands.d.ts +6 -0
  28. package/dist/src/cli/chat/state/local-commands.d.ts.map +1 -1
  29. package/dist/src/cli/chat/state/local-commands.js +38 -45
  30. package/dist/src/cli/chat/state/local-commands.js.map +1 -1
  31. package/dist/src/cli/chat/state/storage.js +1 -0
  32. package/dist/src/cli/chat/state/storage.js.map +1 -1
  33. package/dist/src/cli/chat/state/types.d.ts +1 -0
  34. package/dist/src/cli/chat/state/types.d.ts.map +1 -1
  35. package/dist/src/cli/chat/submit.d.ts.map +1 -1
  36. package/dist/src/cli/chat/submit.js +27 -1
  37. package/dist/src/cli/chat/submit.js.map +1 -1
  38. package/dist/src/cli/chat/utils/format.d.ts +5 -0
  39. package/dist/src/cli/chat/utils/format.d.ts.map +1 -1
  40. package/dist/src/cli/chat/utils/format.js +23 -0
  41. package/dist/src/cli/chat/utils/format.js.map +1 -1
  42. package/dist/src/cli/chat/utils/runtime.d.ts +1 -0
  43. package/dist/src/cli/chat/utils/runtime.d.ts.map +1 -1
  44. package/dist/src/cli/chat/utils/runtime.js +1 -0
  45. package/dist/src/cli/chat/utils/runtime.js.map +1 -1
  46. package/dist/src/index.d.ts +2 -0
  47. package/dist/src/index.d.ts.map +1 -1
  48. package/dist/src/index.js +1 -0
  49. package/dist/src/index.js.map +1 -1
  50. package/dist/src/prompts/system-prompt.d.ts.map +1 -1
  51. package/dist/src/prompts/system-prompt.js +10 -0
  52. package/dist/src/prompts/system-prompt.js.map +1 -1
  53. package/dist/src/tools/edit-file.d.ts +3 -7
  54. package/dist/src/tools/edit-file.d.ts.map +1 -1
  55. package/dist/src/tools/edit-file.js +16 -222
  56. package/dist/src/tools/edit-file.js.map +1 -1
  57. package/dist/src/tools/file-edit-core.d.ts +31 -0
  58. package/dist/src/tools/file-edit-core.d.ts.map +1 -0
  59. package/dist/src/tools/file-edit-core.js +231 -0
  60. package/dist/src/tools/file-edit-core.js.map +1 -0
  61. package/dist/src/tools/memory-notes.d.ts +13 -0
  62. package/dist/src/tools/memory-notes.d.ts.map +1 -0
  63. package/dist/src/tools/memory-notes.js +382 -0
  64. package/dist/src/tools/memory-notes.js.map +1 -0
  65. package/package.json +1 -1
@@ -0,0 +1,231 @@
1
+ import { access, mkdir, readFile, writeFile } from 'node:fs/promises';
2
+ import { dirname, isAbsolute, relative, resolve } from 'node:path';
3
+ const DIFF_CONTEXT_LINES = 2;
4
+ const MAX_DIFF_PREVIEW_LINES = 80;
5
+ export function isScopedEditInput(raw) {
6
+ if (!raw || typeof raw !== 'object' || Array.isArray(raw)) {
7
+ return false;
8
+ }
9
+ const input = raw;
10
+ const keys = Object.keys(input);
11
+ const allowedKeys = new Set(['path', 'oldText', 'newText', 'replaceAll', 'content', 'createIfMissing']);
12
+ if (keys.some((key) => !allowedKeys.has(key))) {
13
+ return false;
14
+ }
15
+ if (typeof input.path !== 'string' || !input.path.trim()) {
16
+ return false;
17
+ }
18
+ const hasReplaceShape = typeof input.oldText === 'string' && typeof input.newText === 'string';
19
+ const hasWriteShape = typeof input.content === 'string';
20
+ if (hasReplaceShape === hasWriteShape) {
21
+ return false;
22
+ }
23
+ if (hasReplaceShape) {
24
+ return input.replaceAll === undefined || typeof input.replaceAll === 'boolean';
25
+ }
26
+ return input.createIfMissing === undefined || typeof input.createIfMissing === 'boolean';
27
+ }
28
+ export async function executeScopedEdit(raw, options) {
29
+ if (!isScopedEditInput(raw)) {
30
+ return {
31
+ ok: false,
32
+ error: `Invalid input for ${options.toolName}. Use either { "path", "oldText", "newText", "replaceAll?" } or { "path", "content", "createIfMissing?" }.`,
33
+ };
34
+ }
35
+ const targetPath = resolve(options.rootPath, raw.path);
36
+ if (!isInsideRoot(options.rootPath, targetPath)) {
37
+ return {
38
+ ok: false,
39
+ error: `${options.toolName} only writes inside the ${options.rootLabel} (${options.rootPath}). Refusing to modify ${targetPath}.`,
40
+ };
41
+ }
42
+ if ('content' in raw) {
43
+ return writeScopedContent(raw, targetPath, options);
44
+ }
45
+ return replaceScopedContent(raw, targetPath, options);
46
+ }
47
+ export async function previewScopedEdit(raw, options) {
48
+ if (!isScopedEditInput(raw)) {
49
+ return undefined;
50
+ }
51
+ const targetPath = resolve(options.rootPath, raw.path);
52
+ if (!isInsideRoot(options.rootPath, targetPath)) {
53
+ return undefined;
54
+ }
55
+ const scopedPath = toScopedPath(options.rootPath, targetPath);
56
+ if ('content' in raw) {
57
+ const existed = await pathExists(targetPath);
58
+ if (!existed && !raw.createIfMissing) {
59
+ return undefined;
60
+ }
61
+ const previousContent = existed ? await readFile(targetPath, 'utf8') : '';
62
+ return buildDiffPreview(previousContent, raw.content, scopedPath, existed ? 'overwritten' : 'created');
63
+ }
64
+ let current;
65
+ try {
66
+ current = await readFile(targetPath, 'utf8');
67
+ }
68
+ catch {
69
+ return undefined;
70
+ }
71
+ const matchCount = countOccurrences(current, raw.oldText);
72
+ if (matchCount === 0 || (matchCount > 1 && !raw.replaceAll)) {
73
+ return undefined;
74
+ }
75
+ const nextContent = raw.replaceAll ? current.split(raw.oldText).join(raw.newText) : replaceFirst(current, raw.oldText, raw.newText);
76
+ return buildDiffPreview(current, nextContent, scopedPath, 'replaced');
77
+ }
78
+ async function writeScopedContent(input, targetPath, options) {
79
+ const existed = await pathExists(targetPath);
80
+ const previousContent = existed ? await readFile(targetPath, 'utf8') : '';
81
+ if (!existed && !input.createIfMissing) {
82
+ return {
83
+ ok: false,
84
+ error: `${options.subjectLabel} does not exist: ${targetPath}. ${options.creationHint}`,
85
+ };
86
+ }
87
+ await mkdir(dirname(targetPath), { recursive: true });
88
+ await writeFile(targetPath, input.content, 'utf8');
89
+ return {
90
+ ok: true,
91
+ output: {
92
+ path: toScopedPath(options.rootPath, targetPath),
93
+ action: existed ? 'overwritten' : 'created',
94
+ bytesWritten: Buffer.byteLength(input.content, 'utf8'),
95
+ diff: buildDiffPreview(previousContent, input.content, toScopedPath(options.rootPath, targetPath), existed ? 'overwritten' : 'created'),
96
+ },
97
+ };
98
+ }
99
+ async function replaceScopedContent(input, targetPath, options) {
100
+ let current;
101
+ try {
102
+ current = await readFile(targetPath, 'utf8');
103
+ }
104
+ catch (error) {
105
+ return {
106
+ ok: false,
107
+ error: `Failed to read ${targetPath}: ${error instanceof Error ? error.message : String(error)}`,
108
+ };
109
+ }
110
+ const matchCount = countOccurrences(current, input.oldText);
111
+ if (matchCount === 0) {
112
+ return {
113
+ ok: false,
114
+ error: `${options.toolName} could not find the requested oldText in ${targetPath}. Read the ${options.subjectLabel} again and provide an exact match.`,
115
+ };
116
+ }
117
+ if (matchCount > 1 && !input.replaceAll) {
118
+ return {
119
+ ok: false,
120
+ error: `${options.toolName} found ${matchCount} matches for oldText in ${targetPath}. Refine oldText or set replaceAll to true.`,
121
+ };
122
+ }
123
+ const nextContent = input.replaceAll ? current.split(input.oldText).join(input.newText) : replaceFirst(current, input.oldText, input.newText);
124
+ await writeFile(targetPath, nextContent, 'utf8');
125
+ return {
126
+ ok: true,
127
+ output: {
128
+ path: toScopedPath(options.rootPath, targetPath),
129
+ action: 'replaced',
130
+ matchCount,
131
+ bytesWritten: Buffer.byteLength(nextContent, 'utf8'),
132
+ diff: buildDiffPreview(current, nextContent, toScopedPath(options.rootPath, targetPath), 'replaced'),
133
+ },
134
+ };
135
+ }
136
+ function isInsideRoot(rootPath, targetPath) {
137
+ const rel = relative(rootPath, targetPath);
138
+ return rel === '' || (!rel.startsWith('..') && !isAbsolute(rel));
139
+ }
140
+ function countOccurrences(content, search) {
141
+ if (!search) {
142
+ return 0;
143
+ }
144
+ let count = 0;
145
+ let index = 0;
146
+ while (true) {
147
+ const nextIndex = content.indexOf(search, index);
148
+ if (nextIndex === -1) {
149
+ return count;
150
+ }
151
+ count++;
152
+ index = nextIndex + search.length;
153
+ }
154
+ }
155
+ function replaceFirst(content, search, replacement) {
156
+ const index = content.indexOf(search);
157
+ if (index === -1) {
158
+ return content;
159
+ }
160
+ return `${content.slice(0, index)}${replacement}${content.slice(index + search.length)}`;
161
+ }
162
+ function toScopedPath(rootPath, targetPath) {
163
+ const rel = relative(rootPath, targetPath);
164
+ return rel || '.';
165
+ }
166
+ async function pathExists(path) {
167
+ try {
168
+ await access(path);
169
+ return true;
170
+ }
171
+ catch {
172
+ return false;
173
+ }
174
+ }
175
+ function buildDiffPreview(previousContent, nextContent, path, action) {
176
+ const previousLines = splitLines(previousContent);
177
+ const nextLines = splitLines(nextContent);
178
+ const prefix = countCommonPrefix(previousLines, nextLines);
179
+ const suffix = countCommonSuffix(previousLines, nextLines, prefix);
180
+ const previousChangedEnd = previousLines.length - suffix;
181
+ const nextChangedEnd = nextLines.length - suffix;
182
+ const contextStart = Math.max(0, prefix - DIFF_CONTEXT_LINES);
183
+ const previousContextEnd = Math.min(previousLines.length, previousChangedEnd + DIFF_CONTEXT_LINES);
184
+ const nextContextEnd = Math.min(nextLines.length, nextChangedEnd + DIFF_CONTEXT_LINES);
185
+ const lines = [
186
+ `--- ${action === 'created' ? '/dev/null' : `a/${path}`}`,
187
+ `+++ b/${path}`,
188
+ `@@ -${formatHunkRange(contextStart + 1, previousContextEnd - contextStart)} +${formatHunkRange(contextStart + 1, nextContextEnd - contextStart)} @@`,
189
+ ...previousLines.slice(contextStart, prefix).map((line) => ` ${line}`),
190
+ ...previousLines.slice(prefix, previousChangedEnd).map((line) => `-${line}`),
191
+ ...nextLines.slice(prefix, nextChangedEnd).map((line) => `+${line}`),
192
+ ...nextLines.slice(nextChangedEnd, nextContextEnd).map((line) => ` ${line}`),
193
+ ];
194
+ if (lines.length <= MAX_DIFF_PREVIEW_LINES) {
195
+ return { path, action, diff: lines.join('\n'), truncated: false };
196
+ }
197
+ const truncatedLines = [
198
+ ...lines.slice(0, MAX_DIFF_PREVIEW_LINES - 1),
199
+ '... diff preview truncated ...',
200
+ ];
201
+ return { path, action, diff: truncatedLines.join('\n'), truncated: true };
202
+ }
203
+ function splitLines(content) {
204
+ if (!content) {
205
+ return [];
206
+ }
207
+ return content.replace(/\r\n/g, '\n').split('\n').slice(0, content.endsWith('\n') ? -1 : undefined);
208
+ }
209
+ function countCommonPrefix(previousLines, nextLines) {
210
+ let index = 0;
211
+ while (index < previousLines.length && index < nextLines.length && previousLines[index] === nextLines[index]) {
212
+ index++;
213
+ }
214
+ return index;
215
+ }
216
+ function countCommonSuffix(previousLines, nextLines, prefix) {
217
+ let count = 0;
218
+ while (previousLines.length - count - 1 >= prefix &&
219
+ nextLines.length - count - 1 >= prefix &&
220
+ previousLines[previousLines.length - count - 1] === nextLines[nextLines.length - count - 1]) {
221
+ count++;
222
+ }
223
+ return count;
224
+ }
225
+ function formatHunkRange(startLine, length) {
226
+ if (length === 0) {
227
+ return `${startLine},0`;
228
+ }
229
+ return length <= 1 ? `${startLine}` : `${startLine},${length}`;
230
+ }
231
+ //# sourceMappingURL=file-edit-core.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"file-edit-core.js","sourceRoot":"","sources":["../../../src/tools/file-edit-core.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,13 @@
1
+ import type { ToolDefinition } from '../types.js';
2
+ export type MemoryNotesToolOptions = {
3
+ memoryRoot?: string;
4
+ };
5
+ export declare function createListMemoryNotesTool(options?: MemoryNotesToolOptions): ToolDefinition;
6
+ export declare function createReadMemoryNoteTool(options?: MemoryNotesToolOptions): ToolDefinition;
7
+ export declare function createSearchMemoryNotesTool(options?: MemoryNotesToolOptions): ToolDefinition;
8
+ export declare function createEditMemoryNoteTool(options?: MemoryNotesToolOptions): ToolDefinition;
9
+ export declare const listMemoryNotesTool: ToolDefinition;
10
+ export declare const readMemoryNoteTool: ToolDefinition;
11
+ export declare const searchMemoryNotesTool: ToolDefinition;
12
+ export declare const editMemoryNoteTool: ToolDefinition;
13
+ //# sourceMappingURL=memory-notes.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"memory-notes.d.ts","sourceRoot":"","sources":["../../../src/tools/memory-notes.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,cAAc,EAAc,MAAM,aAAa,CAAC;AAG9D,MAAM,MAAM,sBAAsB,GAAG;IACnC,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB,CAAC;AAqBF,wBAAgB,yBAAyB,CAAC,OAAO,GAAE,sBAA2B,GAAG,cAAc,CAqD9F;AAED,wBAAgB,wBAAwB,CAAC,OAAO,GAAE,sBAA2B,GAAG,cAAc,CAwD7F;AAED,wBAAgB,2BAA2B,CAAC,OAAO,GAAE,sBAA2B,GAAG,cAAc,CA4ChG;AAED,wBAAgB,wBAAwB,CAAC,OAAO,GAAE,sBAA2B,GAAG,cAAc,CA8C7F;AAED,eAAO,MAAM,mBAAmB,gBAA8B,CAAC;AAC/D,eAAO,MAAM,kBAAkB,gBAA6B,CAAC;AAC7D,eAAO,MAAM,qBAAqB,gBAAgC,CAAC;AACnE,eAAO,MAAM,kBAAkB,gBAA6B,CAAC"}
@@ -0,0 +1,382 @@
1
+ import { access, readdir, readFile, stat } from 'node:fs/promises';
2
+ import { isAbsolute, relative, resolve } from 'node:path';
3
+ import { spawn } from 'node:child_process';
4
+ import { executeScopedEdit } from './file-edit-core.js';
5
+ const DEFAULT_MEMORY_ROOT = resolve(process.cwd(), '.heddle', 'memory');
6
+ const DEFAULT_MAX_SEARCH_RESULTS = 100;
7
+ export function createListMemoryNotesTool(options = {}) {
8
+ return {
9
+ name: 'list_memory_notes',
10
+ description: 'List markdown notes inside Heddle persistent memory under .heddle/memory. Use this when you want to inspect what durable project knowledge already exists before updating memory. Optional field: path, relative to the memory root, to limit listing to a subdirectory. Returns relative note paths. Example inputs: {}, { "path": "." }, { "path": "architecture" }.',
11
+ parameters: {
12
+ type: 'object',
13
+ additionalProperties: false,
14
+ properties: {
15
+ path: {
16
+ type: 'string',
17
+ description: 'Optional memory-relative subdirectory to list from',
18
+ },
19
+ },
20
+ },
21
+ async execute(raw) {
22
+ if (!isListMemoryNotesInput(raw)) {
23
+ return { ok: false, error: 'Invalid input for list_memory_notes. Optional field: path.' };
24
+ }
25
+ const memoryRoot = resolveMemoryRoot(options);
26
+ const targetPath = resolveMemoryPath(memoryRoot, raw.path ?? '.');
27
+ if (!targetPath.ok) {
28
+ return { ok: false, error: targetPath.error };
29
+ }
30
+ const exists = await pathExists(targetPath.path);
31
+ if (!exists) {
32
+ return { ok: true, output: '' };
33
+ }
34
+ try {
35
+ const info = await stat(targetPath.path);
36
+ if (info.isFile()) {
37
+ return {
38
+ ok: true,
39
+ output: toMemoryRelativePath(memoryRoot, targetPath.path),
40
+ };
41
+ }
42
+ const entries = await listMarkdownNotes(targetPath.path);
43
+ return {
44
+ ok: true,
45
+ output: entries.map((entry) => toMemoryRelativePath(memoryRoot, entry)).join('\n'),
46
+ };
47
+ }
48
+ catch (error) {
49
+ return {
50
+ ok: false,
51
+ error: `Failed to list memory notes in ${targetPath.path}: ${error instanceof Error ? error.message : String(error)}`,
52
+ };
53
+ }
54
+ },
55
+ };
56
+ }
57
+ export function createReadMemoryNoteTool(options = {}) {
58
+ return {
59
+ name: 'read_memory_note',
60
+ description: 'Read a persistent memory note from .heddle/memory. Use this for durable project notes such as architecture, known issues, or common commands. Optional fields: maxLines and offset for paging long notes. The path must stay inside the memory root. Example inputs: { "path": "project-summary.md" }, { "path": "architecture.md", "offset": 40, "maxLines": 80 }.',
61
+ parameters: {
62
+ type: 'object',
63
+ additionalProperties: false,
64
+ properties: {
65
+ path: {
66
+ type: 'string',
67
+ description: 'Memory-relative path to the note',
68
+ },
69
+ maxLines: {
70
+ type: 'number',
71
+ description: 'Maximum number of lines to return',
72
+ },
73
+ offset: {
74
+ type: 'number',
75
+ description: '0-based line offset to start from',
76
+ },
77
+ },
78
+ required: ['path'],
79
+ },
80
+ async execute(raw) {
81
+ if (!isReadMemoryNoteInput(raw)) {
82
+ return { ok: false, error: 'Invalid input for read_memory_note. Required field: path. Optional fields: maxLines, offset.' };
83
+ }
84
+ const memoryRoot = resolveMemoryRoot(options);
85
+ const targetPath = resolveMemoryPath(memoryRoot, raw.path);
86
+ if (!targetPath.ok) {
87
+ return { ok: false, error: targetPath.error };
88
+ }
89
+ try {
90
+ const content = await readFile(targetPath.path, 'utf8');
91
+ return {
92
+ ok: true,
93
+ output: pageText(content, raw.offset, raw.maxLines),
94
+ };
95
+ }
96
+ catch (error) {
97
+ if (isErrorWithCode(error, 'EISDIR')) {
98
+ return {
99
+ ok: false,
100
+ error: `Failed to read ${targetPath.path}: path is a directory, not a file. Use list_memory_notes to inspect memory directories.`,
101
+ };
102
+ }
103
+ return {
104
+ ok: false,
105
+ error: `Failed to read ${targetPath.path}: ${error instanceof Error ? error.message : String(error)}`,
106
+ };
107
+ }
108
+ },
109
+ };
110
+ }
111
+ export function createSearchMemoryNotesTool(options = {}) {
112
+ return {
113
+ name: 'search_memory_notes',
114
+ description: 'Search markdown notes under .heddle/memory using mature command-line search tools. Use this when you want durable project knowledge but do not yet know which note contains it. Input example: { "query": "test command" }. Optional fields: path to limit the search to a subdirectory or note, and maxResults to cap returned lines. Returns grep-style path:line:content output or "No matches found.".',
115
+ parameters: {
116
+ type: 'object',
117
+ additionalProperties: false,
118
+ properties: {
119
+ query: {
120
+ type: 'string',
121
+ description: 'Text to search for in memory notes',
122
+ },
123
+ path: {
124
+ type: 'string',
125
+ description: 'Optional memory-relative subdirectory or note path to search within',
126
+ },
127
+ maxResults: {
128
+ type: 'number',
129
+ description: 'Maximum number of matching lines to return',
130
+ },
131
+ },
132
+ required: ['query'],
133
+ },
134
+ async execute(raw) {
135
+ if (!isSearchMemoryNotesInput(raw)) {
136
+ return { ok: false, error: 'Invalid input for search_memory_notes. Required field: query. Optional fields: path, maxResults.' };
137
+ }
138
+ const memoryRoot = resolveMemoryRoot(options);
139
+ const targetPath = resolveMemoryPath(memoryRoot, raw.path ?? '.');
140
+ if (!targetPath.ok) {
141
+ return { ok: false, error: targetPath.error };
142
+ }
143
+ const exists = await pathExists(targetPath.path);
144
+ if (!exists) {
145
+ return { ok: true, output: 'No matches found.' };
146
+ }
147
+ const searchResult = await searchWithCli(memoryRoot, targetPath.path, raw.query, sanitizeMaxResults(raw.maxResults));
148
+ return searchResult;
149
+ },
150
+ };
151
+ }
152
+ export function createEditMemoryNoteTool(options = {}) {
153
+ return {
154
+ name: 'edit_memory_note',
155
+ description: 'Create or edit a persistent markdown note inside .heddle/memory. Use this for stable reusable project knowledge that should survive future sessions. Use { "path", "oldText", "newText" } for an exact replacement, optionally with replaceAll, or use { "path", "content", "createIfMissing" } to overwrite an existing note or create a new one explicitly. This tool does not require approval; you should maintain durable memory proactively at sensible checkpoints. If the built-in edit flow is insufficient, it is acceptable to use mature shell tools against the memory directory.',
156
+ parameters: {
157
+ type: 'object',
158
+ additionalProperties: false,
159
+ properties: {
160
+ path: {
161
+ type: 'string',
162
+ description: 'Memory-relative markdown note path',
163
+ },
164
+ oldText: {
165
+ type: 'string',
166
+ description: 'Existing text to replace exactly',
167
+ },
168
+ newText: {
169
+ type: 'string',
170
+ description: 'Replacement text for oldText',
171
+ },
172
+ replaceAll: {
173
+ type: 'boolean',
174
+ description: 'Replace every matching occurrence instead of requiring a single exact match',
175
+ },
176
+ content: {
177
+ type: 'string',
178
+ description: 'Full note content to write',
179
+ },
180
+ createIfMissing: {
181
+ type: 'boolean',
182
+ description: 'Allow creating the note if it does not already exist when using content',
183
+ },
184
+ },
185
+ required: ['path'],
186
+ },
187
+ async execute(raw) {
188
+ return executeScopedEdit(raw, {
189
+ toolName: 'edit_memory_note',
190
+ rootPath: resolveMemoryRoot(options),
191
+ rootLabel: 'memory root',
192
+ subjectLabel: 'memory note',
193
+ creationHint: 'Set createIfMissing to true if you want edit_memory_note to create it.',
194
+ });
195
+ },
196
+ };
197
+ }
198
+ export const listMemoryNotesTool = createListMemoryNotesTool();
199
+ export const readMemoryNoteTool = createReadMemoryNoteTool();
200
+ export const searchMemoryNotesTool = createSearchMemoryNotesTool();
201
+ export const editMemoryNoteTool = createEditMemoryNoteTool();
202
+ function resolveMemoryRoot(options) {
203
+ return resolve(options.memoryRoot ?? DEFAULT_MEMORY_ROOT);
204
+ }
205
+ function resolveMemoryPath(memoryRoot, requestedPath) {
206
+ if (!requestedPath.trim()) {
207
+ return { ok: false, error: `Memory note paths must be non-empty and stay inside ${memoryRoot}.` };
208
+ }
209
+ const targetPath = resolve(memoryRoot, requestedPath);
210
+ const rel = relative(memoryRoot, targetPath);
211
+ if (rel.startsWith('..') || isAbsolute(rel)) {
212
+ return { ok: false, error: `Memory note paths must stay inside ${memoryRoot}. Refusing to access ${targetPath}.` };
213
+ }
214
+ return { ok: true, path: targetPath };
215
+ }
216
+ async function listMarkdownNotes(root) {
217
+ const results = [];
218
+ const entries = await readdir(root, { withFileTypes: true });
219
+ for (const entry of entries) {
220
+ const nextPath = resolve(root, entry.name);
221
+ if (entry.isDirectory()) {
222
+ results.push(...await listMarkdownNotes(nextPath));
223
+ continue;
224
+ }
225
+ if (entry.isFile() && isMarkdownPath(nextPath)) {
226
+ results.push(nextPath);
227
+ }
228
+ }
229
+ return results.sort();
230
+ }
231
+ function pageText(content, offset, maxLines) {
232
+ const lines = content.split('\n');
233
+ const start = offset && offset > 0 ? Math.floor(offset) : 0;
234
+ const end = maxLines && maxLines > 0 ? start + Math.floor(maxLines) : undefined;
235
+ if (start > 0 || end !== undefined) {
236
+ return lines.slice(start, end).join('\n');
237
+ }
238
+ return content;
239
+ }
240
+ function sanitizeMaxResults(value) {
241
+ if (!value || !Number.isFinite(value) || value <= 0) {
242
+ return DEFAULT_MAX_SEARCH_RESULTS;
243
+ }
244
+ return Math.min(Math.floor(value), DEFAULT_MAX_SEARCH_RESULTS);
245
+ }
246
+ function toMemoryRelativePath(memoryRoot, filePath) {
247
+ const rel = relative(memoryRoot, filePath);
248
+ return rel || '.';
249
+ }
250
+ function isMarkdownPath(path) {
251
+ return /\.md$/i.test(path);
252
+ }
253
+ async function pathExists(path) {
254
+ try {
255
+ await access(path);
256
+ return true;
257
+ }
258
+ catch {
259
+ return false;
260
+ }
261
+ }
262
+ function isListMemoryNotesInput(raw) {
263
+ if (raw === undefined) {
264
+ return true;
265
+ }
266
+ if (!raw || typeof raw !== 'object' || Array.isArray(raw)) {
267
+ return false;
268
+ }
269
+ const input = raw;
270
+ const keys = Object.keys(input);
271
+ if (keys.some((key) => key !== 'path')) {
272
+ return false;
273
+ }
274
+ return input.path === undefined || typeof input.path === 'string';
275
+ }
276
+ function isReadMemoryNoteInput(raw) {
277
+ if (!raw || typeof raw !== 'object' || Array.isArray(raw)) {
278
+ return false;
279
+ }
280
+ const input = raw;
281
+ const keys = Object.keys(input);
282
+ if (keys.some((key) => key !== 'path' && key !== 'maxLines' && key !== 'offset')) {
283
+ return false;
284
+ }
285
+ return (typeof input.path === 'string' &&
286
+ (input.maxLines === undefined || typeof input.maxLines === 'number') &&
287
+ (input.offset === undefined || typeof input.offset === 'number'));
288
+ }
289
+ function isSearchMemoryNotesInput(raw) {
290
+ if (!raw || typeof raw !== 'object' || Array.isArray(raw)) {
291
+ return false;
292
+ }
293
+ const input = raw;
294
+ const keys = Object.keys(input);
295
+ if (keys.some((key) => key !== 'query' && key !== 'path' && key !== 'maxResults')) {
296
+ return false;
297
+ }
298
+ return (typeof input.query === 'string' &&
299
+ input.query.trim().length > 0 &&
300
+ (input.path === undefined || typeof input.path === 'string') &&
301
+ (input.maxResults === undefined || typeof input.maxResults === 'number'));
302
+ }
303
+ function isErrorWithCode(error, code) {
304
+ return error instanceof Error && 'code' in error && error.code === code;
305
+ }
306
+ async function searchWithCli(memoryRoot, targetPath, query, maxResults) {
307
+ const relativeTarget = toMemoryRelativePath(memoryRoot, targetPath);
308
+ const normalizedTarget = relativeTarget === '.' ? '.' : relativeTarget;
309
+ const rgResult = await runSearchCommand('rg', ['-n', '--no-heading', '--glob', '*.md', query, normalizedTarget], memoryRoot);
310
+ if (rgResult.kind === 'ok') {
311
+ return {
312
+ ok: true,
313
+ output: trimSearchOutput(rgResult.stdout, maxResults),
314
+ };
315
+ }
316
+ if (rgResult.kind === 'nonzero_no_match') {
317
+ return { ok: true, output: 'No matches found.' };
318
+ }
319
+ if (rgResult.kind !== 'missing_binary') {
320
+ return {
321
+ ok: false,
322
+ error: `Failed to search memory notes in ${targetPath}: ${rgResult.error}`,
323
+ };
324
+ }
325
+ const grepResult = await runSearchCommand('grep', ['-R', '-n', '--include=*.md', query, normalizedTarget], memoryRoot);
326
+ if (grepResult.kind === 'ok') {
327
+ return {
328
+ ok: true,
329
+ output: trimSearchOutput(grepResult.stdout, maxResults),
330
+ };
331
+ }
332
+ if (grepResult.kind === 'nonzero_no_match') {
333
+ return { ok: true, output: 'No matches found.' };
334
+ }
335
+ return {
336
+ ok: false,
337
+ error: `Failed to search memory notes in ${targetPath}: ${grepResult.error}`,
338
+ };
339
+ }
340
+ function trimSearchOutput(stdout, maxResults) {
341
+ const lines = stdout
342
+ .split('\n')
343
+ .map((line) => line.trimEnd().replace(/^\.\//, ''))
344
+ .filter((line) => line.length > 0)
345
+ .slice(0, maxResults);
346
+ return lines.length > 0 ? lines.join('\n') : 'No matches found.';
347
+ }
348
+ async function runSearchCommand(binary, args, cwd) {
349
+ return new Promise((resolvePromise) => {
350
+ const child = spawn(binary, args, {
351
+ cwd,
352
+ stdio: ['ignore', 'pipe', 'pipe'],
353
+ });
354
+ let stdout = '';
355
+ let stderr = '';
356
+ child.stdout.on('data', (chunk) => {
357
+ stdout += chunk.toString();
358
+ });
359
+ child.stderr.on('data', (chunk) => {
360
+ stderr += chunk.toString();
361
+ });
362
+ child.on('error', (error) => {
363
+ if ('code' in error && error.code === 'ENOENT') {
364
+ resolvePromise({ kind: 'missing_binary', error: `${binary} is not available in the environment.` });
365
+ return;
366
+ }
367
+ resolvePromise({ kind: 'error', error: error.message });
368
+ });
369
+ child.on('close', (code) => {
370
+ if (code === 0) {
371
+ resolvePromise({ kind: 'ok', stdout });
372
+ return;
373
+ }
374
+ if (code === 1) {
375
+ resolvePromise({ kind: 'nonzero_no_match', error: stderr.trim() || `${binary} returned exit code 1` });
376
+ return;
377
+ }
378
+ resolvePromise({ kind: 'error', error: stderr.trim() || `${binary} returned exit code ${code ?? 'unknown'}` });
379
+ });
380
+ });
381
+ }
382
+ //# sourceMappingURL=memory-notes.js.map