evolcore 0.0.28 → 0.0.29

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 (183) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/bin/ec-safe-output.js +270 -54
  3. package/dist/agents/baseagent-availability.js +37 -1
  4. package/dist/agents/baseagent.js +19 -9
  5. package/dist/agents/claude-runner.js +171 -13
  6. package/dist/agents/codex/app-server-client.js +6 -2
  7. package/dist/agents/codex/diagnostics.js +4 -3
  8. package/dist/agents/codex-runner.js +396 -95
  9. package/dist/agents/gemini-runner.js +35 -17
  10. package/dist/aun/group/history.js +130 -90
  11. package/dist/aun/group/operations.js +1 -3
  12. package/dist/aun/menu/transport.js +5 -3
  13. package/dist/aun/msg/managed-operation.js +70 -0
  14. package/dist/aun/msg/p2p.js +1 -3
  15. package/dist/aun/msg/upload.js +2 -2
  16. package/dist/aun/outbox.js +1 -37
  17. package/dist/channels/aun.js +249 -89
  18. package/dist/channels/daemon.js +18 -1
  19. package/dist/channels/feishu.js +1 -2
  20. package/dist/cli/agent-command.js +23 -3
  21. package/dist/cli/agent-create.js +68 -2
  22. package/dist/cli/agent.js +12 -2
  23. package/dist/cli/aun-commands.js +87 -13
  24. package/dist/cli/cli-argv.js +26 -0
  25. package/dist/cli/daemon-commands.js +40 -10
  26. package/dist/cli/data-command.js +4 -4
  27. package/dist/cli/handoff-command.js +2 -0
  28. package/dist/cli/index.js +18 -1
  29. package/dist/cli/managed-command-guard.js +10 -0
  30. package/dist/cli/memory-command.js +441 -0
  31. package/dist/cli/restart-monitor.js +62 -6
  32. package/dist/cli/skill-command.js +41 -0
  33. package/dist/cli/task-context.js +2 -23
  34. package/dist/cli/trigger-command.js +9 -1
  35. package/dist/cli/watch-msg-diag.js +209 -0
  36. package/dist/config/config-field-policy.js +52 -3
  37. package/dist/config/config-manager.js +17 -4
  38. package/dist/config/contact-book-store.js +2 -2
  39. package/dist/config/mention-mode.js +7 -2
  40. package/dist/config/merge.js +28 -9
  41. package/dist/config/resolved-config-op.js +1 -1
  42. package/dist/config/runtime-env.js +137 -0
  43. package/dist/config/schema-registry.js +11 -1
  44. package/dist/config-store.js +10 -10
  45. package/dist/core/audit/event-key.js +3 -1
  46. package/dist/core/audit/log-integrity.js +4 -0
  47. package/dist/core/auth/agent-delegation.js +2 -1
  48. package/dist/core/auth/auth-gateway.js +13 -6
  49. package/dist/core/auth/authorization-audit.js +54 -19
  50. package/dist/core/auth/operation-authorizer.js +17 -0
  51. package/dist/core/auth/operation-catalog.js +9 -0
  52. package/dist/core/causation/context.js +5 -1
  53. package/dist/core/command/agent-control.js +1 -2
  54. package/dist/core/command/command-handler.js +51 -23
  55. package/dist/core/command/memory-slash.js +270 -0
  56. package/dist/core/command/menu-catalog.js +1 -0
  57. package/dist/core/command/menu-handler.js +315 -60
  58. package/dist/core/command/menu-protocol.js +42 -4
  59. package/dist/core/command/slash-gate.js +10 -10
  60. package/dist/core/command/slash-handler.js +206 -64
  61. package/dist/core/data-migration.js +282 -505
  62. package/dist/core/event-catalog.js +26 -0
  63. package/dist/core/evolagent-registry.js +6 -0
  64. package/dist/core/evolagent.js +0 -10
  65. package/dist/core/handoff/store.js +1 -20
  66. package/dist/core/inference/text-inference.js +57 -1
  67. package/dist/core/memory/consolidation-context.js +505 -0
  68. package/dist/core/memory/consolidation-ops-records.js +124 -0
  69. package/dist/core/memory/consolidation-trigger.js +394 -0
  70. package/dist/core/memory/index-manager.js +308 -0
  71. package/dist/core/memory/launch-consolidation.js +395 -0
  72. package/dist/core/memory/memory-admin.js +234 -0
  73. package/dist/core/memory/memory-document.js +100 -0
  74. package/dist/core/memory/memory-session-policy.js +41 -0
  75. package/dist/core/memory/memory-signature.js +257 -0
  76. package/dist/core/memory/memory-writer.js +432 -0
  77. package/dist/core/memory/staleness.js +117 -0
  78. package/dist/core/message/auto-mention-gate.js +454 -0
  79. package/dist/core/message/im-renderer.js +69 -16
  80. package/dist/core/message/message-bridge.js +287 -21
  81. package/dist/core/message/message-log.js +1 -0
  82. package/dist/core/message/message-queue.js +114 -27
  83. package/dist/core/message/pending-hints.js +329 -161
  84. package/dist/core/message/response-engine.js +567 -80
  85. package/dist/core/permission/ec-command-parser.js +1029 -80
  86. package/dist/core/permission/protected-paths.js +376 -126
  87. package/dist/core/permission/sandbox-runtime.js +40 -12
  88. package/dist/core/permission/tool-error-code.js +96 -13
  89. package/dist/core/permission/tool-policy.js +276 -23
  90. package/dist/core/role/runtime-policy.js +3 -7
  91. package/dist/core/session/{runner-task-history.js → runner-event-summary.js} +1 -1
  92. package/dist/core/session/session-fs-store.js +0 -76
  93. package/dist/core/session/session-idle-monitor.js +355 -0
  94. package/dist/core/session/session-manager.js +478 -17
  95. package/dist/core/session/session-mapper.js +33 -16
  96. package/dist/core/session/session-renew.js +217 -155
  97. package/dist/core/session/{session-turns.js → session-transcript.js} +35 -6
  98. package/dist/core/skill/global-watcher.js +282 -0
  99. package/dist/core/skill/index-generator.js +84 -0
  100. package/dist/core/skill/startup.js +73 -0
  101. package/dist/core/skill/switch-resolver.js +115 -0
  102. package/dist/core/task/task-activity-store.js +219 -0
  103. package/dist/eck/kit-renderer.js +30 -3
  104. package/dist/eck/manifest-engine.js +26 -4
  105. package/dist/index.js +708 -18
  106. package/dist/ipc.js +564 -2
  107. package/dist/paths.js +30 -9
  108. package/dist/response-system/engines/v1/proactive-flow.js +116 -15
  109. package/dist/response-system/modes/single-session/index.js +3 -0
  110. package/dist/trigger/feedback.js +1 -0
  111. package/dist/trigger/manager.js +0 -10
  112. package/dist/trigger/scheduler.js +11 -0
  113. package/dist/trigger/validation.js +22 -0
  114. package/dist/utils/avatar-upload.js +59 -18
  115. package/dist/utils/error-dict.json +6 -3
  116. package/dist/utils/error-utils.js +27 -0
  117. package/dist/utils/logger.js +32 -10
  118. package/dist/utils/media-cache.js +84 -0
  119. package/dist/utils/msg-diag.js +97 -0
  120. package/dist/utils/welcome.js +37 -8
  121. package/kits/docs/AUTHORING.md +314 -0
  122. package/kits/docs/GUIDE.md +2 -0
  123. package/kits/docs/INDEX.md +2 -0
  124. package/kits/docs/aun/CHEATSHEET.md +4 -0
  125. package/kits/docs/aun/SDK.md +60 -0
  126. package/kits/docs/context-assembly.md +10 -6
  127. package/kits/docs/evolcore/INDEX.md +17 -3
  128. package/kits/docs/evolcore/agent-create.md +24 -0
  129. package/kits/docs/evolcore/agent.md +5 -2
  130. package/kits/docs/evolcore/config.md +1 -1
  131. package/kits/docs/evolcore/ec-capabilities.md +364 -0
  132. package/kits/docs/evolcore/group.md +34 -18
  133. package/kits/docs/evolcore/memory-architecture.md +78 -0
  134. package/kits/docs/evolcore/memory-consolidation-batches.md +133 -0
  135. package/kits/docs/evolcore/memory-consolidation.md +263 -0
  136. package/kits/docs/evolcore/memory-tags.md +63 -0
  137. package/kits/docs/evolcore/memory.md +110 -0
  138. package/kits/docs/evolcore/menu-client.md +27 -11
  139. package/kits/{schemas → docs/evolcore}/menu-exec-schema-commands.md +12 -6
  140. package/kits/docs/evolcore/menu-protocol.md +213 -0
  141. package/kits/docs/evolcore/msg.md +2 -0
  142. package/kits/docs/evolcore/trigger.md +7 -5
  143. package/kits/docs/path-registry.md +6 -8
  144. package/kits/docs/prompt-loading-architecture.md +5 -3
  145. package/kits/docs/venues/group.md +5 -4
  146. package/kits/eck_manifest.json +294 -88
  147. package/kits/eck_manifest.memory.json +257 -0
  148. package/kits/rules/03-identity.md +8 -8
  149. package/kits/rules/05-venue.md +4 -2
  150. package/kits/rules/06-channel.md +1 -1
  151. package/kits/schemas/_meta.json +276 -47
  152. package/kits/schemas/agent-config.schema.12.json +38 -2
  153. package/kits/schemas/agent-config.schema.13.json +591 -0
  154. package/kits/schemas/daemon.schema.6.json +9 -9
  155. package/kits/schemas/defaults.schema.5.json +29 -0
  156. package/kits/schemas/relation-config.schema.8.json +14 -1
  157. package/kits/schemas/relation-config.schema.9.json +129 -0
  158. package/kits/schemas/role-config.schema.1.json +3 -3
  159. package/kits/skills/.switches.json +3 -0
  160. package/kits/skills/ec-skill/SKILL.md +217 -0
  161. package/kits/skills/ec-skill/scripts/cli.mjs +128 -0
  162. package/kits/skills/ec-skill/scripts/deps.mjs +107 -0
  163. package/kits/skills/ec-skill/scripts/frontmatter.mjs +104 -0
  164. package/kits/skills/ec-skill/scripts/skill.mjs +629 -0
  165. package/kits/skills/ec-skill/scripts/template/SKILL.md.tmpl +30 -0
  166. package/kits/templates/decision/auto-mention.md +25 -0
  167. package/kits/templates/decision/session-renew.md +22 -0
  168. package/kits/templates/memory-consolidation/forget.md +104 -0
  169. package/kits/templates/memory-consolidation/generalize.md +76 -0
  170. package/kits/templates/memory-consolidation/notes.md +133 -0
  171. package/kits/templates/memory-consolidation/profile.md +58 -0
  172. package/kits/templates/memory-consolidation/promote.md +72 -0
  173. package/kits/templates/memory-consolidation/workflow.md +75 -0
  174. package/kits/templates/system-fragments/bootstrap.md +14 -5
  175. package/kits/templates/system-fragments/commands.md +1 -0
  176. package/kits/templates/system-fragments/group-history.md +6 -0
  177. package/kits/templates/system-fragments/identity.md +0 -3
  178. package/kits/templates/system-fragments/memory.md +79 -0
  179. package/kits/templates/system-fragments/reply.md +1 -0
  180. package/kits/templates/system-fragments/venue.md +1 -1
  181. package/package.json +4 -6
  182. package/dist/trigger/legacy-session-history.js +0 -19
  183. package/kits/templates/system-fragments/group-unread-history.md +0 -7
