jinzd-ai-cli 0.4.241 → 0.4.243

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 (42) hide show
  1. package/README.md +2 -2
  2. package/dist/{batch-K66BLTLJ.js → batch-CUVZUPRK.js} +2 -2
  3. package/dist/{chat-index-7HXBWQFH.js → chat-index-ALUHFJKI.js} +3 -2
  4. package/dist/{chat-index-WYXYD7YP.js → chat-index-ML5FF5L6.js} +2 -1
  5. package/dist/{chunk-SMQHEJSH.js → chunk-66CE3E6X.js} +1 -1
  6. package/dist/chunk-B5TYJO7V.js +123 -0
  7. package/dist/{chunk-PGHTAKIA.js → chunk-DJGCG7SF.js} +1 -1
  8. package/dist/{chunk-2QQKS56X.js → chunk-DT77O33T.js} +1 -1
  9. package/dist/{chunk-2LLQOMMW.js → chunk-EMFCHE2B.js} +1 -1
  10. package/dist/chunk-GEWPLNKA.js +164 -0
  11. package/dist/{chunk-LXSUPLCK.js → chunk-H3ARWBYR.js} +2 -2
  12. package/dist/{chunk-3AIR7N5M.js → chunk-HNJX3N2V.js} +5 -5
  13. package/dist/{chunk-5MYPIQ3Y.js → chunk-IGPFYNZA.js} +4 -2
  14. package/dist/{chunk-GORPFFV4.js → chunk-KILKF2VN.js} +97 -254
  15. package/dist/{chunk-JMP3LJC2.js → chunk-KWQKQVPH.js} +4 -2
  16. package/dist/{chunk-NAQPP3GZ.js → chunk-L5IRDRWG.js} +1 -1
  17. package/dist/{chunk-UO3XWZFE.js → chunk-NV63EYFL.js} +3 -119
  18. package/dist/{chunk-KCEO2XJ4.js → chunk-PJ3ONBWZ.js} +0 -119
  19. package/dist/{chunk-ZPXMT2B6.js → chunk-PLEXBFCR.js} +4 -163
  20. package/dist/chunk-PNNZRJ67.js +523 -0
  21. package/dist/{chunk-CHFFEVVT.js → chunk-QNK7GUUB.js} +3 -3
  22. package/dist/chunk-TY4FSEZS.js +432 -0
  23. package/dist/{chunk-IV6GCDVR.js → chunk-X73Y4JZI.js} +7 -1
  24. package/dist/chunk-YVTASHS5.js +121 -0
  25. package/dist/{ci-T2ABDMMQ.js → ci-3O3JR6FX.js} +4 -4
  26. package/dist/{ci-format-S2PPU27O.js → ci-format-KGWF6KOU.js} +2 -2
  27. package/dist/{constants-RS7OUU7X.js → constants-NMFRZ5LW.js} +1 -1
  28. package/dist/{doctor-cli-TGAW6RAP.js → doctor-cli-4QUHTJZ4.js} +4 -4
  29. package/dist/electron-server.js +446 -579
  30. package/dist/{hub-GL5ZU6WS.js → hub-2GXFPWOR.js} +2 -2
  31. package/dist/index.js +273 -62
  32. package/dist/{persist-AMH5SQ4W.js → persist-WGIJ6RXY.js} +4 -3
  33. package/dist/persistent-memory-VFBRMKGG.js +65 -0
  34. package/dist/persistent-memory-ZCQKLIC5.js +63 -0
  35. package/dist/{pr-TJFKUZ4B.js → pr-GIDFO4KP.js} +5 -5
  36. package/dist/{run-tests-KEI7IXTB.js → run-tests-BMH6QJN2.js} +2 -2
  37. package/dist/{run-tests-EWMU34A4.js → run-tests-WCZBWQFS.js} +2 -1
  38. package/dist/{server-UVS2D4M6.js → server-A5NGGDMP.js} +130 -47
  39. package/dist/{server-E545UIX7.js → server-FEFIFJDA.js} +8 -6
  40. package/dist/{task-orchestrator-PWXV2EBN.js → task-orchestrator-J2H6OV6R.js} +8 -6
  41. package/dist/{usage-EDJ6QOWB.js → usage-7WJC234G.js} +2 -2
  42. package/package.json +1 -1
