@sanqianx/project-knowledge 5.0.9-rc.13 → 5.0.9-rc.15

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.
@@ -9,7 +9,7 @@
9
9
  "plugins": [
10
10
  {
11
11
  "name": "project-knowledge",
12
- "version": "5.0.9-rc.13",
12
+ "version": "5.0.9-rc.15",
13
13
  "source": "./plugins/project-knowledge",
14
14
  "description": "Search durable project decisions and implementation history through MCP."
15
15
  }
package/CHANGELOG.md CHANGED
@@ -1,5 +1,33 @@
1
1
  # Changelog
2
2
 
3
+ ## [5.0.9-rc.15] - 2026-10-11
4
+
5
+ - Serialize registration, module removal and registry deletion for each project.
6
+ Recheck membership inside the wiring lease so delayed supervisor registration
7
+ cannot resurrect a project after keep-data unbind. Unrelated projects remain
8
+ independent; failed deletion can still be repaired and retried.
9
+ - Reproduce the race with a delayed real HTTP registration and preserve the
10
+ downloaded rc.14 Node 22 UI failure. Include the regression in the package.
11
+ - Use non-binding CLI readiness probes in tests; the old bind probe could take
12
+ the exact port while its own foreground child was starting.
13
+ - Real MiniMax acceptance covers overlapping analyses, another commit and a
14
+ dirty developer draft during analysis, streaming, secondary development,
15
+ four installed clients and evidenced preference candidates. New downloaded
16
+ candidate acceptance and actual-user cutover still precede stable latest.
17
+
18
+ ## [5.0.9-rc.14] - 2026-10-11
19
+
20
+ - Vendor engine 0.2.3-rc.7: isolated maintenance consumes frozen evidence even
21
+ while the user edits or commits in the source repository. Do not mistake live
22
+ development for analyzer writes; retain live-source protection for custom
23
+ analyzers without the isolation capability.
24
+ - Add a paused child-worker regression covering another commit, uncommitted
25
+ drafts, frozen attribution, same-project FIFO and unchanged path/tool guards.
26
+ - Existing-user npm acceptance verified real MiniMax streaming, retained v5
27
+ history/configuration, D-root indexes and index-only e9f6560 recovery. Preserve
28
+ the actual rc.13 PROJECT_BUSY failure and require a real mid-analysis commit
29
+ retest from downloaded rc.14 before stable promotion.
30
+
3
31
  ## [5.0.9-rc.13] - 2026-10-11
4
32
 
5
33
  - Give native Agent session creation and input setup a bounded 90-second proxy
@@ -41,6 +41,8 @@ node src/bin.js serve [--port 5790] [--host 127.0.0.1] [--data DIR]
41
41
 
42
42
  本模块零新增 npm 依赖、CommonJS、Node ≥18;无头 Agent SDK 由宿主已安装的 Workbench/SDK 提供,缺失时明确失败,不退回假分析。完整产品的 Node 门槛为 ≥22。
43
43
 
44
+ 隔离无头分析只读冻结证据、只写 staging,不以开发工作树的前后变化判定失败。用户可以在分析中继续编辑或提交;当前任务仍归属冻结提交,后续提交按项目 FIFO 执行。未声明隔离冻结证据能力的自定义分析器仍保留源码变更检查;目录越界、工具权限和晋升校验不变。
45
+
44
46
  每次真实分析在只读证据区冻结已有知识的目标路径与哈希清单,覆盖未被检索选中的文件;重试复用该清单。Agent 不得把“未检索到”当作“文件不存在”,也不得用部分片段覆盖完整旧文档;无法保留原文时只追加当前提交的变更记录。已有目标仍由晋升校验拒绝非法 create,不自动改为覆盖。清单仅保存元数据,不把整个知识树复制到每次运行中。
45
47
 
46
48
  ## API
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sanqianx/ai-coding-knowledge-engine",
3
- "version": "0.2.3-rc.6",
3
+ "version": "0.2.3-rc.7",
4
4
  "description": "Knowledge engine module for project-knowledge: commit-boundary triggered knowledge analysis/generation (trigger plane) and retrieval for development assistants (retrieval plane).",
5
5
  "license": "Apache-2.0",
6
6
  "main": "src/server.js",
@@ -50,7 +50,7 @@ class EngineCoordinator extends EventEmitter {
50
50
  this.retrieval = new EngineRetrieval({ layout: this.layout, projectStore: this.projectStore, registryStore, store, memories: this.memories, vectorUrl: this.vectorUrl });
51
51
  this.conversations = new ConversationStore({ layout: this.layout, projectStore: this.projectStore });
52
52
  this.analyzer = options.analyzer || new HeadlessAnalyzer({ resolveProfile: id => this.profiles.get(id) || this.profiles.get('default'), timeoutMs: options.timeoutMs });
53
- this.promotion = new KnowledgePromotionService({ layout: this.layout, logger: this.diagnostics, projectStore: this.projectStore, analyzer: { runClaim: async input => {
53
+ this.promotion = new KnowledgePromotionService({ layout: this.layout, logger: this.diagnostics, projectStore: this.projectStore, analyzer: { sourceReadMode: this.analyzer.sourceReadMode, runClaim: async input => {
54
54
  const run = this.currentRun(input.projectId);
55
55
  store.updateRun(run.runId, { analysisRunId: input.claim.runId, phase: 'analyzing' });
56
56
  this.notify(run, '正在分析冻结的提交证据');
@@ -6,6 +6,7 @@ const { finalizeAnalysisOutput } = require('./analysis-output');
6
6
  const { freezeKnowledgeTargets } = require('./knowledge-targets');
7
7
 
8
8
  class HeadlessAnalyzer {
9
+ get sourceReadMode() { return 'isolated-frozen-evidence'; }
9
10
  constructor({ resolveProfile, timeoutMs = 15 * 60 * 1000, workerPath = path.join(__dirname, 'analysis-worker.js') } = {}) { this.resolveProfile = resolveProfile; this.timeoutMs = timeoutMs; this.workerPath = workerPath; this.children = new Map(); this.closed = false; }
10
11
  async runClaim(input) {
11
12
  if (this.closed) throw Object.assign(new Error('分析服务正在关闭。'), { code: 'WORKER_STOPPED', retryable: true });
@@ -312,7 +312,11 @@ class KnowledgePromotionService {
312
312
  if (!claim || claim.projectId !== projectId || !claim.runId || !claim.commitSha) throw new DomainError('INVALID_ARGUMENT', 'Active claim is required for knowledge promotion.');
313
313
  const operationId = input.operationId || claim.operationId || createId('op');
314
314
  const stagingPath = this.prepareStaging(projectId, claim.runId);
315
- const beforeSource = await gitStatus(config.repoPath);
315
+ // The isolated worker cannot read or write the live repository. A user may
316
+ // keep developing while it consumes frozen evidence; live status is neither
317
+ // its input nor proof of worker writes. Keep the guard for other analyzers.
318
+ const verifyLiveSource = this.analyzer?.sourceReadMode !== 'isolated-frozen-evidence';
319
+ const beforeSource = verifyLiveSource ? await gitStatus(config.repoPath) : null;
316
320
  try {
317
321
  if (!this.analyzer || typeof this.analyzer.runClaim !== 'function') throw new DomainError('INVALID_ARGUMENT', 'Knowledge analyzer is unavailable.', { status: 503, retryable: true });
318
322
  const analyzerResult = await this.analyzer.runClaim({
@@ -329,7 +333,7 @@ class KnowledgePromotionService {
329
333
  }),
330
334
  });
331
335
  if (analyzerResult && analyzerResult.ok === false) throw new DomainError('INVALID_ARGUMENT', analyzerResult.error || 'Knowledge analyzer failed.', { status: 500, retryable: true });
332
- const afterSource = await gitStatus(config.repoPath);
336
+ const afterSource = verifyLiveSource ? await gitStatus(config.repoPath) : null;
333
337
  if (beforeSource != null && afterSource != null && beforeSource !== afterSource) {
334
338
  throw new DomainError('PROJECT_BUSY', 'Source repository changed during knowledge analysis.', { status: 409, retryable: true });
335
339
  }
@@ -78,12 +78,25 @@ function isPortListening(port) {
78
78
  async function waitForListening(port, deadlineMs = 8000) {
79
79
  const deadline = Date.now() + deadlineMs;
80
80
  while (Date.now() < deadline) {
81
- if (await isPortListening(port)) return true;
81
+ if (await canConnect(port)) return true;
82
82
  await new Promise(resolve => setTimeout(resolve, 150));
83
83
  }
84
84
  return false;
85
85
  }
86
86
 
87
+ function canConnect(port) {
88
+ // A bind probe can steal the selected port exactly as the child starts.
89
+ // Readiness checks must connect, never compete with the server's listener.
90
+ return new Promise(resolve => {
91
+ const socket = net.createConnection({ host: '127.0.0.1', port });
92
+ let settled = false;
93
+ const finish = result => { if (settled) return; settled = true; socket.destroy(); resolve(result); };
94
+ socket.setTimeout(500, () => finish(false));
95
+ socket.once('connect', () => finish(true));
96
+ socket.once('error', () => finish(false));
97
+ });
98
+ }
99
+
87
100
  (async () => {
88
101
  // Make sure no leftover PID file or server from a previous run.
89
102
  removePidFile();
@@ -185,7 +198,7 @@ async function waitForListening(port, deadlineMs = 8000) {
185
198
  while (Date.now() < deadline && !detectedPort) {
186
199
  const rec = readPidFile();
187
200
  if (rec && rec.port && rec.port !== busyPort) {
188
- if (await isPortListening(rec.port)) {
201
+ if (await canConnect(rec.port)) {
189
202
  detectedPort = rec.port;
190
203
  break;
191
204
  }
@@ -108,7 +108,7 @@ async function main() {
108
108
  vectorHubUrl: DEAD_URL,
109
109
  eventBridgeUrl: stubUrl,
110
110
  engineUrl: DEAD_URL,
111
- registryStore: { listIds: () => ['demo-proj'] },
111
+ registryStore: { listIds: () => ['demo-proj'], readDisplaySnapshot: id => id === 'demo-proj' ? { displayName: 'demo' } : null },
112
112
  projectStore: makeStoreStub(),
113
113
  });
114
114
 
@@ -0,0 +1,59 @@
1
+ 'use strict';
2
+ const assert = require('node:assert/strict');
3
+ const http = require('node:http');
4
+ const { ModuleBridge } = require('../lib/module-bridge');
5
+
6
+ async function main() {
7
+ let releaseRegister, registerStarted, registered = true;
8
+ const started = new Promise(resolve => { registerStarted = resolve; });
9
+ const gate = new Promise(resolve => { releaseRegister = resolve; });
10
+ const engineProjects = new Set();
11
+ const calls = [];
12
+ const server = http.createServer(async (req, res) => {
13
+ req.resume();
14
+ if (req.method === 'POST' && req.url === '/engine/api/projects') {
15
+ registerStarted(); await gate;
16
+ engineProjects.add('project-race'); calls.push('register');
17
+ }
18
+ if (req.method === 'DELETE' && req.url === '/engine/api/projects/project-race') { engineProjects.delete('project-race'); calls.push('remove'); }
19
+ res.setHeader('Content-Type', 'application/json');
20
+ res.end(JSON.stringify({ ok: true, projects: [], id: 'terminal-race', project: { id: 'event-race' }, sequence: 0 }));
21
+ });
22
+ await new Promise(resolve => server.listen(0, '127.0.0.1', resolve));
23
+ const base = `http://127.0.0.1:${server.address().port}`;
24
+ const state = { modules: {} };
25
+ const bridge = new ModuleBridge({ terminalUrl: base + '/terminal', vectorHubUrl: base + '/vector', eventBridgeUrl: base + '/event', engineUrl: base + '/engine', registryStore: { readDisplaySnapshot: () => registered ? { displayName: 'Race' } : null }, projectStore: { readState: () => state, readConfig: () => ({}), updateState: async (_id, apply) => { apply(state); } } });
26
+ try {
27
+ const input = { projectId: 'project-race', name: 'Race', workspacePath: process.cwd(), knowledgePath: process.cwd() };
28
+ const wiring = bridge.registerProjectLinks(input);
29
+ await started;
30
+ const removing = bridge.removeProjectLinks({ projectId: 'project-race', onRemoved: async () => { registered = false; } }).then(() => { registered = false; });
31
+ // A delayed supervisor registration must settle before removal, never after.
32
+ await new Promise(resolve => setTimeout(resolve, 100)); releaseRegister();
33
+ await Promise.all([wiring, removing]);
34
+ assert.deepEqual([...engineProjects], [], 'delayed wiring must not resurrect a removed engine project');
35
+ assert.deepEqual(calls, ['register', 'remove']);
36
+ const before = calls.length;
37
+ const stale = await bridge.registerProjectLinks(input);
38
+ assert.equal(calls.length, before, 'a stale registry snapshot cannot register an absent project');
39
+ assert.equal(stale.engine.status, 'removed');
40
+ assert.equal(bridge.projectWiringQueue?.size || 0, 0, 'settled operations must not leak per-project queues');
41
+ let releaseOther;
42
+ const otherGate = new Promise(resolve => { releaseOther = resolve; });
43
+ const order = [];
44
+ const held = bridge._withProjectWiring('project-a', async () => { await otherGate; order.push('a'); });
45
+ const independent = bridge._withProjectWiring('project-b', async () => { order.push('b'); });
46
+ await independent; assert.deepEqual(order, ['b'], 'unrelated projects do not share a global wiring lock');
47
+ releaseOther(); await held; assert.deepEqual(order, ['b', 'a']);
48
+ // Failed deletion keeps its registry, then a retry can still repair/remove it.
49
+ registered = true;
50
+ await assert.rejects(bridge.removeProjectLinks({ projectId: 'project-race', onRemoved: async () => { throw new Error('controlled hook conflict'); } }), /controlled hook conflict/);
51
+ assert.equal(registered, true);
52
+ await bridge.registerProjectLinks(input);
53
+ assert.ok(engineProjects.has('project-race'));
54
+ await bridge.removeProjectLinks({ projectId: 'project-race', onRemoved: async () => { registered = false; } });
55
+ assert.deepEqual([...engineProjects], []);
56
+ console.log('module-wiring-unbind-race-test PASS');
57
+ } finally { releaseRegister(); server.closeAllConnections(); await new Promise(resolve => server.close(resolve)); }
58
+ }
59
+ main().catch(error => { console.error(error); process.exitCode = 1; });
@@ -24,7 +24,10 @@ async function connectEngine(runtime) {
24
24
  step = 'project-wiring';
25
25
  for (const id of runtime.registryStore.listIds()) {
26
26
  projectId = id;
27
- const config = runtime.projectStore.readConfig(id);
27
+ if (!runtime.registryStore.readDisplaySnapshot(id)) continue;
28
+ let config;
29
+ try { config = runtime.projectStore.readConfig(id); }
30
+ catch (error) { if (!runtime.registryStore.readDisplaySnapshot(id)) continue; throw error; }
28
31
  const modules = await bridge.registerProjectLinks({ projectId: id, name: config.displayName, workspacePath: config.repoPath, knowledgePath: config.knowledgePath });
29
32
  if (!modules.engine.ok || !modules.eventBridge.ok) throw new Error('PROJECT_WIRING_PENDING');
30
33
  }
@@ -93,6 +93,7 @@ class ModuleBridge {
93
93
  this.runtimeToken = options.runtimeToken || crypto.randomBytes(32).toString('hex');
94
94
  this.registryStore = options.registryStore || null;
95
95
  this.projectStore = options.projectStore || null;
96
+ this.projectWiringQueue = new Map();
96
97
  this.settingsStore = options.settingsStore || null;
97
98
  this.dataDir = options.dataDir || null;
98
99
  this.supervised = [];
@@ -398,7 +399,24 @@ class ModuleBridge {
398
399
  * (retry on the next register call or health-check pass), never a hard error.
399
400
  * KB_MODULES_REGISTER=0 disables fan-out entirely (test isolation).
400
401
  */
401
- async registerProjectLinks({ projectId, name, workspacePath, knowledgePath }) {
402
+ _withProjectWiring(projectId, action) {
403
+ const prior = this.projectWiringQueue.get(projectId) || Promise.resolve();
404
+ const operation = prior.catch(() => {}).then(action);
405
+ const settled = operation.finally(() => { if (this.projectWiringQueue.get(projectId) === settled) this.projectWiringQueue.delete(projectId); });
406
+ this.projectWiringQueue.set(projectId, settled);
407
+ return settled;
408
+ }
409
+
410
+ registerProjectLinks(input) {
411
+ return this._withProjectWiring(input.projectId, () => {
412
+ // A supervisor may have captured this project before a concurrent unbind.
413
+ // Recheck the spine after acquiring the same lease as the removal path.
414
+ if (this.registryStore && !this.registryStore.readDisplaySnapshot(input.projectId)) return Object.fromEntries(['terminal', 'vectorHub', 'eventBridge', 'engine'].map(key => [key, { ok: true, status: 'removed', skipped: 'project-not-registered' }]));
415
+ return this._registerProjectLinks(input);
416
+ });
417
+ }
418
+
419
+ async _registerProjectLinks({ projectId, name, workspacePath, knowledgePath }) {
402
420
  const result = { terminal: { ok: false }, vectorHub: { ok: false }, eventBridge: { ok: false }, engine: { ok: false } };
403
421
  if (String(process.env.KB_MODULES_REGISTER || '') === '0') {
404
422
  result.terminal = { ok: false, status: 'disabled' };
@@ -490,7 +508,17 @@ class ModuleBridge {
490
508
  * only drops its derived index (source untouched); terminal removes the project
491
509
  * with keepSessions=1 so session records survive; the event-bridge console only
492
510
  * unregisters the project — journals and sealed commit documents stay on disk. */
493
- async removeProjectLinks({ projectId, requireEngine = true }) {
511
+ removeProjectLinks(input) {
512
+ return this._withProjectWiring(input.projectId, async () => {
513
+ const result = await this._removeProjectLinks(input);
514
+ // The caller owns registry/lifecycle changes. Hold the wiring lease until
515
+ // its transaction settles, so delayed reconfiguration cannot resurrect it.
516
+ await input.onRemoved?.(result);
517
+ return result;
518
+ });
519
+ }
520
+
521
+ async _removeProjectLinks({ projectId, requireEngine = true }) {
494
522
  const wiring = (this.projectStore ? this.projectStore.readState(projectId).modules : null) || {};
495
523
  const result = { terminal: { ok: false }, vectorHub: { ok: false }, eventBridge: { ok: false }, engine: { ok: false } };
496
524
  // Stop the writer first, before removing hooks/index registrations.
@@ -1057,11 +1057,13 @@ function createRequestHandler(runtime, options = {}) {
1057
1057
  throw new DomainError('PROJECT_NOT_FOUND', 'Project was not found.', { status: 404 });
1058
1058
  }
1059
1059
  const body = await readJsonBody(req);
1060
- if (runtime.registryStore.readDisplaySnapshot(projectId)) await runtime.moduleBridge.removeProjectLinks({ projectId, requireEngine: runtime.analysisOwner !== 'legacy' });
1061
- const result = await runtime.lifecycleService.deleteProject(projectId, {
1060
+ const remove = () => runtime.lifecycleService.deleteProject(projectId, {
1062
1061
  deleteKnowledge: body.deleteKnowledge === true,
1063
1062
  confirmationToken: body.confirmationToken || '',
1064
1063
  });
1064
+ let result;
1065
+ if (runtime.registryStore.readDisplaySnapshot(projectId)) await runtime.moduleBridge.removeProjectLinks({ projectId, requireEngine: runtime.analysisOwner !== 'legacy', onRemoved: async () => { result = await remove(); } });
1066
+ else result = await remove();
1065
1067
  return send(res, 200, result);
1066
1068
  }
1067
1069
  const gitStatusMatch = pathname.match(/^\/api\/projects\/([^/]+)\/git-status$/);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sanqianx/project-knowledge",
3
- "version": "5.0.9-rc.13",
3
+ "version": "5.0.9-rc.15",
4
4
  "description": "Knowledge base manager with Git integration, AI-driven analysis, and bilingual (zh-CN/en-US) knowledge output",
5
5
  "main": "_site/server.js",
6
6
  "scripts": {
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "$schema": "https://json.schemastore.org/claude-code-plugin-manifest.json",
3
3
  "name": "project-knowledge",
4
- "version": "5.0.9-rc.13",
4
+ "version": "5.0.9-rc.15",
5
5
  "description": "Read durable project decisions and implementation history from Project Knowledge.",
6
6
  "author": {
7
7
  "name": "SanQianX",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "project-knowledge",
3
- "version": "5.0.9-rc.13",
3
+ "version": "5.0.9-rc.15",
4
4
  "description": "Read durable project decisions and implementation history from Project Knowledge.",
5
5
  "author": {
6
6
  "name": "SanQianX",
@@ -6,7 +6,7 @@
6
6
  "args": [
7
7
  "-y",
8
8
  "--package",
9
- "@sanqianx/project-knowledge@5.0.9-rc.13",
9
+ "@sanqianx/project-knowledge@5.0.9-rc.15",
10
10
  "project-knowledge-mcp"
11
11
  ]
12
12
  }
@@ -3,19 +3,19 @@
3
3
  "modules": {
4
4
  "knowledge-engine": {
5
5
  "name": "@sanqianx/ai-coding-knowledge-engine",
6
- "version": "0.2.3-rc.6",
6
+ "version": "0.2.3-rc.7",
7
7
  "root": "_modules/knowledge-engine",
8
8
  "files": {
9
- "README.md": "efcef635df01736a8672219642a76175f3059d23b466f529140d791ec75000d5",
10
- "package.json": "09e32aacd4ec5c3b596e60f3210f62515c625844e31866261c5596937195db30",
9
+ "README.md": "351d971a522253c5dceb424b9f1a9c2d9427cd01c2b76d0c37fbc17b76f71ba3",
10
+ "package.json": "2da4b42d29e6742ea4ac8f285ffc49da66e0ed44d1d0d136cfab1e1b485093b4",
11
11
  "src/analysis-contract.js": "fee063967d32eefc955cf0729f7dc8cb31bd690b85e22471c9059494a599c77a",
12
12
  "src/analysis-output.js": "95a3c19dcc9ab85d1dba7fee5b9aec80f9c000dfba89da3fd0be25a81e2fb680",
13
13
  "src/analysis-worker.js": "a105cabe6e1573b2d986b1e7a5aaf615e2fb53524156fc8e4e2c0a71f2c97e55",
14
14
  "src/bin.js": "22208907431d0f3a971a669c7e5eb44b3b7af9bbb36fd7ba17c3b90c4288bd70",
15
15
  "src/bridge-client.js": "ae0dba21e4286694bcd9e064ca36564ec997969ae1eb1ce4f8a6665e447cbffe",
16
- "src/coordinator.js": "1df096060faa83245c49b65a861f32cdaeeb7a190b94abf2ad6477151d7ca2ff",
16
+ "src/coordinator.js": "1d515d891a781fbad3ce7df39fa083dda2663a8bcd5610ff52dc8c307e2c5fbd",
17
17
  "src/diagnostics.js": "f7b516ace61a1e9faaa6876930b9e2e258499a25857d1203f4e53d28bfd0fa1a",
18
- "src/headless-analyzer.js": "2f345adee332c003ecb9a1e01eac4fa69d8802b4e6dd9effb03ac3cb19aa3e48",
18
+ "src/headless-analyzer.js": "d181d50cd7756d217d7d66c4a4c4e528453c8527dc6fe413a19788a47ccbf323",
19
19
  "src/index-request.js": "78f26b872299005499fff4f2d1f9e8f986784f2ff36a5cf2be2b1e7b7c7d9378",
20
20
  "src/knowledge-targets.js": "aa44ec285d16d7b1c0b63bf084058a1d6f867fa9929c753b6b0cbb24f7b20ee2",
21
21
  "src/memories.js": "0a8667e6daba99fa5022393951a28e96cdadf98af85b6af9a6be96c7f4e896fc",
@@ -33,7 +33,7 @@
33
33
  "src/runtime/conversation-store.js": "644318f9c04376a3317ced240dd94afa99a79631f0ecc394c5aa298ae2bc0474",
34
34
  "src/runtime/evidence-bundle.js": "33d4614849d5e1d69aba8b75c3a43c18b7b6f94e5c78654c82ef631331cc94cc",
35
35
  "src/runtime/git-runner.js": "b74fe197bd4dcf3e70635ef7b1f2615edf965c1d9a3ffe5406aabd41b76d74a9",
36
- "src/runtime/knowledge-promotion.js": "c24aba505caf11cdc8faba763beffc65956c1330ef28ed5fbd6ae16eac640fa6",
36
+ "src/runtime/knowledge-promotion.js": "892a227921ca399e80ea7b3991bfd13de0ffc221c7a0562393f1e5d33c2d00fa",
37
37
  "src/runtime/knowledge-retrieval-service.js": "75444f495a9ad0720af0e57b8d27d7ac1325c78a768f4f68ebd86ebc2524945e",
38
38
  "src/runtime/layout.js": "c48fa1db62e5df1edefe13218e470c5ceea4ef2781293512c2c18e5c2ba0c7e9",
39
39
  "src/runtime/markdown-knowledge-indexer.js": "4d6b5b955849d274ca0614ef030c94526da7c39ab0874f02c168d0681f5b1406",