@@ -0,0 +1,308 @@
1
+ /**
2
+ * index-manager.ts — MEMORY.md 索引管理(v3)
3
+ *
4
+ * 负责:
5
+ * 1. 重建索引:扫描 consolidated/,逐份验签,按 tags 筛选,截断到 indexMaxBytes
6
+ * 2. 追加条目:单条写入时的快速路径(写入后仍以 rebuild 为准)
7
+ *
8
+ * 排序与超期判断一律读**文件 mtime**;frontmatter 的 updatedAt 只作可读信息。
9
+ *
10
+ * 索引行格式:`- 20261005 [slug] — 描述 [tag1,tag2]`
11
+ * slug 即文件名(consolidated/<slug>.md),不再重复写路径。
12
+ *
13
+ * 验签规则(v3 收紧):只认 `verified`。
14
+ * - `invalid`(签名不符)→ 移入 archive/invalid/,不进索引
15
+ * - `unsigned`(没有签名块)→ 同样移入 archive/invalid/,不进索引
16
+ * 主会话和整理会话都写不到 consolidated/(create 由程序签名),出现未签名文件
17
+ * 说明有人绕过写入通道直接落盘。
18
+ */
19
+ import fs from 'fs';
20
+ import path from 'path';
21
+ import { getMemoryArray, getMemoryString, parseMemoryDocument } from './memory-document.js';
22
+ /** 索引容量上限(字节)。实际取值由调用方从 memory.indexMaxBytes 传入。 */
23
+ export const DEFAULT_INDEX_MAX_BYTES = 5 * 1024;
24
+ /** Fast metadata index for consolidation candidate scans. */
25
+ export const MEMORY_META_VERSION = 1;
26
+ export const MEMORY_META_FILENAME = '_meta.json';
27
+ function metaPath(memoryDir) {
28
+ return path.join(memoryDir, MEMORY_META_FILENAME);
29
+ }
30
+ export function refreshMemoryMeta(memoryDir, now = Date.now()) {
31
+ try {
32
+ const consolidatedDir = path.join(memoryDir, 'consolidated');
33
+ const meta = {
34
+ v: MEMORY_META_VERSION,
35
+ updatedAt: now,
36
+ count: 0,
37
+ hasToGeneralize: false,
38
+ hasToPromote: false,
39
+ profileSlug: null,
40
+ profileMtimeMs: null,
41
+ oldestMtimeMs: null,
42
+ };
43
+ if (fs.existsSync(consolidatedDir)) {
44
+ for (const f of fs.readdirSync(consolidatedDir)) {
45
+ if (!f.endsWith('.md'))
46
+ continue;
47
+ const fp = path.join(consolidatedDir, f);
48
+ let content;
49
+ let mtimeMs;
50
+ try {
51
+ content = fs.readFileSync(fp, 'utf-8');
52
+ mtimeMs = fs.statSync(fp).mtimeMs;
53
+ }
54
+ catch {
55
+ continue;
56
+ }
57
+ meta.count++;
58
+ if (meta.oldestMtimeMs === null || mtimeMs < meta.oldestMtimeMs)
59
+ meta.oldestMtimeMs = mtimeMs;
60
+ const tags = getMemoryArray(parseMemoryDocument(content), 'tags');
61
+ if (tags.includes('to-generalize'))
62
+ meta.hasToGeneralize = true;
63
+ if (tags.includes('to-promote'))
64
+ meta.hasToPromote = true;
65
+ if (tags.includes('profile')) {
66
+ meta.profileSlug = f.replace(/\.md$/, '');
67
+ meta.profileMtimeMs = mtimeMs;
68
+ }
69
+ }
70
+ }
71
+ fs.mkdirSync(memoryDir, { recursive: true });
72
+ const tmp = `${metaPath(memoryDir)}.${process.pid}.tmp`;
73
+ fs.writeFileSync(tmp, JSON.stringify(meta), 'utf-8');
74
+ fs.renameSync(tmp, metaPath(memoryDir));
75
+ }
76
+ catch { /* metadata is an optimization and never blocks writes */ }
77
+ }
78
+ export function readMemoryMeta(memoryDir) {
79
+ try {
80
+ const p = metaPath(memoryDir);
81
+ if (!fs.existsSync(p))
82
+ return null;
83
+ const parsed = JSON.parse(fs.readFileSync(p, 'utf-8'));
84
+ if (!parsed || parsed.v !== MEMORY_META_VERSION)
85
+ return null;
86
+ if (typeof parsed.count !== 'number')
87
+ return null;
88
+ if (typeof parsed.hasToGeneralize !== 'boolean' || typeof parsed.hasToPromote !== 'boolean')
89
+ return null;
90
+ return parsed;
91
+ }
92
+ catch {
93
+ return null;
94
+ }
95
+ }
96
+ export function removeMemoryMeta(memoryDir) {
97
+ try {
98
+ fs.rmSync(metaPath(memoryDir), { force: true });
99
+ }
100
+ catch { /* ignore */ }
101
+ }
102
+ /**
103
+ * 必进索引、不被大小截断的标签。
104
+ *
105
+ * 注意「永不淘汰」**只指不会被索引的 indexMaxBytes 截断**——只要这条记忆还在,
106
+ * 就保证出现在索引里。它**不代表免于遗忘审查**:打了这些标签的记忆照样会进
107
+ * 遗忘候选、照样会被归档。遗忘审查的是「内容还成不成立」,与「在不在索引里」是两个维度。
108
+ */
109
+ const PINNED_TAGS = ['important', 'urgent', 'profile'];
110
+ /** 任务态标签,排在其后 */
111
+ const ACTIVE_STATE_TAGS = ['in-progress', 'pending', 'blocked'];
112
+ /** 不进索引的状态 */
113
+ const EXCLUDED_TAGS = ['done', 'cancelled'];
114
+ // ── 解析索引行 ──────────────────────────────────────────────────────────────
115
+ /**
116
+ * 解析索引行,支持两种格式:
117
+ * 新格式:- 20261005 [slug] — 描述 [tag1,tag2]
118
+ * 旧格式:- [描述](consolidated/<slug>.md) #kw1 #kw2
119
+ */
120
+ function parseIndexLine(line) {
121
+ const trimmed = line.trim();
122
+ if (!trimmed || trimmed.startsWith('---') || trimmed.startsWith('#'))
123
+ return null;
124
+ // 新格式:- 20261005 [slug] — 描述 [tag1,tag2]
125
+ const newFmt = trimmed.match(/^-\s+(\d{8})\s+\[([^\]]+)\]\s+—\s+(.+?)(?:\s+\[([^\]]*)\])?$/);
126
+ if (newFmt) {
127
+ const tags = newFmt[4] ? newFmt[4].split(',').map(t => t.trim()).filter(Boolean) : [];
128
+ return { slug: newFmt[2].trim(), description: newFmt[3].trim(), tags };
129
+ }
130
+ // 旧格式(兼容):- [描述](consolidated/xxx.md) #kw1 #kw2
131
+ const md = trimmed.match(/^-\s*\[([^\]]*)\]\(consolidated\/([^)]+?)\.md\)\s*(#.*)?$/);
132
+ if (md) {
133
+ const tags = (md[3] ?? '').split(/\s+/)
134
+ .map(t => t.replace(/^#/, '').trim())
135
+ .filter(Boolean);
136
+ return { slug: md[2].trim(), description: md[1].trim(), tags };
137
+ }
138
+ // 极旧格式:[path] | 描述 | #tag1 #tag2
139
+ const legacy = trimmed.match(/^\[([^\]]+)\]\s*\|\s*(.*?)(?:\s*\|\s*(#.*))?$/);
140
+ if (legacy) {
141
+ const tags = (legacy[3] ?? '').split(/\s+/)
142
+ .map(t => t.replace(/^#/, '').trim())
143
+ .filter(Boolean);
144
+ const slug = legacy[1].trim().replace(/^consolidated\//, '').replace(/\.md$/, '');
145
+ return { slug, description: legacy[2].trim(), tags };
146
+ }
147
+ return null;
148
+ }
149
+ function serializeIndex(scope, entries) {
150
+ const now = new Date().toISOString();
151
+ const lines = [
152
+ '---',
153
+ `scope: ${scope}`,
154
+ `consolidated: ./consolidated/`,
155
+ `updatedAt: ${now}`,
156
+ '---',
157
+ '',
158
+ ];
159
+ for (const e of entries) {
160
+ // 格式:- 20261005 [slug] — 描述 [tag1,tag2]
161
+ const dateStr = e.mtimeMs
162
+ ? new Date(e.mtimeMs).toISOString().slice(0, 10).replace(/-/g, '')
163
+ : new Date().toISOString().slice(0, 10).replace(/-/g, '');
164
+ const tagStr = e.tags.length > 0 ? ` [${e.tags.join(',')}]` : '';
165
+ lines.push(`- ${dateStr} [${e.slug}] — ${e.description}${tagStr}`);
166
+ }
167
+ if (entries.length === 0)
168
+ lines.push('(暂无正式记忆)');
169
+ lines.push('');
170
+ return lines.join('\n');
171
+ }
172
+ // ── 排序 ────────────────────────────────────────────────────────────────────
173
+ /** 分类权重,越小越靠前。 */
174
+ function rankOf(tags) {
175
+ if (tags.some(t => PINNED_TAGS.includes(t)))
176
+ return 0;
177
+ if (tags.some(t => ACTIVE_STATE_TAGS.includes(t)))
178
+ return 1;
179
+ if (tags.includes('long-term'))
180
+ return 2;
181
+ return 3;
182
+ }
183
+ /**
184
+ * 排序:权重升序,同权重按 mtime 降序(新的在前)。
185
+ * 不做稳定性依赖——mtime 相同时按路径排,保证结果可复现。
186
+ */
187
+ function sortEntries(entries) {
188
+ entries.sort((a, b) => {
189
+ const ra = rankOf(a.tags);
190
+ const rb = rankOf(b.tags);
191
+ if (ra !== rb)
192
+ return ra - rb;
193
+ const ma = a.mtimeMs ?? 0;
194
+ const mb = b.mtimeMs ?? 0;
195
+ if (ma !== mb)
196
+ return mb - ma;
197
+ return a.slug.localeCompare(b.slug);
198
+ });
199
+ }
200
+ /** 按上限截断(保留权重高的),返回被保留的条目。 */
201
+ function truncateToLimit(scope, entries, maxBytes) {
202
+ let kept = [...entries];
203
+ while (kept.length > 0) {
204
+ const serialized = serializeIndex(scope, kept);
205
+ if (Buffer.byteLength(serialized, 'utf-8') <= maxBytes)
206
+ return kept;
207
+ // 从末尾(权重最低、最旧)移除
208
+ kept = kept.slice(0, -1);
209
+ }
210
+ return [];
211
+ }
212
+ // ── 读取 frontmatter ────────────────────────────────────────────────────────
213
+ // ── 追加条目(写入后的快速路径) ────────────────────────────────────────────
214
+ /**
215
+ * 向 MEMORY.md 追加(或更新)一条条目。超出上限时按权重截断。
216
+ * 注意:这不做验签,调用方应保证 entry 来自刚写入的已签名文件。
217
+ * 整理会话产生多条记忆时,应在会话结束后统一 rebuildIndex。
218
+ */
219
+ export function appendToIndex(indexPath, entry, scope, indexMaxBytes = DEFAULT_INDEX_MAX_BYTES) {
220
+ const existing = readIndexEntries(indexPath);
221
+ const idx = existing.findIndex(e => e.slug === entry.slug);
222
+ const merged = { ...entry, mtimeMs: Date.now() };
223
+ if (idx >= 0)
224
+ existing[idx] = merged;
225
+ else
226
+ existing.push(merged);
227
+ const filtered = existing.filter(e => !e.tags.some(t => EXCLUDED_TAGS.includes(t)));
228
+ sortEntries(filtered);
229
+ const kept = truncateToLimit(scope, filtered, indexMaxBytes);
230
+ writeIndexFile(indexPath, serializeIndex(scope, kept));
231
+ }
232
+ function readIndexEntries(indexPath) {
233
+ if (!fs.existsSync(indexPath))
234
+ return [];
235
+ try {
236
+ return fs.readFileSync(indexPath, 'utf-8')
237
+ .split('\n')
238
+ .map(parseIndexLine)
239
+ .filter((e) => e !== null);
240
+ }
241
+ catch {
242
+ return [];
243
+ }
244
+ }
245
+ function writeIndexFile(indexPath, content) {
246
+ const tmp = indexPath + '.tmp';
247
+ fs.writeFileSync(tmp, content, 'utf-8');
248
+ fs.renameSync(tmp, indexPath);
249
+ }
250
+ // ── 重建索引 ────────────────────────────────────────────────────────────────
251
+ /**
252
+ * 从 consolidated/ 重建 MEMORY.md。
253
+ *
254
+ * verifyFn 可选。不传时所有文件视为通过,直接进索引,不移动任何文件。
255
+ * 传入时验签失败的文件只跳过(不进索引),同样不移动文件——移动行为是验签门控
256
+ * 开启后才需要的,暂未实现。排序读文件 mtime,按 tags 分级,超 indexMaxBytes
257
+ * 从末尾截断(pinned 标签保留)。
258
+ */
259
+ export async function rebuildIndex(consolidatedDir, indexPath, scope, invalidDir, verifyFn, indexMaxBytes = DEFAULT_INDEX_MAX_BYTES) {
260
+ if (!fs.existsSync(consolidatedDir)) {
261
+ writeIndexFile(indexPath, serializeIndex(scope, []));
262
+ return { indexed: 0, invalidMoved: 0, verifiedFiles: [] };
263
+ }
264
+ const files = fs.readdirSync(consolidatedDir).filter(f => f.endsWith('.md')).sort();
265
+ const entries = [];
266
+ const verifiedFiles = [];
267
+ for (const file of files) {
268
+ const filePath = path.join(consolidatedDir, file);
269
+ let content;
270
+ let mtimeMs;
271
+ try {
272
+ content = fs.readFileSync(filePath, 'utf-8');
273
+ mtimeMs = fs.statSync(filePath).mtimeMs;
274
+ }
275
+ catch {
276
+ continue;
277
+ }
278
+ if (verifyFn) {
279
+ const verifyResult = await verifyFn(content);
280
+ if (verifyResult.status === 'verified') {
281
+ verifiedFiles.push(file);
282
+ }
283
+ else {
284
+ // 验签失败:跳过,不进索引,不移动文件(门控开启后的移动逻辑暂未实现)
285
+ continue;
286
+ }
287
+ }
288
+ else {
289
+ // 未开启验签:所有文件视为通过
290
+ verifiedFiles.push(file);
291
+ }
292
+ const document = parseMemoryDocument(content);
293
+ const description = getMemoryString(document, 'description') ?? file.replace(/\.md$/, '');
294
+ const tags = getMemoryArray(document, 'tags');
295
+ if (tags.some(t => EXCLUDED_TAGS.includes(t)))
296
+ continue;
297
+ entries.push({
298
+ slug: file.replace(/\.md$/, ''),
299
+ description,
300
+ tags,
301
+ mtimeMs,
302
+ });
303
+ }
304
+ sortEntries(entries);
305
+ const kept = truncateToLimit(scope, entries, indexMaxBytes);
306
+ writeIndexFile(indexPath, serializeIndex(scope, kept));
307
+ return { indexed: kept.length, invalidMoved: 0, verifiedFiles };
308
+ }