jinzd-ai-cli 0.4.241 → 0.4.242

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-D3UHL432.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-NAQPP3GZ.js → chunk-6ZWNXHNH.js} +1 -1
  6. package/dist/chunk-B5TYJO7V.js +123 -0
  7. package/dist/chunk-D27TMEOV.js +327 -0
  8. package/dist/{chunk-2QQKS56X.js → chunk-DT77O33T.js} +1 -1
  9. package/dist/{chunk-GORPFFV4.js → chunk-FEMPCNX3.js} +79 -252
  10. package/dist/{chunk-3AIR7N5M.js → chunk-FRNWQIG6.js} +5 -5
  11. package/dist/{chunk-5MYPIQ3Y.js → chunk-IGPFYNZA.js} +4 -2
  12. package/dist/{chunk-2LLQOMMW.js → chunk-J363BGOH.js} +1 -1
  13. package/dist/{chunk-JMP3LJC2.js → chunk-KWQKQVPH.js} +4 -2
  14. package/dist/{chunk-PGHTAKIA.js → chunk-LHINXQZF.js} +1 -1
  15. package/dist/{chunk-ZPXMT2B6.js → chunk-MC2ZNPBC.js} +4 -163
  16. package/dist/chunk-MKLPAQT6.js +164 -0
  17. package/dist/{chunk-UO3XWZFE.js → chunk-NV63EYFL.js} +3 -119
  18. package/dist/{chunk-CHFFEVVT.js → chunk-OO2PHOXD.js} +3 -3
  19. package/dist/{chunk-KCEO2XJ4.js → chunk-PJ3ONBWZ.js} +0 -119
  20. package/dist/{chunk-LXSUPLCK.js → chunk-PSP6UIKL.js} +2 -2
  21. package/dist/chunk-UWV2NBCO.js +418 -0
  22. package/dist/{chunk-IV6GCDVR.js → chunk-WMAXYDCB.js} +7 -1
  23. package/dist/{chunk-SMQHEJSH.js → chunk-Y4CMNXZH.js} +1 -1
  24. package/dist/chunk-YVTASHS5.js +121 -0
  25. package/dist/{ci-T2ABDMMQ.js → ci-WSED3Z6V.js} +4 -4
  26. package/dist/{ci-format-S2PPU27O.js → ci-format-6AYL2X2J.js} +2 -2
  27. package/dist/{constants-RS7OUU7X.js → constants-FJ3L5GYO.js} +1 -1
  28. package/dist/{doctor-cli-TGAW6RAP.js → doctor-cli-HCP56MKH.js} +4 -4
  29. package/dist/electron-server.js +423 -572
  30. package/dist/{hub-GL5ZU6WS.js → hub-K5MYSVEF.js} +2 -2
  31. package/dist/index.js +203 -58
  32. package/dist/{persist-AMH5SQ4W.js → persist-WGIJ6RXY.js} +4 -3
  33. package/dist/persistent-memory-EYAKL2XA.js +55 -0
  34. package/dist/persistent-memory-HWFNDQFQ.js +53 -0
  35. package/dist/{pr-TJFKUZ4B.js → pr-OICMUN6R.js} +5 -5
  36. package/dist/{run-tests-EWMU34A4.js → run-tests-B3FSE5NV.js} +2 -1
  37. package/dist/{run-tests-KEI7IXTB.js → run-tests-DSEW5HED.js} +2 -2
  38. package/dist/{server-UVS2D4M6.js → server-2FS65CQZ.js} +130 -47
  39. package/dist/{server-E545UIX7.js → server-EDCGPEGC.js} +8 -6
  40. package/dist/{task-orchestrator-PWXV2EBN.js → task-orchestrator-WCZYV3TE.js} +8 -6
  41. package/dist/{usage-EDJ6QOWB.js → usage-WVUYNLOD.js} +2 -2
  42. package/package.json +1 -1
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  truncateForPersist
4
- } from "./chunk-GORPFFV4.js";
4
+ } from "./chunk-FEMPCNX3.js";
5
5
  import {
6
6
  APP_NAME,
7
7
  CONFIG_DIR_NAME,
@@ -11,7 +11,7 @@ import {
11
11
  MCP_PROTOCOL_VERSION,
12
12
  MCP_TOOL_PREFIX,
13
13
  VERSION
14
- } from "./chunk-SMQHEJSH.js";
14
+ } from "./chunk-Y4CMNXZH.js";
15
15
  import {
16
16
  atomicWriteFileSync
17
17
  } from "./chunk-IW3Q7AE5.js";
