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,432 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ getGitRoot
4
+ } from "./chunk-HOSJZMQS.js";
5
+ import {
6
+ MEMORY_FILE_NAME,
7
+ MEMORY_MAX_CHARS,
8
+ MEMORY_STORE_FILE_NAME
9
+ } from "./chunk-66CE3E6X.js";
10
+ import {
11
+ DEFAULT_PATTERNS,
12
+ redactString
13
+ } from "./chunk-B5TYJO7V.js";
14
+ import {
15
+ atomicWriteFileSync
16
+ } from "./chunk-IW3Q7AE5.js";
17
+
18
+ // src/memory/persistent-memory.ts
19
+ import { copyFileSync, existsSync, mkdirSync, readFileSync, statSync } from "fs";
20
+ import { randomUUID, createHash } from "crypto";
21
+ import { dirname, join, resolve } from "path";
22
+ function parseWritableScope(value) {
23
+ return value === "personal" || value === "project" ? value : void 0;
24
+ }
25
+ function memoryStorePath(configDir) {
26
+ return join(configDir, MEMORY_STORE_FILE_NAME);
27
+ }
28
+ function memoryMarkdownPath(configDir) {
29
+ return join(configDir, MEMORY_FILE_NAME);
30
+ }
31
+ function getProjectKey(cwd = process.cwd()) {
32
+ return resolve(getGitRoot(cwd) ?? cwd);
33
+ }
34
+ function nowIso() {
35
+ return (/* @__PURE__ */ new Date()).toISOString();
36
+ }
37
+ function isValidScope(value) {
38
+ return value === "personal" || value === "project" || value === "session" || value === "team";
39
+ }
40
+ function isValidSensitivity(value) {
41
+ return value === "low" || value === "medium" || value === "high";
42
+ }
43
+ function isValidSource(value) {
44
+ return value === "tool" || value === "manual";
45
+ }
46
+ function normalizeEntry(value) {
47
+ if (!value || typeof value !== "object") return null;
48
+ const raw = value;
49
+ const content = typeof raw.content === "string" ? raw.content.trim() : "";
50
+ if (!content) return null;
51
+ const createdAt = typeof raw.createdAt === "string" ? raw.createdAt : nowIso();
52
+ return {
53
+ id: typeof raw.id === "string" && raw.id ? raw.id : randomUUID(),
54
+ content,
55
+ scope: isValidScope(raw.scope) ? raw.scope : "personal",
56
+ source: isValidSource(raw.source) ? raw.source : void 0,
57
+ sourceSession: typeof raw.sourceSession === "string" && raw.sourceSession ? raw.sourceSession : void 0,
58
+ sourceProject: typeof raw.sourceProject === "string" && raw.sourceProject ? raw.sourceProject : void 0,
59
+ createdAt,
60
+ updatedAt: typeof raw.updatedAt === "string" ? raw.updatedAt : createdAt,
61
+ expiresAt: typeof raw.expiresAt === "string" && raw.expiresAt ? raw.expiresAt : void 0,
62
+ lastReferencedAt: typeof raw.lastReferencedAt === "string" && raw.lastReferencedAt ? raw.lastReferencedAt : void 0,
63
+ sensitivity: isValidSensitivity(raw.sensitivity) ? raw.sensitivity : "low",
64
+ approved: raw.approved === true,
65
+ redactedKinds: Array.isArray(raw.redactedKinds) ? raw.redactedKinds.filter((x) => typeof x === "string") : void 0
66
+ };
67
+ }
68
+ function legacyEntriesFromMarkdown(configDir) {
69
+ const file = memoryMarkdownPath(configDir);
70
+ if (!existsSync(file)) return [];
71
+ const content = readFileSync(file, "utf-8").trim();
72
+ if (!content) return [];
73
+ const parts = content.split(/\n(?=##\s+\d{4}-\d{2}-\d{2})/g);
74
+ const chunks = parts.length > 1 ? parts : [content];
75
+ return chunks.map((chunk, index) => {
76
+ const match = chunk.match(/^##\s+([^\n]+)\n([\s\S]*)$/);
77
+ const body = (match ? match[2] : chunk).trim();
78
+ const parsed = match ? Date.parse(match[1].replace(" ", "T")) : NaN;
79
+ const createdAt = Number.isFinite(parsed) ? new Date(parsed).toISOString() : nowIso();
80
+ const id = "legacy-" + createHash("sha1").update(`${index}
81
+ ${chunk}`).digest("hex").slice(0, 12);
82
+ return normalizeEntry({ id, content: body, scope: "personal", createdAt, updatedAt: createdAt, sensitivity: "low", approved: true });
83
+ }).filter((entry) => Boolean(entry));
84
+ }
85
+ function loadMemoryEntries(configDir) {
86
+ const file = memoryStorePath(configDir);
87
+ if (!existsSync(file)) return legacyEntriesFromMarkdown(configDir);
88
+ const text = readFileSync(file, "utf-8").trim();
89
+ if (!text) return [];
90
+ const entries = [];
91
+ for (const line of text.split("\n")) {
92
+ const trimmed = line.trim();
93
+ if (!trimmed) continue;
94
+ try {
95
+ const entry = normalizeEntry(JSON.parse(trimmed));
96
+ if (entry) entries.push(entry);
97
+ } catch {
98
+ }
99
+ }
100
+ return entries;
101
+ }
102
+ function saveMemoryEntries(configDir, entries) {
103
+ mkdirSync(configDir, { recursive: true });
104
+ const jsonl = entries.map((entry) => JSON.stringify(entry)).join("\n");
105
+ atomicWriteFileSync(memoryStorePath(configDir), jsonl ? jsonl + "\n" : "");
106
+ }
107
+ function isMemoryExpired(entry, at = /* @__PURE__ */ new Date()) {
108
+ return Boolean(entry.expiresAt && Date.parse(entry.expiresAt) <= at.getTime());
109
+ }
110
+ function isMemoryVisibleInProject(entry, cwd = process.cwd()) {
111
+ if (entry.scope !== "project") return true;
112
+ return entry.sourceProject === getProjectKey(cwd);
113
+ }
114
+ function listMemoryEntries(configDir, options = {}) {
115
+ const cwd = options.cwd ?? process.cwd();
116
+ return loadMemoryEntries(configDir).filter((entry) => options.includeRejected || entry.approved).filter((entry) => options.includeExpired || !isMemoryExpired(entry)).filter((entry) => isMemoryVisibleInProject(entry, cwd));
117
+ }
118
+ function addMemoryEntry(configDir, content, options = {}) {
119
+ const original = content.trim();
120
+ if (!original) throw new Error("memory content is required");
121
+ const redact = options.redact ?? true;
122
+ const redacted = redact ? redactString(original, { enabled: true, patterns: options.patterns ?? DEFAULT_PATTERNS, customRegexes: options.customPatterns }) : { redacted: original, hits: [] };
123
+ const sensitivity = options.sensitivity ?? (redacted.hits.length > 0 ? "high" : "low");
124
+ const approved = options.approved ?? (sensitivity === "low" && redacted.hits.length === 0 && options.source !== "tool");
125
+ const scope = options.scope ?? "personal";
126
+ const timestamp = nowIso();
127
+ const entry = {
128
+ id: randomUUID(),
129
+ content: redacted.redacted.trim(),
130
+ scope,
131
+ source: options.source,
132
+ sourceSession: options.sourceSession,
133
+ sourceProject: scope === "project" ? getProjectKey(options.cwd) : void 0,
134
+ createdAt: timestamp,
135
+ updatedAt: timestamp,
136
+ expiresAt: options.expiresAt,
137
+ sensitivity,
138
+ approved,
139
+ redactedKinds: [...new Set(redacted.hits.map((hit) => hit.kind))]
140
+ };
141
+ const entries = loadMemoryEntries(configDir);
142
+ entries.push(entry);
143
+ saveMemoryEntries(configDir, entries);
144
+ return entry;
145
+ }
146
+ function updateMemoryEntry(configDir, idPrefix, newContent, options = {}) {
147
+ const content = newContent.trim();
148
+ if (!content) throw new Error("memory content is required");
149
+ const entries = loadMemoryEntries(configDir);
150
+ const entry = findUnique(entries, idPrefix);
151
+ const redact = options.redact ?? true;
152
+ const redacted = redact ? redactString(content, { enabled: true, patterns: options.patterns ?? DEFAULT_PATTERNS, customRegexes: options.customPatterns }) : { redacted: content, hits: [] };
153
+ entry.content = redacted.redacted.trim();
154
+ entry.updatedAt = nowIso();
155
+ entry.redactedKinds = [...new Set(redacted.hits.map((hit) => hit.kind))];
156
+ if (redacted.hits.length > 0) {
157
+ entry.sensitivity = "high";
158
+ entry.approved = false;
159
+ } else if (options.source === "tool") {
160
+ entry.approved = false;
161
+ }
162
+ saveMemoryEntries(configDir, entries);
163
+ return entry;
164
+ }
165
+ function updateMemoryApproval(configDir, idPrefix, approved) {
166
+ const entries = loadMemoryEntries(configDir);
167
+ const entry = findUnique(entries, idPrefix);
168
+ entry.approved = approved;
169
+ entry.updatedAt = nowIso();
170
+ saveMemoryEntries(configDir, entries);
171
+ return entry;
172
+ }
173
+ function deleteMemoryEntry(configDir, idPrefix) {
174
+ const entries = loadMemoryEntries(configDir);
175
+ const entry = findUnique(entries, idPrefix);
176
+ saveMemoryEntries(configDir, entries.filter((candidate) => candidate.id !== entry.id));
177
+ return entry;
178
+ }
179
+ function expireMemoryEntry(configDir, idPrefix, expiresAt = nowIso()) {
180
+ const entries = loadMemoryEntries(configDir);
181
+ const entry = findUnique(entries, idPrefix);
182
+ entry.expiresAt = expiresAt;
183
+ entry.updatedAt = nowIso();
184
+ saveMemoryEntries(configDir, entries);
185
+ return entry;
186
+ }
187
+ function countPendingMemories(configDir) {
188
+ return loadMemoryEntries(configDir).filter((entry) => !entry.approved && !isMemoryExpired(entry)).length;
189
+ }
190
+ function clearMemoryEntries(configDir) {
191
+ const entries = loadMemoryEntries(configDir);
192
+ const storePath = memoryStorePath(configDir);
193
+ let backupPath = null;
194
+ if (entries.length > 0) {
195
+ const sourceFile = existsSync(storePath) ? storePath : memoryMarkdownPath(configDir);
196
+ if (existsSync(sourceFile)) {
197
+ backupPath = `${sourceFile}.bak.${(/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-")}`;
198
+ copyFileSync(sourceFile, backupPath);
199
+ }
200
+ }
201
+ mkdirSync(configDir, { recursive: true });
202
+ atomicWriteFileSync(storePath, "");
203
+ atomicWriteFileSync(memoryMarkdownPath(configDir), "");
204
+ return { backupPath, cleared: entries.length };
205
+ }
206
+ function exportMemoryEntries(configDir, cwd = process.cwd(), format = "json") {
207
+ if (format === "md") {
208
+ const active = listMemoryEntries(configDir, { cwd }).filter((entry) => entry.approved && !isMemoryExpired(entry));
209
+ return renderMemoryMarkdown(active);
210
+ }
211
+ return JSON.stringify(listMemoryEntries(configDir, { cwd, includeExpired: true, includeRejected: true }), null, 2);
212
+ }
213
+ function findUnique(entries, idPrefix) {
214
+ const matches = entries.filter((entry) => entry.id.startsWith(idPrefix));
215
+ if (matches.length === 0) throw new Error(`memory id not found: ${idPrefix}`);
216
+ if (matches.length > 1) throw new Error(`memory id is ambiguous: ${idPrefix}`);
217
+ return matches[0];
218
+ }
219
+ function renderMemoryMarkdown(entries) {
220
+ return entries.map((entry) => {
221
+ const date = entry.createdAt.replace("T", " ").slice(0, 19);
222
+ const source = [
223
+ `id:${entry.id.slice(0, 8)}`,
224
+ `scope:${entry.scope}`,
225
+ entry.source ? `source:${entry.source}` : "",
226
+ entry.sourceSession ? `session:${entry.sourceSession.slice(0, 8)}` : "",
227
+ entry.sourceProject ? `project:${entry.sourceProject}` : "",
228
+ `sensitivity:${entry.sensitivity}`
229
+ ].filter(Boolean).join(" \xB7 ");
230
+ return `## ${date}
231
+ <!-- ${source} -->
232
+ ${entry.content}
233
+ `;
234
+ }).join("\n");
235
+ }
236
+ function syncLegacyMarkdown(configDir, entries = loadMemoryEntries(configDir)) {
237
+ mkdirSync(dirname(memoryMarkdownPath(configDir)), { recursive: true });
238
+ const active = entries.filter((entry) => entry.approved && !isMemoryExpired(entry));
239
+ atomicWriteFileSync(memoryMarkdownPath(configDir), renderMemoryMarkdown(active));
240
+ }
241
+ function formatMemoryForPrompt(configDir, cwd = process.cwd(), maxChars = MEMORY_MAX_CHARS) {
242
+ const entries = listMemoryEntries(configDir, { cwd }).filter((entry) => entry.approved && !isMemoryExpired(entry));
243
+ if (entries.length === 0) return null;
244
+ let content = entries.map((entry) => {
245
+ const meta = [
246
+ `id ${entry.id.slice(0, 8)}`,
247
+ entry.scope,
248
+ entry.source ?? "unknown",
249
+ entry.createdAt.slice(0, 10)
250
+ ].join(" \xB7 ");
251
+ return `## ${meta}
252
+ ${entry.content}`;
253
+ }).join("\n\n");
254
+ let entryCount = entries.length;
255
+ let truncated = false;
256
+ if (content.length > maxChars) {
257
+ truncated = true;
258
+ content = content.slice(-maxChars);
259
+ const firstEntry = content.indexOf("\n## ");
260
+ if (firstEntry !== -1) content = content.slice(firstEntry + 1);
261
+ entryCount = Math.max(1, (content.match(/^## /gm) ?? []).length);
262
+ 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>.)
263
+
264
+ ` + content;
265
+ }
266
+ return { content, entryCount, totalCount: entries.length, truncated };
267
+ }
268
+ function findSimilarMemory(entries, content) {
269
+ const normalize = (s) => s.toLowerCase().replace(/\s+/g, " ").trim();
270
+ const target = normalize(content);
271
+ if (target.length < 8) return null;
272
+ for (const entry of entries) {
273
+ const existing = normalize(entry.content);
274
+ if (existing === target || existing.includes(target) || target.includes(existing)) return entry;
275
+ }
276
+ return null;
277
+ }
278
+ function listOrphanedProjectMemories(configDir) {
279
+ return loadMemoryEntries(configDir).filter((entry) => entry.scope === "project" && entry.sourceProject !== void 0 && !existsSync(entry.sourceProject));
280
+ }
281
+ function rebindMemoryProject(configDir, idPrefix, newProjectPath) {
282
+ const entries = loadMemoryEntries(configDir);
283
+ const entry = findUnique(entries, idPrefix);
284
+ if (entry.scope !== "project") throw new Error(`memory ${entry.id.slice(0, 8)} is not project-scoped (scope: ${entry.scope})`);
285
+ entry.sourceProject = getProjectKey(resolve(newProjectPath));
286
+ entry.updatedAt = nowIso();
287
+ saveMemoryEntries(configDir, entries);
288
+ return entry;
289
+ }
290
+ function detectStaleLegacyMarkdownEdit(configDir) {
291
+ const jsonlPath = memoryStorePath(configDir);
292
+ const mdPath = memoryMarkdownPath(configDir);
293
+ if (!existsSync(jsonlPath) || !existsSync(mdPath)) return false;
294
+ try {
295
+ if (!readFileSync(mdPath, "utf-8").trim()) return false;
296
+ const jsonlMtime = statSync(jsonlPath).mtimeMs;
297
+ const mdMtime = statSync(mdPath).mtimeMs;
298
+ return mdMtime > jsonlMtime + 2e3;
299
+ } catch {
300
+ return false;
301
+ }
302
+ }
303
+ function touchMemoryReferences(configDir, ids) {
304
+ if (ids.length === 0) return;
305
+ const wanted = new Set(ids);
306
+ const entries = loadMemoryEntries(configDir);
307
+ let changed = false;
308
+ const ts = nowIso();
309
+ for (const entry of entries) {
310
+ if (wanted.has(entry.id)) {
311
+ entry.lastReferencedAt = ts;
312
+ changed = true;
313
+ }
314
+ }
315
+ if (changed) saveMemoryEntries(configDir, entries);
316
+ }
317
+ function isMemoryStale(entry, days, at = /* @__PURE__ */ new Date()) {
318
+ const reference = Date.parse(entry.lastReferencedAt ?? entry.updatedAt);
319
+ if (!Number.isFinite(reference)) return false;
320
+ return at.getTime() - reference > days * 864e5;
321
+ }
322
+ function tokenizeQuery(query) {
323
+ const tokens = [];
324
+ for (const word of query.toLowerCase().split(/\s+/).filter(Boolean)) {
325
+ const cjkRuns = word.match(/[一-鿿぀-ヿ가-힯]+/g) ?? [];
326
+ const nonCjk = word.replace(/[一-鿿぀-ヿ가-힯]+/g, " ").split(/\s+/).filter((t) => t.length >= 2);
327
+ tokens.push(...nonCjk);
328
+ for (const run of cjkRuns) {
329
+ if (run.length === 1) tokens.push(run);
330
+ for (let i = 0; i + 2 <= run.length; i++) tokens.push(run.slice(i, i + 2));
331
+ }
332
+ }
333
+ return [...new Set(tokens)];
334
+ }
335
+ function searchPersistentMemories(configDir, query, cwd = process.cwd(), limit = 3) {
336
+ const tokens = tokenizeQuery(query);
337
+ if (tokens.length === 0) return [];
338
+ const entries = listMemoryEntries(configDir, { cwd }).filter((entry) => entry.approved && !isMemoryExpired(entry));
339
+ const hits = [];
340
+ for (const entry of entries) {
341
+ const haystack = entry.content.toLowerCase();
342
+ const matched = tokens.filter((token) => haystack.includes(token)).length;
343
+ const score = matched / tokens.length;
344
+ if (score >= 0.34) hits.push({ entry, score });
345
+ }
346
+ return hits.sort((a, b) => b.score - a.score).slice(0, limit);
347
+ }
348
+ function backupMemoryStore(configDir, outPath) {
349
+ const entries = loadMemoryEntries(configDir);
350
+ if (entries.length === 0) throw new Error("memory is empty \u2014 nothing to back up");
351
+ const storePath = memoryStorePath(configDir);
352
+ if (!existsSync(storePath)) saveMemoryEntries(configDir, entries);
353
+ const backupPath = outPath ?? `${storePath}.bak.${(/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-")}`;
354
+ copyFileSync(storePath, backupPath);
355
+ return { backupPath, count: entries.length };
356
+ }
357
+ function restoreMemoryEntries(configDir, filePath) {
358
+ const text = readFileSync(filePath, "utf-8").trim();
359
+ if (!text) throw new Error(`restore file is empty: ${filePath}`);
360
+ let rawItems;
361
+ try {
362
+ const parsed = JSON.parse(text);
363
+ rawItems = Array.isArray(parsed) ? parsed : [parsed];
364
+ } catch {
365
+ rawItems = text.split("\n").map((line) => {
366
+ try {
367
+ return JSON.parse(line.trim());
368
+ } catch {
369
+ return null;
370
+ }
371
+ }).filter((x) => x !== null);
372
+ }
373
+ const candidates = rawItems.map((item) => normalizeEntry(item)).filter((entry) => Boolean(entry));
374
+ if (candidates.length === 0) throw new Error(`no valid memory entries found in ${filePath}`);
375
+ const existing = loadMemoryEntries(configDir);
376
+ const existingIds = new Set(existing.map((entry) => entry.id));
377
+ let backupPath = null;
378
+ if (existing.length > 0) {
379
+ backupPath = backupMemoryStore(configDir).backupPath;
380
+ }
381
+ let imported = 0;
382
+ let skipped = 0;
383
+ for (const candidate of candidates) {
384
+ if (existingIds.has(candidate.id)) {
385
+ skipped++;
386
+ continue;
387
+ }
388
+ const redacted = redactString(candidate.content, { enabled: true, patterns: DEFAULT_PATTERNS });
389
+ if (redacted.hits.length > 0) {
390
+ candidate.content = redacted.redacted.trim();
391
+ candidate.sensitivity = "high";
392
+ candidate.approved = false;
393
+ candidate.redactedKinds = [...new Set(redacted.hits.map((hit) => hit.kind))];
394
+ }
395
+ existing.push(candidate);
396
+ existingIds.add(candidate.id);
397
+ imported++;
398
+ }
399
+ saveMemoryEntries(configDir, existing);
400
+ return { imported, skipped, backupPath };
401
+ }
402
+
403
+ export {
404
+ parseWritableScope,
405
+ memoryStorePath,
406
+ memoryMarkdownPath,
407
+ getProjectKey,
408
+ loadMemoryEntries,
409
+ isMemoryExpired,
410
+ isMemoryVisibleInProject,
411
+ listMemoryEntries,
412
+ addMemoryEntry,
413
+ updateMemoryEntry,
414
+ updateMemoryApproval,
415
+ deleteMemoryEntry,
416
+ expireMemoryEntry,
417
+ countPendingMemories,
418
+ clearMemoryEntries,
419
+ exportMemoryEntries,
420
+ renderMemoryMarkdown,
421
+ syncLegacyMarkdown,
422
+ formatMemoryForPrompt,
423
+ findSimilarMemory,
424
+ listOrphanedProjectMemories,
425
+ rebindMemoryProject,
426
+ detectStaleLegacyMarkdownEdit,
427
+ touchMemoryReferences,
428
+ isMemoryStale,
429
+ searchPersistentMemories,
430
+ backupMemoryStore,
431
+ restoreMemoryEntries
432
+ };
@@ -8,7 +8,7 @@ import {
8
8
  CONFIG_FILE_NAME,
9
9
  HISTORY_DIR_NAME,
10
10
  PLUGINS_DIR_NAME
11
- } from "./chunk-SMQHEJSH.js";
11
+ } from "./chunk-66CE3E6X.js";
12
12
  import {
13
13
  atomicWriteFileSync
14
14
  } from "./chunk-IW3Q7AE5.js";
@@ -131,6 +131,12 @@ var ConfigSchema = z.object({
131
131
  projectDocMaxBytes: z.number().int().positive().default(32 * 1024),
132
132
  showLoadedContextSources: z.boolean().default(false)
133
133
  }).default({}),
134
+ // 持久记忆注入配置(MEM-005)
135
+ // maxChars: 注入 system prompt 的字符预算,超出丢最老条目并显式标注截断。
136
+ // 注意 CJK ≈ 1 字符/token——中文记忆的 token 开销约等于字符数。
137
+ memory: z.object({
138
+ maxChars: z.number().int().positive().max(2e5).default(1e4)
139
+ }).default({}),
134
140
  // Google Custom Search API 的 Search Engine ID (cx 参数)
135
141
  // API Key 通过 apiKeys['google-search'] 或 AICLI_API_KEY_GOOGLESEARCH 环境变量配置
136
142
  // CX 也可通过 AICLI_GOOGLE_CX 环境变量覆盖
@@ -0,0 +1,121 @@
1
+ // src/security/redactor.ts
2
+ var DEFAULT_PATTERNS = [
3
+ // password: xxx / password = xxx / password="xxx"
4
+ // Covers YAML / JSON / shell-ish / env-file forms.
5
+ { kind: "password", regex: /\b(password|passwd|pwd)\s*[:=]\s*["']?([^\s"',;{}]{4,200})["']?/gi },
6
+ // PGPASSWORD=xxx (explicit bash env-var form, separate rule because no quotes usually)
7
+ { kind: "pgpassword-env", regex: /\b(PGPASSWORD)=([^\s"']{4,200})/g },
8
+ // JDBC/PG/MySQL/Mongo connection strings with inline credentials
9
+ // postgresql://user:pass@host/db → redact pass
10
+ { kind: "db-uri-password", regex: /(\b(?:postgres(?:ql)?|mysql|mongodb(?:\+srv)?|redis|amqp|mssql):\/\/[^:\s]+:)([^@\s]+)(@)/gi },
11
+ // Anthropic API keys
12
+ { kind: "anthropic-key", regex: /(sk-ant-[a-zA-Z0-9_-]{90,})/g },
13
+ // L6 (v0.4.108): Zhipu / GLM API keys — `<24+ hex/base64-ish>.<32+>`
14
+ // Two segments separated by a dot, each safely identifiable by length
15
+ // and char class. Conservative on the lower bound so we don't eat
16
+ // version strings like `1.0.0` or filenames.
17
+ { kind: "zhipu-key", regex: /\b([a-zA-Z0-9]{24,}\.[a-zA-Z0-9]{32,})\b/g },
18
+ // OpenAI / generic sk- keys — requires length ≥32 to avoid eating short identifiers
19
+ { kind: "openai-key", regex: /(sk-(?:proj-)?[a-zA-Z0-9_-]{32,})/g },
20
+ // GitHub personal access tokens
21
+ { kind: "github-pat", regex: /\b(ghp_[a-zA-Z0-9]{36})\b/g },
22
+ { kind: "github-oauth", regex: /\b(gho_[a-zA-Z0-9]{36})\b/g },
23
+ { kind: "github-install", regex: /\b(ghs_[a-zA-Z0-9]{36})\b/g },
24
+ // Slack tokens
25
+ { kind: "slack-bot", regex: /\b(xoxb-\d+-\d+-[a-zA-Z0-9]+)\b/g },
26
+ { kind: "slack-user", regex: /\b(xoxp-\d+-\d+-\d+-[a-zA-Z0-9]+)\b/g },
27
+ // AWS access key IDs (AKIA...) and secret access keys are context-dependent;
28
+ // we only catch the ID because secret key alone is indistinguishable from random base64.
29
+ { kind: "aws-access-key-id", regex: /\b(AKIA[0-9A-Z]{16})\b/g },
30
+ // Google API keys
31
+ { kind: "google-api-key", regex: /\b(AIza[0-9A-Za-z_-]{35})\b/g },
32
+ // Generic "api_key": "..." / "apiKey": "..." / api-key=xxx
33
+ { kind: "api-key", regex: /\b(api[_-]?key)\s*[:=]\s*["']?([a-zA-Z0-9_\-.]{16,200})["']?/gi },
34
+ // Generic token: xxx (only when value looks token-shaped; avoids eating human prose)
35
+ { kind: "token", regex: /\b(token|access[_-]?token|bearer[_-]?token)\s*[:=]\s*["']?([a-zA-Z0-9_\-.]{20,300})["']?/gi },
36
+ // Bearer <token> in Authorization headers
37
+ { kind: "bearer", regex: /\b(Authorization:\s*Bearer\s+)([a-zA-Z0-9_\-.=]{20,500})/g },
38
+ // Private key PEM blocks — catch the header+footer together
39
+ // P2-PERF-02: PEM bodies use strict Base64 + =\n — narrowed char class avoids backtracking
40
+ { kind: "private-key", regex: /-----BEGIN [A-Z ]*PRIVATE KEY-----[ \t\n\r]*[A-Za-z0-9+/=\n\r-]*[ \t\n\r]*-----END [A-Z ]*PRIVATE KEY-----/g }
41
+ ];
42
+ var MAX_CUSTOM = 32;
43
+ var MAX_PATTERN_LEN = 500;
44
+ var SUSPICIOUS_REDOS = /\([^)]*[+*][^)]*\)\s*[+*{]/;
45
+ var MAX_REDACT_INPUT = 512e3;
46
+ function render(placeholder, kind) {
47
+ return placeholder.replace("{kind}", kind);
48
+ }
49
+ function redactString(input, options) {
50
+ if (!options.enabled || !input) return { redacted: input, hits: [] };
51
+ if (input.length > MAX_REDACT_INPUT) return { redacted: input, hits: [] };
52
+ const placeholder = options.placeholder ?? "[REDACTED:{kind}]";
53
+ const customSrcs = (options.customRegexes ?? []).slice(0, MAX_CUSTOM);
54
+ const patterns = [
55
+ ...options.patterns ?? DEFAULT_PATTERNS,
56
+ ...customSrcs.flatMap((src, i) => {
57
+ if (typeof src !== "string" || src.length === 0 || src.length > MAX_PATTERN_LEN) return [];
58
+ try {
59
+ const flags = src.match(/^\/.*\/([gimsuy]*)$/)?.[1] ?? "";
60
+ const body = src.replace(/^\/(.*)\/[gimsuy]*$/, "$1");
61
+ if (SUSPICIOUS_REDOS.test(body)) return [];
62
+ const regex = new RegExp(body, flags.includes("g") ? flags : flags + "g");
63
+ return [{ kind: `custom-${i}`, regex }];
64
+ } catch {
65
+ return [];
66
+ }
67
+ })
68
+ ];
69
+ let redacted = input;
70
+ const hits = [];
71
+ for (const { kind, regex } of patterns) {
72
+ const rx = new RegExp(regex.source, regex.flags);
73
+ const captureCount = new RegExp(rx.source + "|").exec("").length - 1;
74
+ redacted = redacted.replace(rx, (...args) => {
75
+ const match = args[0];
76
+ const g1 = captureCount >= 1 ? args[1] : void 0;
77
+ const g2 = captureCount >= 2 ? args[2] : void 0;
78
+ const offset = args[1 + captureCount];
79
+ if (captureCount >= 2 && typeof g2 === "string") {
80
+ hits.push({ kind, start: offset + (g1?.length ?? 0), length: g2.length, secret: g2 });
81
+ return `${g1}${render(placeholder, kind)}`;
82
+ }
83
+ hits.push({ kind, start: offset, length: match.length, secret: g1 ?? match });
84
+ return render(placeholder, kind);
85
+ });
86
+ }
87
+ return { redacted, hits };
88
+ }
89
+ function redactJson(value, options) {
90
+ if (!options.enabled) return { value, hits: [] };
91
+ const allHits = [];
92
+ function walk(v) {
93
+ if (typeof v === "string") {
94
+ const r = redactString(v, options);
95
+ allHits.push(...r.hits);
96
+ return r.redacted;
97
+ }
98
+ if (Array.isArray(v)) return v.map(walk);
99
+ if (v && typeof v === "object") {
100
+ const out = {};
101
+ for (const [k, vv] of Object.entries(v)) {
102
+ out[k] = walk(vv);
103
+ }
104
+ return out;
105
+ }
106
+ return v;
107
+ }
108
+ const redacted = walk(value);
109
+ return { value: redacted, hits: allHits };
110
+ }
111
+ function scanString(input, options) {
112
+ const { hits } = redactString(input, { ...options, enabled: true });
113
+ return hits;
114
+ }
115
+
116
+ export {
117
+ DEFAULT_PATTERNS,
118
+ redactString,
119
+ redactJson,
120
+ scanString
121
+ };
@@ -3,14 +3,14 @@ import {
3
3
  CI_COMMENT_MARKER,
4
4
  countSeverity,
5
5
  runCi
6
- } from "./chunk-CHFFEVVT.js";
7
- import "./chunk-PGHTAKIA.js";
6
+ } from "./chunk-QNK7GUUB.js";
7
+ import "./chunk-DJGCG7SF.js";
8
8
  import "./chunk-HLWUDRBO.js";
9
9
  import "./chunk-QYQI7ZWK.js";
10
+ import "./chunk-X73Y4JZI.js";
10
11
  import "./chunk-O3XSFPYH.js";
11
- import "./chunk-IV6GCDVR.js";
12
12
  import "./chunk-5ULLIOVC.js";
13
- import "./chunk-SMQHEJSH.js";
13
+ import "./chunk-66CE3E6X.js";
14
14
  import "./chunk-IW3Q7AE5.js";
15
15
  export {
16
16
  CI_COMMENT_MARKER,
@@ -6,8 +6,8 @@ import {
6
6
  formatCiResult,
7
7
  formatCiSarif,
8
8
  normalizeCiThresholds
9
- } from "./chunk-PGHTAKIA.js";
10
- import "./chunk-SMQHEJSH.js";
9
+ } from "./chunk-DJGCG7SF.js";
10
+ import "./chunk-66CE3E6X.js";
11
11
  export {
12
12
  DEFAULT_CI_THRESHOLDS,
13
13
  detectCiGateSignals,
@@ -38,7 +38,7 @@ import {
38
38
  TEST_TIMEOUT,
39
39
  VERSION,
40
40
  buildUserIdentityPrompt
41
- } from "./chunk-SMQHEJSH.js";
41
+ } from "./chunk-66CE3E6X.js";
42
42
  export {
43
43
  AGENTIC_BEHAVIOR_GUIDELINE,
44
44
  APP_NAME,
@@ -2,14 +2,14 @@
2
2
  import {
3
3
  formatDoctorReport,
4
4
  runDoctorCli
5
- } from "./chunk-3AIR7N5M.js";
5
+ } from "./chunk-HNJX3N2V.js";
6
6
  import "./chunk-QYQI7ZWK.js";
7
- import "./chunk-2LLQOMMW.js";
7
+ import "./chunk-X73Y4JZI.js";
8
+ import "./chunk-EMFCHE2B.js";
8
9
  import "./chunk-O3XSFPYH.js";
9
- import "./chunk-IV6GCDVR.js";
10
10
  import "./chunk-5ULLIOVC.js";
11
11
  import "./chunk-HOSJZMQS.js";
12
- import "./chunk-SMQHEJSH.js";
12
+ import "./chunk-66CE3E6X.js";
13
13
  import "./chunk-IW3Q7AE5.js";
14
14
  export {
15
15
  formatDoctorReport,