@@ -0,0 +1,523 @@
1
+ import {
2
+ DEFAULT_PATTERNS,
3
+ redactString
4
+ } from "./chunk-YVTASHS5.js";
5
+ import {
6
+ MEMORY_FILE_NAME,
7
+ MEMORY_MAX_CHARS,
8
+ MEMORY_STORE_FILE_NAME
9
+ } from "./chunk-GEWPLNKA.js";
10
+ import {
11
+ atomicWriteFileSync
12
+ } from "./chunk-6BUTA5VW.js";
13
+
14
+ // src/memory/persistent-memory.ts
15
+ import { copyFileSync, existsSync as existsSync2, mkdirSync, readFileSync, statSync } from "fs";
16
+ import { randomUUID, createHash } from "crypto";
17
+ import { dirname, join as join2, resolve } from "path";
18
+
19
+ // src/tools/git-context.ts
20
+ import { execSync } from "child_process";
21
+ import { existsSync } from "fs";
22
+ import { join } from "path";
23
+ function runGit(cmd, cwd) {
24
+ try {
25
+ return execSync(`git ${cmd}`, {
26
+ cwd,
27
+ encoding: "utf-8",
28
+ stdio: ["pipe", "pipe", "pipe"],
29
+ timeout: 5e3
30
+ }).trim();
31
+ } catch {
32
+ return null;
33
+ }
34
+ }
35
+ function getGitRoot(cwd = process.cwd()) {
36
+ return runGit("rev-parse --show-toplevel", cwd);
37
+ }
38
+ function getGitContext(cwd = process.cwd()) {
39
+ if (!existsSync(join(cwd, ".git"))) {
40
+ const result = runGit("rev-parse --git-dir", cwd);
41
+ if (!result) return null;
42
+ }
43
+ const branch = runGit("rev-parse --abbrev-ref HEAD", cwd);
44
+ if (!branch) return null;
45
+ const statusOutput = runGit("status --porcelain", cwd) ?? "";
46
+ const statusLines = statusOutput ? statusOutput.split("\n").filter(Boolean) : [];
47
+ const stagedFiles = [];
48
+ const changedFiles = [];
49
+ for (const line of statusLines) {
50
+ const xy = line.slice(0, 2);
51
+ const file = line.slice(3).trim();
52
+ const indexStatus = xy[0];
53
+ const workStatus = xy[1];
54
+ if (indexStatus && indexStatus !== " " && indexStatus !== "?") {
55
+ stagedFiles.push(`${indexStatus} ${file}`);
56
+ }
57
+ if (workStatus && workStatus !== " ") {
58
+ changedFiles.push(`${workStatus} ${file}`);
59
+ }
60
+ }
61
+ const logOutput = runGit("log --oneline -3", cwd) ?? "";
62
+ const recentCommits = logOutput ? logOutput.split("\n").filter(Boolean) : [];
63
+ const unpushedOutput = runGit("log @{u}..HEAD --oneline", cwd);
64
+ const hasUnpushed = unpushedOutput !== null && unpushedOutput.trim().length > 0;
65
+ return {
66
+ branch,
67
+ changedFiles,
68
+ stagedFiles,
69
+ recentCommits,
70
+ hasUnpushed
71
+ };
72
+ }
73
+ function formatGitContextForPrompt(ctx) {
74
+ const lines = ["# Git Repository Status", ""];
75
+ lines.push(`- **Branch**: \`${ctx.branch}\``);
76
+ if (ctx.stagedFiles.length > 0) {
77
+ lines.push(`- **Staged** (${ctx.stagedFiles.length} files):`);
78
+ for (const f of ctx.stagedFiles.slice(0, 10)) {
79
+ lines.push(` - ${f}`);
80
+ }
81
+ if (ctx.stagedFiles.length > 10) {
82
+ lines.push(` - ... and ${ctx.stagedFiles.length - 10} more`);
83
+ }
84
+ }
85
+ if (ctx.changedFiles.length > 0) {
86
+ lines.push(`- **Modified** (${ctx.changedFiles.length} files):`);
87
+ for (const f of ctx.changedFiles.slice(0, 10)) {
88
+ lines.push(` - ${f}`);
89
+ }
90
+ if (ctx.changedFiles.length > 10) {
91
+ lines.push(` - ... and ${ctx.changedFiles.length - 10} more`);
92
+ }
93
+ }
94
+ if (ctx.stagedFiles.length === 0 && ctx.changedFiles.length === 0) {
95
+ lines.push("- **Working tree**: clean");
96
+ }
97
+ if (ctx.recentCommits.length > 0) {
98
+ lines.push("- **Recent commits**:");
99
+ for (const c of ctx.recentCommits) {
100
+ lines.push(` - ${c}`);
101
+ }
102
+ }
103
+ if (ctx.hasUnpushed) {
104
+ lines.push("- \u26A0\uFE0F Has unpushed commits");
105
+ }
106
+ return lines.join("\n");
107
+ }
108
+
109
+ // src/memory/persistent-memory.ts
110
+ function parseWritableScope(value) {
111
+ return value === "personal" || value === "project" ? value : void 0;
112
+ }
113
+ function memoryStorePath(configDir) {
114
+ return join2(configDir, MEMORY_STORE_FILE_NAME);
115
+ }
116
+ function memoryMarkdownPath(configDir) {
117
+ return join2(configDir, MEMORY_FILE_NAME);
118
+ }
119
+ function getProjectKey(cwd = process.cwd()) {
120
+ return resolve(getGitRoot(cwd) ?? cwd);
121
+ }
122
+ function nowIso() {
123
+ return (/* @__PURE__ */ new Date()).toISOString();
124
+ }
125
+ function isValidScope(value) {
126
+ return value === "personal" || value === "project" || value === "session" || value === "team";
127
+ }
128
+ function isValidSensitivity(value) {
129
+ return value === "low" || value === "medium" || value === "high";
130
+ }
131
+ function isValidSource(value) {
132
+ return value === "tool" || value === "manual";
133
+ }
134
+ function normalizeEntry(value) {
135
+ if (!value || typeof value !== "object") return null;
136
+ const raw = value;
137
+ const content = typeof raw.content === "string" ? raw.content.trim() : "";
138
+ if (!content) return null;
139
+ const createdAt = typeof raw.createdAt === "string" ? raw.createdAt : nowIso();
140
+ return {
141
+ id: typeof raw.id === "string" && raw.id ? raw.id : randomUUID(),
142
+ content,
143
+ scope: isValidScope(raw.scope) ? raw.scope : "personal",
144
+ source: isValidSource(raw.source) ? raw.source : void 0,
145
+ sourceSession: typeof raw.sourceSession === "string" && raw.sourceSession ? raw.sourceSession : void 0,
146
+ sourceProject: typeof raw.sourceProject === "string" && raw.sourceProject ? raw.sourceProject : void 0,
147
+ createdAt,
148
+ updatedAt: typeof raw.updatedAt === "string" ? raw.updatedAt : createdAt,
149
+ expiresAt: typeof raw.expiresAt === "string" && raw.expiresAt ? raw.expiresAt : void 0,
150
+ lastReferencedAt: typeof raw.lastReferencedAt === "string" && raw.lastReferencedAt ? raw.lastReferencedAt : void 0,
151
+ sensitivity: isValidSensitivity(raw.sensitivity) ? raw.sensitivity : "low",
152
+ approved: raw.approved === true,
153
+ redactedKinds: Array.isArray(raw.redactedKinds) ? raw.redactedKinds.filter((x) => typeof x === "string") : void 0
154
+ };
155
+ }
156
+ function legacyEntriesFromMarkdown(configDir) {
157
+ const file = memoryMarkdownPath(configDir);
158
+ if (!existsSync2(file)) return [];
159
+ const content = readFileSync(file, "utf-8").trim();
160
+ if (!content) return [];
161
+ const parts = content.split(/\n(?=##\s+\d{4}-\d{2}-\d{2})/g);
162
+ const chunks = parts.length > 1 ? parts : [content];
163
+ return chunks.map((chunk, index) => {
164
+ const match = chunk.match(/^##\s+([^\n]+)\n([\s\S]*)$/);
165
+ const body = (match ? match[2] : chunk).trim();
166
+ const parsed = match ? Date.parse(match[1].replace(" ", "T")) : NaN;
167
+ const createdAt = Number.isFinite(parsed) ? new Date(parsed).toISOString() : nowIso();
168
+ const id = "legacy-" + createHash("sha1").update(`${index}
169
+ ${chunk}`).digest("hex").slice(0, 12);
170
+ return normalizeEntry({ id, content: body, scope: "personal", createdAt, updatedAt: createdAt, sensitivity: "low", approved: true });
171
+ }).filter((entry) => Boolean(entry));
172
+ }
173
+ function loadMemoryEntries(configDir) {
174
+ const file = memoryStorePath(configDir);
175
+ if (!existsSync2(file)) return legacyEntriesFromMarkdown(configDir);
176
+ const text = readFileSync(file, "utf-8").trim();
177
+ if (!text) return [];
178
+ const entries = [];
179
+ for (const line of text.split("\n")) {
180
+ const trimmed = line.trim();
181
+ if (!trimmed) continue;
182
+ try {
183
+ const entry = normalizeEntry(JSON.parse(trimmed));
184
+ if (entry) entries.push(entry);
185
+ } catch {
186
+ }
187
+ }
188
+ return entries;
189
+ }
190
+ function saveMemoryEntries(configDir, entries) {
191
+ mkdirSync(configDir, { recursive: true });
192
+ const jsonl = entries.map((entry) => JSON.stringify(entry)).join("\n");
193
+ atomicWriteFileSync(memoryStorePath(configDir), jsonl ? jsonl + "\n" : "");
194
+ }
195
+ function isMemoryExpired(entry, at = /* @__PURE__ */ new Date()) {
196
+ return Boolean(entry.expiresAt && Date.parse(entry.expiresAt) <= at.getTime());
197
+ }
198
+ function isMemoryVisibleInProject(entry, cwd = process.cwd()) {
199
+ if (entry.scope !== "project") return true;
200
+ return entry.sourceProject === getProjectKey(cwd);
201
+ }
202
+ function listMemoryEntries(configDir, options = {}) {
203
+ const cwd = options.cwd ?? process.cwd();
204
+ return loadMemoryEntries(configDir).filter((entry) => options.includeRejected || entry.approved).filter((entry) => options.includeExpired || !isMemoryExpired(entry)).filter((entry) => isMemoryVisibleInProject(entry, cwd));
205
+ }
206
+ function addMemoryEntry(configDir, content, options = {}) {
207
+ const original = content.trim();
208
+ if (!original) throw new Error("memory content is required");
209
+ const redact = options.redact ?? true;
210
+ const redacted = redact ? redactString(original, { enabled: true, patterns: options.patterns ?? DEFAULT_PATTERNS, customRegexes: options.customPatterns }) : { redacted: original, hits: [] };
211
+ const sensitivity = options.sensitivity ?? (redacted.hits.length > 0 ? "high" : "low");
212
+ const approved = options.approved ?? (sensitivity === "low" && redacted.hits.length === 0 && options.source !== "tool");
213
+ const scope = options.scope ?? "personal";
214
+ const timestamp = nowIso();
215
+ const entry = {
216
+ id: randomUUID(),
217
+ content: redacted.redacted.trim(),
218
+ scope,
219
+ source: options.source,
220
+ sourceSession: options.sourceSession,
221
+ sourceProject: scope === "project" ? getProjectKey(options.cwd) : void 0,
222
+ createdAt: timestamp,
223
+ updatedAt: timestamp,
224
+ expiresAt: options.expiresAt,
225
+ sensitivity,
226
+ approved,
227
+ redactedKinds: [...new Set(redacted.hits.map((hit) => hit.kind))]
228
+ };
229
+ const entries = loadMemoryEntries(configDir);
230
+ entries.push(entry);
231
+ saveMemoryEntries(configDir, entries);
232
+ return entry;
233
+ }
234
+ function updateMemoryEntry(configDir, idPrefix, newContent, options = {}) {
235
+ const content = newContent.trim();
236
+ if (!content) throw new Error("memory content is required");
237
+ const entries = loadMemoryEntries(configDir);
238
+ const entry = findUnique(entries, idPrefix);
239
+ const redact = options.redact ?? true;
240
+ const redacted = redact ? redactString(content, { enabled: true, patterns: options.patterns ?? DEFAULT_PATTERNS, customRegexes: options.customPatterns }) : { redacted: content, hits: [] };
241
+ entry.content = redacted.redacted.trim();
242
+ entry.updatedAt = nowIso();
243
+ entry.redactedKinds = [...new Set(redacted.hits.map((hit) => hit.kind))];
244
+ if (redacted.hits.length > 0) {
245
+ entry.sensitivity = "high";
246
+ entry.approved = false;
247
+ } else if (options.source === "tool") {
248
+ entry.approved = false;
249
+ }
250
+ saveMemoryEntries(configDir, entries);
251
+ return entry;
252
+ }
253
+ function updateMemoryApproval(configDir, idPrefix, approved) {
254
+ const entries = loadMemoryEntries(configDir);
255
+ const entry = findUnique(entries, idPrefix);
256
+ entry.approved = approved;
257
+ entry.updatedAt = nowIso();
258
+ saveMemoryEntries(configDir, entries);
259
+ return entry;
260
+ }
261
+ function deleteMemoryEntry(configDir, idPrefix) {
262
+ const entries = loadMemoryEntries(configDir);
263
+ const entry = findUnique(entries, idPrefix);
264
+ saveMemoryEntries(configDir, entries.filter((candidate) => candidate.id !== entry.id));
265
+ return entry;
266
+ }
267
+ function expireMemoryEntry(configDir, idPrefix, expiresAt = nowIso()) {
268
+ const entries = loadMemoryEntries(configDir);
269
+ const entry = findUnique(entries, idPrefix);
270
+ entry.expiresAt = expiresAt;
271
+ entry.updatedAt = nowIso();
272
+ saveMemoryEntries(configDir, entries);
273
+ return entry;
274
+ }
275
+ function countPendingMemories(configDir) {
276
+ return loadMemoryEntries(configDir).filter((entry) => !entry.approved && !isMemoryExpired(entry)).length;
277
+ }
278
+ function clearMemoryEntries(configDir) {
279
+ const entries = loadMemoryEntries(configDir);
280
+ const storePath = memoryStorePath(configDir);
281
+ let backupPath = null;
282
+ if (entries.length > 0) {
283
+ const sourceFile = existsSync2(storePath) ? storePath : memoryMarkdownPath(configDir);
284
+ if (existsSync2(sourceFile)) {
285
+ backupPath = `${sourceFile}.bak.${(/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-")}`;
286
+ copyFileSync(sourceFile, backupPath);
287
+ }
288
+ }
289
+ mkdirSync(configDir, { recursive: true });
290
+ atomicWriteFileSync(storePath, "");
291
+ atomicWriteFileSync(memoryMarkdownPath(configDir), "");
292
+ return { backupPath, cleared: entries.length };
293
+ }
294
+ function exportMemoryEntries(configDir, cwd = process.cwd(), format = "json") {
295
+ if (format === "md") {
296
+ const active = listMemoryEntries(configDir, { cwd }).filter((entry) => entry.approved && !isMemoryExpired(entry));
297
+ return renderMemoryMarkdown(active);
298
+ }
299
+ return JSON.stringify(listMemoryEntries(configDir, { cwd, includeExpired: true, includeRejected: true }), null, 2);
300
+ }
301
+ function findUnique(entries, idPrefix) {
302
+ const matches = entries.filter((entry) => entry.id.startsWith(idPrefix));
303
+ if (matches.length === 0) throw new Error(`memory id not found: ${idPrefix}`);
304
+ if (matches.length > 1) throw new Error(`memory id is ambiguous: ${idPrefix}`);
305
+ return matches[0];
306
+ }
307
+ function renderMemoryMarkdown(entries) {
308
+ return entries.map((entry) => {
309
+ const date = entry.createdAt.replace("T", " ").slice(0, 19);
310
+ const source = [
311
+ `id:${entry.id.slice(0, 8)}`,
312
+ `scope:${entry.scope}`,
313
+ entry.source ? `source:${entry.source}` : "",
314
+ entry.sourceSession ? `session:${entry.sourceSession.slice(0, 8)}` : "",
315
+ entry.sourceProject ? `project:${entry.sourceProject}` : "",
316
+ `sensitivity:${entry.sensitivity}`
317
+ ].filter(Boolean).join(" \xB7 ");
318
+ return `## ${date}
319
+ <!-- ${source} -->
320
+ ${entry.content}
321
+ `;
322
+ }).join("\n");
323
+ }
324
+ function syncLegacyMarkdown(configDir, entries = loadMemoryEntries(configDir)) {
325
+ mkdirSync(dirname(memoryMarkdownPath(configDir)), { recursive: true });
326
+ const active = entries.filter((entry) => entry.approved && !isMemoryExpired(entry));
327
+ atomicWriteFileSync(memoryMarkdownPath(configDir), renderMemoryMarkdown(active));
328
+ }
329
+ function formatMemoryForPrompt(configDir, cwd = process.cwd(), maxChars = MEMORY_MAX_CHARS) {
330
+ const entries = listMemoryEntries(configDir, { cwd }).filter((entry) => entry.approved && !isMemoryExpired(entry));
331
+ if (entries.length === 0) return null;
332
+ let content = entries.map((entry) => {
333
+ const meta = [
334
+ `id ${entry.id.slice(0, 8)}`,
335
+ entry.scope,
336
+ entry.source ?? "unknown",
337
+ entry.createdAt.slice(0, 10)
338
+ ].join(" \xB7 ");
339
+ return `## ${meta}
340
+ ${entry.content}`;
341
+ }).join("\n\n");
342
+ let entryCount = entries.length;
343
+ let truncated = false;
344
+ if (content.length > maxChars) {
345
+ truncated = true;
346
+ content = content.slice(-maxChars);
347
+ const firstEntry = content.indexOf("\n## ");
348
+ if (firstEntry !== -1) content = content.slice(firstEntry + 1);
349
+ entryCount = Math.max(1, (content.match(/^## /gm) ?? []).length);
350
+ content = `(Note: persistent memory exceeded the ${maxChars}-char budget \u2014 showing the newest ${entryCount} of ${entries.length} entries; older entries were dropped. The user can prune with /memory show and /memory delete <id>.)
351
+
352
+ ` + content;
353
+ }
354
+ return { content, entryCount, totalCount: entries.length, truncated };
355
+ }
356
+ function findSimilarMemory(entries, content) {
357
+ const normalize = (s) => s.toLowerCase().replace(/\s+/g, " ").trim();
358
+ const target = normalize(content);
359
+ if (target.length < 8) return null;
360
+ for (const entry of entries) {
361
+ const existing = normalize(entry.content);
362
+ if (existing === target || existing.includes(target) || target.includes(existing)) return entry;
363
+ }
364
+ return null;
365
+ }
366
+ function listOrphanedProjectMemories(configDir) {
367
+ return loadMemoryEntries(configDir).filter((entry) => entry.scope === "project" && entry.sourceProject !== void 0 && !existsSync2(entry.sourceProject));
368
+ }
369
+ function rebindMemoryProject(configDir, idPrefix, newProjectPath) {
370
+ const entries = loadMemoryEntries(configDir);
371
+ const entry = findUnique(entries, idPrefix);
372
+ if (entry.scope !== "project") throw new Error(`memory ${entry.id.slice(0, 8)} is not project-scoped (scope: ${entry.scope})`);
373
+ entry.sourceProject = getProjectKey(resolve(newProjectPath));
374
+ entry.updatedAt = nowIso();
375
+ saveMemoryEntries(configDir, entries);
376
+ return entry;
377
+ }
378
+ function detectStaleLegacyMarkdownEdit(configDir) {
379
+ const jsonlPath = memoryStorePath(configDir);
380
+ const mdPath = memoryMarkdownPath(configDir);
381
+ if (!existsSync2(jsonlPath) || !existsSync2(mdPath)) return false;
382
+ try {
383
+ if (!readFileSync(mdPath, "utf-8").trim()) return false;
384
+ const jsonlMtime = statSync(jsonlPath).mtimeMs;
385
+ const mdMtime = statSync(mdPath).mtimeMs;
386
+ return mdMtime > jsonlMtime + 2e3;
387
+ } catch {
388
+ return false;
389
+ }
390
+ }
391
+ function touchMemoryReferences(configDir, ids) {
392
+ if (ids.length === 0) return;
393
+ const wanted = new Set(ids);
394
+ const entries = loadMemoryEntries(configDir);
395
+ let changed = false;
396
+ const ts = nowIso();
397
+ for (const entry of entries) {
398
+ if (wanted.has(entry.id)) {
399
+ entry.lastReferencedAt = ts;
400
+ changed = true;
401
+ }
402
+ }
403
+ if (changed) saveMemoryEntries(configDir, entries);
404
+ }
405
+ function isMemoryStale(entry, days, at = /* @__PURE__ */ new Date()) {
406
+ const reference = Date.parse(entry.lastReferencedAt ?? entry.updatedAt);
407
+ if (!Number.isFinite(reference)) return false;
408
+ return at.getTime() - reference > days * 864e5;
409
+ }
410
+ function tokenizeQuery(query) {
411
+ const tokens = [];
412
+ for (const word of query.toLowerCase().split(/\s+/).filter(Boolean)) {
413
+ const cjkRuns = word.match(/[一-鿿぀-ヿ가-힯]+/g) ?? [];
414
+ const nonCjk = word.replace(/[一-鿿぀-ヿ가-힯]+/g, " ").split(/\s+/).filter((t) => t.length >= 2);
415
+ tokens.push(...nonCjk);
416
+ for (const run of cjkRuns) {
417
+ if (run.length === 1) tokens.push(run);
418
+ for (let i = 0; i + 2 <= run.length; i++) tokens.push(run.slice(i, i + 2));
419
+ }
420
+ }
421
+ return [...new Set(tokens)];
422
+ }
423
+ function searchPersistentMemories(configDir, query, cwd = process.cwd(), limit = 3) {
424
+ const tokens = tokenizeQuery(query);
425
+ if (tokens.length === 0) return [];
426
+ const entries = listMemoryEntries(configDir, { cwd }).filter((entry) => entry.approved && !isMemoryExpired(entry));
427
+ const hits = [];
428
+ for (const entry of entries) {
429
+ const haystack = entry.content.toLowerCase();
430
+ const matched = tokens.filter((token) => haystack.includes(token)).length;
431
+ const score = matched / tokens.length;
432
+ if (score >= 0.34) hits.push({ entry, score });
433
+ }
434
+ return hits.sort((a, b) => b.score - a.score).slice(0, limit);
435
+ }
436
+ function backupMemoryStore(configDir, outPath) {
437
+ const entries = loadMemoryEntries(configDir);
438
+ if (entries.length === 0) throw new Error("memory is empty \u2014 nothing to back up");
439
+ const storePath = memoryStorePath(configDir);
440
+ if (!existsSync2(storePath)) saveMemoryEntries(configDir, entries);
441
+ const backupPath = outPath ?? `${storePath}.bak.${(/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-")}`;
442
+ copyFileSync(storePath, backupPath);
443
+ return { backupPath, count: entries.length };
444
+ }
445
+ function restoreMemoryEntries(configDir, filePath) {
446
+ const text = readFileSync(filePath, "utf-8").trim();
447
+ if (!text) throw new Error(`restore file is empty: ${filePath}`);
448
+ let rawItems;
449
+ try {
450
+ const parsed = JSON.parse(text);
451
+ rawItems = Array.isArray(parsed) ? parsed : [parsed];
452
+ } catch {
453
+ rawItems = text.split("\n").map((line) => {
454
+ try {
455
+ return JSON.parse(line.trim());
456
+ } catch {
457
+ return null;
458
+ }
459
+ }).filter((x) => x !== null);
460
+ }
461
+ const candidates = rawItems.map((item) => normalizeEntry(item)).filter((entry) => Boolean(entry));
462
+ if (candidates.length === 0) throw new Error(`no valid memory entries found in ${filePath}`);
463
+ const existing = loadMemoryEntries(configDir);
464
+ const existingIds = new Set(existing.map((entry) => entry.id));
465
+ let backupPath = null;
466
+ if (existing.length > 0) {
467
+ backupPath = backupMemoryStore(configDir).backupPath;
468
+ }
469
+ let imported = 0;
470
+ let skipped = 0;
471
+ for (const candidate of candidates) {
472
+ if (existingIds.has(candidate.id)) {
473
+ skipped++;
474
+ continue;
475
+ }
476
+ const redacted = redactString(candidate.content, { enabled: true, patterns: DEFAULT_PATTERNS });
477
+ if (redacted.hits.length > 0) {
478
+ candidate.content = redacted.redacted.trim();
479
+ candidate.sensitivity = "high";
480
+ candidate.approved = false;
481
+ candidate.redactedKinds = [...new Set(redacted.hits.map((hit) => hit.kind))];
482
+ }
483
+ existing.push(candidate);
484
+ existingIds.add(candidate.id);
485
+ imported++;
486
+ }
487
+ saveMemoryEntries(configDir, existing);
488
+ return { imported, skipped, backupPath };
489
+ }
490
+
491
+ export {
492
+ getGitRoot,
493
+ getGitContext,
494
+ formatGitContextForPrompt,
495
+ parseWritableScope,
496
+ memoryStorePath,
497
+ memoryMarkdownPath,
498
+ getProjectKey,
499
+ loadMemoryEntries,
500
+ isMemoryExpired,
501
+ isMemoryVisibleInProject,
502
+ listMemoryEntries,
503
+ addMemoryEntry,
504
+ updateMemoryEntry,
505
+ updateMemoryApproval,
506
+ deleteMemoryEntry,
507
+ expireMemoryEntry,
508
+ countPendingMemories,
509
+ clearMemoryEntries,
510
+ exportMemoryEntries,
511
+ renderMemoryMarkdown,
512
+ syncLegacyMarkdown,
513
+ formatMemoryForPrompt,
514
+ findSimilarMemory,
515
+ listOrphanedProjectMemories,
516
+ rebindMemoryProject,
517
+ detectStaleLegacyMarkdownEdit,
518
+ touchMemoryReferences,
519
+ isMemoryStale,
520
+ searchPersistentMemories,
521
+ backupMemoryStore,
522
+ restoreMemoryEntries
523
+ };
@@ -2,7 +2,7 @@
2
2
  import {
3
3
  evaluateCiGate,
4
4
  normalizeCiThresholds
5
- } from "./chunk-PGHTAKIA.js";
5
+ } from "./chunk-DJGCG7SF.js";
6
6
  import {
7
7
  buildReviewPrompt,
8
8
  buildSecurityReviewPrompt,
@@ -13,10 +13,10 @@ import {
13
13
  } from "./chunk-QYQI7ZWK.js";
14
14
  import {
15
15
  ConfigManager
16
- } from "./chunk-IV6GCDVR.js";
16
+ } from "./chunk-X73Y4JZI.js";
17
17
  import {
18
18
  VERSION
19
- } from "./chunk-SMQHEJSH.js";
19
+ } from "./chunk-66CE3E6X.js";
20
20
 
21
21
  // src/cli/ci.ts
22
22
  import { execFileSync } from "child_process";