@@ -0,0 +1,418 @@
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-MKLPAQT6.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
+ sensitivity: isValidSensitivity(raw.sensitivity) ? raw.sensitivity : "low",
151
+ approved: raw.approved === true,
152
+ redactedKinds: Array.isArray(raw.redactedKinds) ? raw.redactedKinds.filter((x) => typeof x === "string") : void 0
153
+ };
154
+ }
155
+ function legacyEntriesFromMarkdown(configDir) {
156
+ const file = memoryMarkdownPath(configDir);
157
+ if (!existsSync2(file)) return [];
158
+ const content = readFileSync(file, "utf-8").trim();
159
+ if (!content) return [];
160
+ const parts = content.split(/\n(?=##\s+\d{4}-\d{2}-\d{2})/g);
161
+ const chunks = parts.length > 1 ? parts : [content];
162
+ return chunks.map((chunk, index) => {
163
+ const match = chunk.match(/^##\s+([^\n]+)\n([\s\S]*)$/);
164
+ const body = (match ? match[2] : chunk).trim();
165
+ const parsed = match ? Date.parse(match[1].replace(" ", "T")) : NaN;
166
+ const createdAt = Number.isFinite(parsed) ? new Date(parsed).toISOString() : nowIso();
167
+ const id = "legacy-" + createHash("sha1").update(`${index}
168
+ ${chunk}`).digest("hex").slice(0, 12);
169
+ return normalizeEntry({ id, content: body, scope: "personal", createdAt, updatedAt: createdAt, sensitivity: "low", approved: true });
170
+ }).filter((entry) => Boolean(entry));
171
+ }
172
+ function loadMemoryEntries(configDir) {
173
+ const file = memoryStorePath(configDir);
174
+ if (!existsSync2(file)) return legacyEntriesFromMarkdown(configDir);
175
+ const text = readFileSync(file, "utf-8").trim();
176
+ if (!text) return [];
177
+ const entries = [];
178
+ for (const line of text.split("\n")) {
179
+ const trimmed = line.trim();
180
+ if (!trimmed) continue;
181
+ try {
182
+ const entry = normalizeEntry(JSON.parse(trimmed));
183
+ if (entry) entries.push(entry);
184
+ } catch {
185
+ }
186
+ }
187
+ return entries;
188
+ }
189
+ function saveMemoryEntries(configDir, entries) {
190
+ mkdirSync(configDir, { recursive: true });
191
+ const jsonl = entries.map((entry) => JSON.stringify(entry)).join("\n");
192
+ atomicWriteFileSync(memoryStorePath(configDir), jsonl ? jsonl + "\n" : "");
193
+ }
194
+ function isMemoryExpired(entry, at = /* @__PURE__ */ new Date()) {
195
+ return Boolean(entry.expiresAt && Date.parse(entry.expiresAt) <= at.getTime());
196
+ }
197
+ function isMemoryVisibleInProject(entry, cwd = process.cwd()) {
198
+ if (entry.scope !== "project") return true;
199
+ return entry.sourceProject === getProjectKey(cwd);
200
+ }
201
+ function listMemoryEntries(configDir, options = {}) {
202
+ const cwd = options.cwd ?? process.cwd();
203
+ return loadMemoryEntries(configDir).filter((entry) => options.includeRejected || entry.approved).filter((entry) => options.includeExpired || !isMemoryExpired(entry)).filter((entry) => isMemoryVisibleInProject(entry, cwd));
204
+ }
205
+ function addMemoryEntry(configDir, content, options = {}) {
206
+ const original = content.trim();
207
+ if (!original) throw new Error("memory content is required");
208
+ const redact = options.redact ?? true;
209
+ const redacted = redact ? redactString(original, { enabled: true, patterns: options.patterns ?? DEFAULT_PATTERNS, customRegexes: options.customPatterns }) : { redacted: original, hits: [] };
210
+ const sensitivity = options.sensitivity ?? (redacted.hits.length > 0 ? "high" : "low");
211
+ const approved = options.approved ?? (sensitivity === "low" && redacted.hits.length === 0 && options.source !== "tool");
212
+ const scope = options.scope ?? "personal";
213
+ const timestamp = nowIso();
214
+ const entry = {
215
+ id: randomUUID(),
216
+ content: redacted.redacted.trim(),
217
+ scope,
218
+ source: options.source,
219
+ sourceSession: options.sourceSession,
220
+ sourceProject: scope === "project" ? getProjectKey(options.cwd) : void 0,
221
+ createdAt: timestamp,
222
+ updatedAt: timestamp,
223
+ expiresAt: options.expiresAt,
224
+ sensitivity,
225
+ approved,
226
+ redactedKinds: [...new Set(redacted.hits.map((hit) => hit.kind))]
227
+ };
228
+ const entries = loadMemoryEntries(configDir);
229
+ entries.push(entry);
230
+ saveMemoryEntries(configDir, entries);
231
+ return entry;
232
+ }
233
+ function updateMemoryEntry(configDir, idPrefix, newContent, options = {}) {
234
+ const content = newContent.trim();
235
+ if (!content) throw new Error("memory content is required");
236
+ const entries = loadMemoryEntries(configDir);
237
+ const entry = findUnique(entries, idPrefix);
238
+ const redact = options.redact ?? true;
239
+ const redacted = redact ? redactString(content, { enabled: true, patterns: options.patterns ?? DEFAULT_PATTERNS, customRegexes: options.customPatterns }) : { redacted: content, hits: [] };
240
+ entry.content = redacted.redacted.trim();
241
+ entry.updatedAt = nowIso();
242
+ entry.redactedKinds = [...new Set(redacted.hits.map((hit) => hit.kind))];
243
+ if (redacted.hits.length > 0) {
244
+ entry.sensitivity = "high";
245
+ entry.approved = false;
246
+ } else if (options.source === "tool") {
247
+ entry.approved = false;
248
+ }
249
+ saveMemoryEntries(configDir, entries);
250
+ return entry;
251
+ }
252
+ function updateMemoryApproval(configDir, idPrefix, approved) {
253
+ const entries = loadMemoryEntries(configDir);
254
+ const entry = findUnique(entries, idPrefix);
255
+ entry.approved = approved;
256
+ entry.updatedAt = nowIso();
257
+ saveMemoryEntries(configDir, entries);
258
+ return entry;
259
+ }
260
+ function deleteMemoryEntry(configDir, idPrefix) {
261
+ const entries = loadMemoryEntries(configDir);
262
+ const entry = findUnique(entries, idPrefix);
263
+ saveMemoryEntries(configDir, entries.filter((candidate) => candidate.id !== entry.id));
264
+ return entry;
265
+ }
266
+ function expireMemoryEntry(configDir, idPrefix, expiresAt = nowIso()) {
267
+ const entries = loadMemoryEntries(configDir);
268
+ const entry = findUnique(entries, idPrefix);
269
+ entry.expiresAt = expiresAt;
270
+ entry.updatedAt = nowIso();
271
+ saveMemoryEntries(configDir, entries);
272
+ return entry;
273
+ }
274
+ function countPendingMemories(configDir) {
275
+ return loadMemoryEntries(configDir).filter((entry) => !entry.approved && !isMemoryExpired(entry)).length;
276
+ }
277
+ function clearMemoryEntries(configDir) {
278
+ const entries = loadMemoryEntries(configDir);
279
+ const storePath = memoryStorePath(configDir);
280
+ let backupPath = null;
281
+ if (entries.length > 0) {
282
+ const sourceFile = existsSync2(storePath) ? storePath : memoryMarkdownPath(configDir);
283
+ if (existsSync2(sourceFile)) {
284
+ backupPath = `${sourceFile}.bak.${(/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-")}`;
285
+ copyFileSync(sourceFile, backupPath);
286
+ }
287
+ }
288
+ mkdirSync(configDir, { recursive: true });
289
+ atomicWriteFileSync(storePath, "");
290
+ atomicWriteFileSync(memoryMarkdownPath(configDir), "");
291
+ return { backupPath, cleared: entries.length };
292
+ }
293
+ function exportMemoryEntries(configDir, cwd = process.cwd(), format = "json") {
294
+ if (format === "md") {
295
+ const active = listMemoryEntries(configDir, { cwd }).filter((entry) => entry.approved && !isMemoryExpired(entry));
296
+ return renderMemoryMarkdown(active);
297
+ }
298
+ return JSON.stringify(listMemoryEntries(configDir, { cwd, includeExpired: true, includeRejected: true }), null, 2);
299
+ }
300
+ function findUnique(entries, idPrefix) {
301
+ const matches = entries.filter((entry) => entry.id.startsWith(idPrefix));
302
+ if (matches.length === 0) throw new Error(`memory id not found: ${idPrefix}`);
303
+ if (matches.length > 1) throw new Error(`memory id is ambiguous: ${idPrefix}`);
304
+ return matches[0];
305
+ }
306
+ function renderMemoryMarkdown(entries) {
307
+ return entries.map((entry) => {
308
+ const date = entry.createdAt.replace("T", " ").slice(0, 19);
309
+ const source = [
310
+ `id:${entry.id.slice(0, 8)}`,
311
+ `scope:${entry.scope}`,
312
+ entry.source ? `source:${entry.source}` : "",
313
+ entry.sourceSession ? `session:${entry.sourceSession.slice(0, 8)}` : "",
314
+ entry.sourceProject ? `project:${entry.sourceProject}` : "",
315
+ `sensitivity:${entry.sensitivity}`
316
+ ].filter(Boolean).join(" \xB7 ");
317
+ return `## ${date}
318
+ <!-- ${source} -->
319
+ ${entry.content}
320
+ `;
321
+ }).join("\n");
322
+ }
323
+ function syncLegacyMarkdown(configDir, entries = loadMemoryEntries(configDir)) {
324
+ mkdirSync(dirname(memoryMarkdownPath(configDir)), { recursive: true });
325
+ const active = entries.filter((entry) => entry.approved && !isMemoryExpired(entry));
326
+ atomicWriteFileSync(memoryMarkdownPath(configDir), renderMemoryMarkdown(active));
327
+ }
328
+ function formatMemoryForPrompt(configDir, cwd = process.cwd(), maxChars = MEMORY_MAX_CHARS) {
329
+ const entries = listMemoryEntries(configDir, { cwd }).filter((entry) => entry.approved && !isMemoryExpired(entry));
330
+ if (entries.length === 0) return null;
331
+ let content = entries.map((entry) => {
332
+ const meta = [
333
+ `id ${entry.id.slice(0, 8)}`,
334
+ entry.scope,
335
+ entry.source ?? "unknown",
336
+ entry.createdAt.slice(0, 10)
337
+ ].join(" \xB7 ");
338
+ return `## ${meta}
339
+ ${entry.content}`;
340
+ }).join("\n\n");
341
+ let entryCount = entries.length;
342
+ let truncated = false;
343
+ if (content.length > maxChars) {
344
+ truncated = true;
345
+ content = content.slice(-maxChars);
346
+ const firstEntry = content.indexOf("\n## ");
347
+ if (firstEntry !== -1) content = content.slice(firstEntry + 1);
348
+ entryCount = Math.max(1, (content.match(/^## /gm) ?? []).length);
349
+ 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>.)
350
+
351
+ ` + content;
352
+ }
353
+ return { content, entryCount, totalCount: entries.length, truncated };
354
+ }
355
+ function findSimilarMemory(entries, content) {
356
+ const normalize = (s) => s.toLowerCase().replace(/\s+/g, " ").trim();
357
+ const target = normalize(content);
358
+ if (target.length < 8) return null;
359
+ for (const entry of entries) {
360
+ const existing = normalize(entry.content);
361
+ if (existing === target || existing.includes(target) || target.includes(existing)) return entry;
362
+ }
363
+ return null;
364
+ }
365
+ function listOrphanedProjectMemories(configDir) {
366
+ return loadMemoryEntries(configDir).filter((entry) => entry.scope === "project" && entry.sourceProject !== void 0 && !existsSync2(entry.sourceProject));
367
+ }
368
+ function rebindMemoryProject(configDir, idPrefix, newProjectPath) {
369
+ const entries = loadMemoryEntries(configDir);
370
+ const entry = findUnique(entries, idPrefix);
371
+ if (entry.scope !== "project") throw new Error(`memory ${entry.id.slice(0, 8)} is not project-scoped (scope: ${entry.scope})`);
372
+ entry.sourceProject = getProjectKey(resolve(newProjectPath));
373
+ entry.updatedAt = nowIso();
374
+ saveMemoryEntries(configDir, entries);
375
+ return entry;
376
+ }
377
+ function detectStaleLegacyMarkdownEdit(configDir) {
378
+ const jsonlPath = memoryStorePath(configDir);
379
+ const mdPath = memoryMarkdownPath(configDir);
380
+ if (!existsSync2(jsonlPath) || !existsSync2(mdPath)) return false;
381
+ try {
382
+ if (!readFileSync(mdPath, "utf-8").trim()) return false;
383
+ const jsonlMtime = statSync(jsonlPath).mtimeMs;
384
+ const mdMtime = statSync(mdPath).mtimeMs;
385
+ return mdMtime > jsonlMtime + 2e3;
386
+ } catch {
387
+ return false;
388
+ }
389
+ }
390
+
391
+ export {
392
+ getGitRoot,
393
+ getGitContext,
394
+ formatGitContextForPrompt,
395
+ parseWritableScope,
396
+ memoryStorePath,
397
+ memoryMarkdownPath,
398
+ getProjectKey,
399
+ loadMemoryEntries,
400
+ isMemoryExpired,
401
+ isMemoryVisibleInProject,
402
+ listMemoryEntries,
403
+ addMemoryEntry,
404
+ updateMemoryEntry,
405
+ updateMemoryApproval,
406
+ deleteMemoryEntry,
407
+ expireMemoryEntry,
408
+ countPendingMemories,
409
+ clearMemoryEntries,
410
+ exportMemoryEntries,
411
+ renderMemoryMarkdown,
412
+ syncLegacyMarkdown,
413
+ formatMemoryForPrompt,
414
+ findSimilarMemory,
415
+ listOrphanedProjectMemories,
416
+ rebindMemoryProject,
417
+ detectStaleLegacyMarkdownEdit
418
+ };
@@ -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-Y4CMNXZH.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 环境变量覆盖
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // src/core/constants.ts
4
- var VERSION = "0.4.241";
4
+ var VERSION = "0.4.242";
5
5
  var APP_NAME = "ai-cli";
6
6
  var CONFIG_DIR_NAME = ".aicli";
7
7
  var CONFIG_FILE_NAME = "config.json";
@@ -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-OO2PHOXD.js";
7
+ import "./chunk-LHINXQZF.js";
8
8
  import "./chunk-HLWUDRBO.js";
9
9
  import "./chunk-QYQI7ZWK.js";
10
+ import "./chunk-WMAXYDCB.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-Y4CMNXZH.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-LHINXQZF.js";
10
+ import "./chunk-Y4CMNXZH.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-Y4CMNXZH.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-FRNWQIG6.js";
6
6
  import "./chunk-QYQI7ZWK.js";
7
- import "./chunk-2LLQOMMW.js";
7
+ import "./chunk-WMAXYDCB.js";
8
+ import "./chunk-J363BGOH.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-Y4CMNXZH.js";
13
13
  import "./chunk-IW3Q7AE5.js";
14
14
  export {
15
15
  formatDoctorReport,