@fonlan/dsh-gitmemo 0.1.5 → 0.3.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.
- package/README.md +63 -65
- package/README.zh.md +55 -61
- package/cordis.patch.yml +10 -3
- package/lib/cli.d.ts +2 -0
- package/lib/cli.js +173 -0
- package/lib/index.d.ts +42 -38
- package/lib/index.js +243 -154
- package/lib/mem.d.ts +274 -65
- package/lib/mem.js +1442 -470
- package/lib/migrate.d.ts +73 -0
- package/lib/migrate.js +566 -0
- package/package.json +13 -7
package/lib/mem.d.ts
CHANGED
|
@@ -1,106 +1,315 @@
|
|
|
1
|
+
/** Schema version stored in `.gitmemo-format`. */
|
|
2
|
+
export declare const GITMEMO_FORMAT_VERSION = "2";
|
|
3
|
+
/** Format marker file tracked in the .mem repo (content = schema version). */
|
|
4
|
+
export declare const FORMAT_FILE = ".gitmemo-format";
|
|
5
|
+
/** Entry directory inside the .mem repo. */
|
|
6
|
+
export declare const ENTRY_DIR = "entries";
|
|
7
|
+
/** Sibling lock file (usable before `.mem` exists). */
|
|
8
|
+
export declare const LOCK_FILE_NAME = ".mem.gitmemo.lock";
|
|
9
|
+
/** Sibling journal used to recover an interrupted migration directory swap. */
|
|
10
|
+
export declare const MIGRATION_JOURNAL_NAME = ".mem.gitmemo-migration.json";
|
|
11
|
+
/** Max entry content size. */
|
|
12
|
+
export declare const MAX_CONTENT_BYTES: number;
|
|
13
|
+
/** Error type thrown by every gitmemo operation. */
|
|
14
|
+
export declare class GitMemoError extends Error {
|
|
15
|
+
}
|
|
1
16
|
/** Per-invocation engine options. */
|
|
2
17
|
export interface GitMemoConfig {
|
|
3
|
-
/**
|
|
4
|
-
memDirName?: string;
|
|
5
|
-
/** Maximum number of search hits returned per call. Default 20. */
|
|
18
|
+
/** Max search hits returned per call (page size). Default 20. */
|
|
6
19
|
searchLimit?: number;
|
|
7
|
-
/** Whether `.mem` branch follows the project branch on write. Default true. */
|
|
8
|
-
branchAlign?: boolean;
|
|
9
20
|
/** Per git subprocess timeout in milliseconds. Default 60000. */
|
|
10
21
|
gitTimeoutMs?: number;
|
|
11
|
-
/**
|
|
12
|
-
|
|
22
|
+
/** Cross-process lock wait timeout in milliseconds. Default 30000. */
|
|
23
|
+
lockTimeoutMs?: number;
|
|
24
|
+
/** @deprecated parsed for compatibility only — the memory dir is fixed at `<projectRoot>/.mem`. */
|
|
25
|
+
memDirName?: string;
|
|
26
|
+
/** @deprecated parsed for compatibility only — `.mem` always stays on main. */
|
|
27
|
+
branchAlign?: boolean;
|
|
28
|
+
/** @deprecated parsed for compatibility only — session-start recent seeding was removed. */
|
|
29
|
+
recentContextLimit?: number;
|
|
13
30
|
}
|
|
14
|
-
/** One search hit
|
|
31
|
+
/** One scored search hit. */
|
|
15
32
|
export interface SearchHit {
|
|
16
33
|
hash: string;
|
|
17
34
|
title: string;
|
|
18
35
|
date: string;
|
|
36
|
+
summary: string;
|
|
37
|
+
keywords: string[];
|
|
38
|
+
score: number;
|
|
39
|
+
matched_keywords: string[];
|
|
40
|
+
}
|
|
41
|
+
export interface SearchOutput {
|
|
42
|
+
snapshot: string;
|
|
43
|
+
total: number;
|
|
44
|
+
next_skip: number | null;
|
|
45
|
+
results: SearchHit[];
|
|
46
|
+
legacy?: boolean;
|
|
47
|
+
warning?: string;
|
|
48
|
+
diagnostics?: string[];
|
|
19
49
|
}
|
|
20
|
-
export interface
|
|
21
|
-
|
|
50
|
+
export interface SearchOptions {
|
|
51
|
+
skip?: number;
|
|
52
|
+
/** Snapshot returned by the first page — pass back unchanged for stable pagination. */
|
|
53
|
+
snapshot?: string;
|
|
54
|
+
/** @deprecated legacy `mode` — recall is always OR now. */
|
|
55
|
+
mode?: "and" | "or" | "auto";
|
|
56
|
+
}
|
|
57
|
+
export interface WriteInput {
|
|
58
|
+
/** Short "[module] action + object" title (single line, 1-200 chars). */
|
|
59
|
+
title: string;
|
|
60
|
+
/** 1-3 sentence summary (multi-line allowed, <= 4000 chars). */
|
|
61
|
+
summary: string;
|
|
62
|
+
/** 2-12 keywords (1-64 chars each); include 中英文 synonyms. Legacy writes may pass fewer. */
|
|
63
|
+
keywords: string[];
|
|
64
|
+
/** Memory markdown body (engine generates front matter). */
|
|
65
|
+
content: string;
|
|
66
|
+
related_branches?: string[];
|
|
67
|
+
related_paths?: string[];
|
|
68
|
+
}
|
|
69
|
+
/** One-version compatibility shape accepted by the engine, not model-facing tools. */
|
|
70
|
+
export interface LegacyWriteInput {
|
|
22
71
|
title: string;
|
|
23
|
-
|
|
72
|
+
summary?: string;
|
|
73
|
+
keywords?: string[];
|
|
24
74
|
content?: string;
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
/** Existing entry path to commit in place: `entries/foo.md`, `.mem/entries/foo.md`, or an absolute path under .mem. */
|
|
28
|
-
file?: string;
|
|
29
|
-
/** Optional single-line commit body. */
|
|
75
|
+
related_branches?: string[];
|
|
76
|
+
related_paths?: string[];
|
|
30
77
|
body?: string;
|
|
31
|
-
|
|
32
|
-
|
|
78
|
+
body_file?: string;
|
|
79
|
+
content_file?: string;
|
|
80
|
+
/** Old in-place updates conflict with immutable entries and are always rejected. */
|
|
81
|
+
file?: string;
|
|
33
82
|
}
|
|
34
83
|
export interface WriteResult {
|
|
35
84
|
hash: string;
|
|
36
85
|
file: string;
|
|
86
|
+
legacy?: boolean;
|
|
37
87
|
}
|
|
38
|
-
export interface
|
|
39
|
-
|
|
40
|
-
|
|
88
|
+
export interface ReadResult {
|
|
89
|
+
hash: string;
|
|
90
|
+
file: string;
|
|
91
|
+
content: string;
|
|
92
|
+
legacy?: boolean;
|
|
93
|
+
}
|
|
94
|
+
export interface DeleteInput {
|
|
95
|
+
commit_hash: string;
|
|
96
|
+
reason: string;
|
|
97
|
+
}
|
|
98
|
+
export interface ReplaceInput extends WriteInput {
|
|
99
|
+
commit_hash: string;
|
|
100
|
+
}
|
|
101
|
+
/** Parsed structured commit message. */
|
|
102
|
+
export interface ParsedCommitMessage {
|
|
103
|
+
subject: string;
|
|
104
|
+
summary: string;
|
|
105
|
+
type: "add" | "replace" | "delete" | "unknown";
|
|
106
|
+
keywords: string[];
|
|
107
|
+
digest?: string;
|
|
108
|
+
deletes?: string;
|
|
109
|
+
replaces?: string;
|
|
110
|
+
legacy: boolean;
|
|
111
|
+
}
|
|
112
|
+
interface LogRecord {
|
|
113
|
+
hash: string;
|
|
114
|
+
committerTime: number;
|
|
115
|
+
subject: string;
|
|
116
|
+
body: string;
|
|
117
|
+
added: string[];
|
|
118
|
+
deleted: string[];
|
|
119
|
+
message: ParsedCommitMessage;
|
|
120
|
+
}
|
|
121
|
+
interface TransactionJournal {
|
|
122
|
+
operationId: string;
|
|
123
|
+
operation: string;
|
|
124
|
+
phase: string;
|
|
125
|
+
baseHead: string;
|
|
126
|
+
add: string[];
|
|
127
|
+
delete: string[];
|
|
128
|
+
digest?: string;
|
|
129
|
+
createdAt: string;
|
|
130
|
+
}
|
|
131
|
+
export interface MigrationSwapJournal {
|
|
132
|
+
operationId: string;
|
|
133
|
+
sourceDir: string;
|
|
134
|
+
targetDir: string;
|
|
135
|
+
oldDir: string;
|
|
136
|
+
tmpDir: string;
|
|
137
|
+
phase: "prepared" | "old-renamed" | "new-installed";
|
|
138
|
+
createdAt: string;
|
|
139
|
+
}
|
|
140
|
+
interface CodeContext {
|
|
141
|
+
branch: string;
|
|
142
|
+
commit: string;
|
|
143
|
+
}
|
|
144
|
+
interface GitOptions {
|
|
145
|
+
input?: string;
|
|
146
|
+
env?: NodeJS.ProcessEnv;
|
|
147
|
+
}
|
|
148
|
+
/** Run one git subprocess (internal; exported for migrate/cli). */
|
|
149
|
+
export declare function git(dir: string, args: string[], gitTimeoutMs: number, opts?: GitOptions): Promise<string>;
|
|
150
|
+
/**
|
|
151
|
+
* Commit a message via stdin, retrying once with a built-in identity when the
|
|
152
|
+
* ambient git configuration has no user.name/user.email (fresh machines,
|
|
153
|
+
* CI, tests). The retry only adds env defaults — it never overrides an
|
|
154
|
+
* explicitly configured identity.
|
|
155
|
+
*/
|
|
156
|
+
export declare function commitWithIdentity(memDir: string, message: string, gitTimeoutMs: number, opts?: GitOptions): Promise<string>;
|
|
157
|
+
/** `git init` with an explicit `main` branch (falls back for old git). */
|
|
158
|
+
export declare function gitInitMain(dir: string, gitTimeoutMs: number): Promise<void>;
|
|
159
|
+
/**
|
|
160
|
+
* Resolve the code project root for a session working directory (plan 3.1):
|
|
161
|
+
* explicit config > git work-tree root > session cwd.
|
|
162
|
+
*/
|
|
163
|
+
export declare function resolveProjectRoot(cwd: string, gitTimeoutMs?: number): Promise<string>;
|
|
164
|
+
/**
|
|
165
|
+
* Query/write normalization contract: NFKC + Unicode default case folding +
|
|
166
|
+
* whitespace collapsing to single ASCII spaces. Both query keywords and the
|
|
167
|
+
* `GitMemo-Search-Text` projections use this exact function, so the first
|
|
168
|
+
* grep stage never depends on the platform locale.
|
|
169
|
+
*/
|
|
170
|
+
export declare function normalizeText(input: string): string;
|
|
171
|
+
export declare function normalizeKeyword(input: string): string;
|
|
172
|
+
export interface ValidatedWriteInput {
|
|
173
|
+
title: string;
|
|
174
|
+
summary: string;
|
|
175
|
+
keywords: string[];
|
|
176
|
+
content: string;
|
|
177
|
+
related_branches: string[];
|
|
178
|
+
related_paths: string[];
|
|
41
179
|
}
|
|
42
|
-
/**
|
|
43
|
-
|
|
180
|
+
/**
|
|
181
|
+
* Validate a write/replace payload (plan 5.3). `legacy` relaxes the keyword
|
|
182
|
+
* minimum to 0 (legacy adapter and migration entries); everything else is
|
|
183
|
+
* enforced identically.
|
|
184
|
+
*/
|
|
185
|
+
export declare function validateWriteInput(input: WriteInput, legacy?: boolean): ValidatedWriteInput;
|
|
186
|
+
/**
|
|
187
|
+
* Stable digest over the semantic payload only: title + summary +
|
|
188
|
+
* normalized+sorted keywords + content. Write time, code branch/SHA and
|
|
189
|
+
* related branches/paths are excluded, so the same conclusion never produces
|
|
190
|
+
* an exact duplicate because of when/where it was written, and a withdrawn
|
|
191
|
+
* conclusion can be re-written later.
|
|
192
|
+
*/
|
|
193
|
+
export declare function computeDigest(title: string, summary: string, keywords: string[], content: string): string;
|
|
194
|
+
/** UTC millisecond timestamp for filenames: `20260823T113612.123Z`. */
|
|
195
|
+
export declare function utcMs(date: Date): string;
|
|
196
|
+
/** ISO date for a committer timestamp; falls back to the epoch for out-of-range values. */
|
|
197
|
+
export declare function safeIsoDate(committerTime: number): string;
|
|
198
|
+
export declare function slugifyTitle(title: string): string;
|
|
199
|
+
/**
|
|
200
|
+
* Create an entry file under entries/ with an exclusive create; on collision
|
|
201
|
+
* append a numeric suffix. Never overwrites an existing file.
|
|
202
|
+
*/
|
|
203
|
+
export declare function createEntryFile(memDir: string, baseName: string, content: string): Promise<string>;
|
|
204
|
+
/** Build the full entry markdown (engine-generated front matter + body). */
|
|
205
|
+
export declare function buildEntryMarkdown(input: ValidatedWriteInput, ctx: CodeContext & {
|
|
206
|
+
relatedBranches: string[];
|
|
207
|
+
}, digest: string, date: Date): string;
|
|
208
|
+
/** ADD commit message (plan 4.1). */
|
|
209
|
+
export declare function buildAddMessage(title: string, summary: string, keywords: string[], digest: string, legacy?: boolean): string;
|
|
210
|
+
/** DELETE commit message (plan 4.2). */
|
|
211
|
+
export declare function buildDeleteMessage(entryName: string, reason: string, deletesHash: string): string;
|
|
212
|
+
/** REPLACE commit message (plan 4.3). */
|
|
213
|
+
export declare function buildReplaceMessage(title: string, summary: string, keywords: string[], digest: string, replacesHash: string): string;
|
|
214
|
+
/**
|
|
215
|
+
* Parse a commit message into logical search fields. The trailer block is the
|
|
216
|
+
* contiguous run at the end; everything between subject and the block is the
|
|
217
|
+
* summary. Malformed input degrades to `type: "unknown"` (legacy commits).
|
|
218
|
+
*/
|
|
219
|
+
export declare function parseCommitMessage(raw: string): ParsedCommitMessage;
|
|
220
|
+
/**
|
|
221
|
+
* Parse the NUL-delimited log stream
|
|
222
|
+
* (`--format=%x00%H%x00%ct%x00%s%x00%B%x00 --name-status --no-renames`).
|
|
223
|
+
* Every commit contributes exactly five NUL-separated tokens followed by the
|
|
224
|
+
* status tail, so records align rigidly regardless of newlines in bodies
|
|
225
|
+
* (git itself forbids NUL inside commit messages).
|
|
226
|
+
*/
|
|
227
|
+
export declare function parseLogRecords(output: string): LogRecord[];
|
|
228
|
+
export declare function writeMigrationSwapJournal(root: string, journal: MigrationSwapJournal): Promise<void>;
|
|
229
|
+
/**
|
|
230
|
+
* Recover the narrow two-rename migration swap window before any operation is
|
|
231
|
+
* allowed to auto-initialize `.mem`. This prevents a crash between renames
|
|
232
|
+
* from being mistaken for a genuinely fresh project.
|
|
233
|
+
*/
|
|
234
|
+
export declare function recoverMigrationSwap(root: string, gitTimeoutMs: number): Promise<void>;
|
|
235
|
+
/** Detect the repo format from `.gitmemo-format` at the given snapshot. */
|
|
236
|
+
export declare function repoFormat(memDir: string, gitTimeoutMs: number, snapshot?: string): Promise<"new" | "legacy">;
|
|
237
|
+
/** Resolve a ref/hash to a full commit hash or throw. */
|
|
238
|
+
export declare function resolveCommit(memDir: string, ref: string, gitTimeoutMs: number): Promise<string>;
|
|
239
|
+
/** Entry files added by one commit (single ADD/REPLACE mapping). */
|
|
240
|
+
export declare function addedFilesOf(memDir: string, hash: string, gitTimeoutMs: number): Promise<string[]>;
|
|
241
|
+
/**
|
|
242
|
+
* Cross-process transaction lock (plan 8.1): exclusive create of a sibling
|
|
243
|
+
* lock file, backed by an in-process mutex on the same canonical key. Used by
|
|
244
|
+
* init/write/delete/replace/migrate; search/read take it briefly to resolve
|
|
245
|
+
* their immutable snapshot (read barrier) and release before scanning.
|
|
246
|
+
*/
|
|
247
|
+
export declare function withLock<T>(root: string, opts: {
|
|
248
|
+
lockTimeoutMs: number;
|
|
249
|
+
}, operation: string, fn: () => Promise<T>): Promise<T>;
|
|
250
|
+
/**
|
|
251
|
+
* Strict journal recovery (plan 8.3): only when HEAD still equals the
|
|
252
|
+
* journal's base and the worktree/index changes are exactly the planned
|
|
253
|
+
* paths is the rollback performed; anything else aborts with repair guidance.
|
|
254
|
+
*/
|
|
255
|
+
export declare function rollbackJournal(memDir: string, journal: TransactionJournal, gitTimeoutMs: number): Promise<void>;
|
|
256
|
+
/**
|
|
257
|
+
* Initialize a new-format `.mem` repo (plan 8.4): build the whole repo in a
|
|
258
|
+
* sibling temp directory (init commit carries `.gitmemo-format` + `.gitkeep`
|
|
259
|
+
* on `main`), then atomically rename it into place. Never converts an
|
|
260
|
+
* existing legacy repo. Idempotent for already-initialized repos.
|
|
261
|
+
*/
|
|
262
|
+
export declare function ensureInit(root: string, memDir: string, gitTimeoutMs: number): Promise<void>;
|
|
44
263
|
/**
|
|
45
264
|
* Git-backed long-term memory engine operating on one project root.
|
|
265
|
+
* Every operation auto-initializes a new-format repo; legacy repos are
|
|
266
|
+
* read-only for search/read until migrated via the CLI.
|
|
46
267
|
*/
|
|
47
268
|
export declare class GitMemo {
|
|
48
269
|
readonly root: string;
|
|
49
270
|
readonly memDir: string;
|
|
50
271
|
private readonly config;
|
|
51
272
|
constructor(root: string, config?: GitMemoConfig);
|
|
52
|
-
/**
|
|
53
|
-
|
|
54
|
-
private projectBranch;
|
|
55
|
-
private memBranch;
|
|
273
|
+
/** Initialize the memory repo when missing (idempotent). */
|
|
274
|
+
init(): Promise<string>;
|
|
56
275
|
/**
|
|
57
|
-
*
|
|
58
|
-
*
|
|
276
|
+
* Search past memories (plan 6): fixed-string OR grep recall over commit
|
|
277
|
+
* messages only, active-entry filtering, field-level matching and scoring,
|
|
278
|
+
* snapshot-stable pagination, in-process LRU.
|
|
279
|
+
*
|
|
280
|
+
* @param keywords - 1-12 keywords (legacy callers may pass a comma-separated string).
|
|
281
|
+
* @param options.skip - pagination offset.
|
|
282
|
+
* @param options.snapshot - pass back the snapshot from the first page.
|
|
59
283
|
*/
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
private syncBranch;
|
|
284
|
+
search(keywords: string[] | string, options?: SearchOptions): Promise<SearchOutput>;
|
|
285
|
+
private computeSearch;
|
|
63
286
|
/**
|
|
64
|
-
*
|
|
65
|
-
*
|
|
66
|
-
*
|
|
67
|
-
* @param mode - "and" (strict), "or" (broad), or "auto" (AND first, OR fallback).
|
|
287
|
+
* Read one memory entry by create/replace commit hash. Historical entries
|
|
288
|
+
* (deleted or replaced) stay readable for audit; `mem_read` accepts legacy
|
|
289
|
+
* hashes too.
|
|
68
290
|
*/
|
|
69
|
-
|
|
70
|
-
private runSearchMode;
|
|
291
|
+
read(commitHash: string): Promise<ReadResult>;
|
|
71
292
|
/**
|
|
72
|
-
*
|
|
73
|
-
*
|
|
74
|
-
*
|
|
293
|
+
* Write a memory entry (plan 5.3). Entries are immutable: each write
|
|
294
|
+
* creates a brand-new file `entries/<utc-ms>-<digest-prefix>-<slug>.md`
|
|
295
|
+
* with one ADD commit. `legacy = true` relaxes the keyword minimum to 0
|
|
296
|
+
* (compatibility adapter + migration reimports).
|
|
75
297
|
*/
|
|
76
|
-
|
|
77
|
-
/** Active (non-deleted) entry files at HEAD. */
|
|
78
|
-
private activeEntries;
|
|
79
|
-
/** Shared git-log scan: keyword-grep optional, active-entry filtering, skip/limit. */
|
|
80
|
-
private scanLog;
|
|
298
|
+
write(input: WriteInput | LegacyWriteInput, legacy?: boolean): Promise<WriteResult>;
|
|
81
299
|
/**
|
|
82
|
-
*
|
|
83
|
-
*
|
|
84
|
-
* context can be registered before the first request assembles.
|
|
300
|
+
* Delete a memory entry (plan 5.4). Only withdraws the ACTIVE entry of a
|
|
301
|
+
* create/replace hash; stale hashes are rejected.
|
|
85
302
|
*/
|
|
86
|
-
|
|
87
|
-
private acceptRecord;
|
|
88
|
-
/** Resolve the entry file touched by a commit (first file under entries/). */
|
|
89
|
-
private entryFileFromCommit;
|
|
90
|
-
/** Read one memory entry by commit hash. */
|
|
91
|
-
read(commitHash: string): Promise<{
|
|
303
|
+
delete(input: DeleteInput): Promise<{
|
|
92
304
|
file: string;
|
|
93
|
-
content: string;
|
|
94
305
|
}>;
|
|
95
306
|
/**
|
|
96
|
-
*
|
|
97
|
-
*
|
|
98
|
-
*
|
|
307
|
+
* Replace an active entry (plan 5.5): one transaction, one commit deletes
|
|
308
|
+
* the old file and adds the new one (never delete-then-write). The active
|
|
309
|
+
* digest dedupe excludes the entry being replaced, so replacing with the
|
|
310
|
+
* same digest but updated non-digest metadata (related branches/paths) is
|
|
311
|
+
* legal; colliding with any OTHER active entry is rejected.
|
|
99
312
|
*/
|
|
100
|
-
|
|
101
|
-
/** Delete a memory entry by commit hash. */
|
|
102
|
-
delete(commitHash: string): Promise<string>;
|
|
313
|
+
replace(input: ReplaceInput): Promise<WriteResult>;
|
|
103
314
|
}
|
|
104
|
-
|
|
105
|
-
export declare function ensureInit(memDir: string, gitTimeoutMs?: number): Promise<void>;
|
|
106
|
-
export { isEntriesPath };
|
|
315
|
+
export {};
|