@sanqianx/project-knowledge 5.0.5 → 5.0.6

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.5",
12
+ "version": "5.0.6",
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,16 @@
1
1
  # Changelog
2
2
 
3
+ ## [5.0.6] - 2026-10-10
4
+
5
+ - Do not mistake unregistered, abandoned legacy chat records for a knowledge
6
+ database on first launch. Preserve their bytes without restoring sessions or
7
+ importing projects; continue rejecting actual old registries and maintenance.
8
+ - Wait for the background server's real HTTP readiness before reporting success
9
+ or opening the browser. Surface startup failures in the invoking terminal with
10
+ a nonzero exit code, and name the exact conflicting data entries.
11
+ - Isolate regression data from the user's default data directory. Add a detached
12
+ npm CLI startup regression from an unrelated working directory.
13
+
3
14
  ## [5.0.5] - 2026-10-10
4
15
 
5
16
  - Invalidate in-flight registry snapshots after domain creation, project import,
@@ -9,6 +9,10 @@
9
9
 
10
10
  const fs = require('fs');
11
11
  const path = require('path');
12
+ const os = require('os');
13
+ const TEST_DATA = fs.mkdtempSync(path.join(os.tmpdir(), 'kb-workbench-unit-'));
14
+ process.env.KB_DATA_DIR = TEST_DATA;
15
+ process.on('exit', () => fs.rmSync(TEST_DATA, { recursive: true, force: true }));
12
16
  const runner = require('../lib/claude-cli-runner');
13
17
 
14
18
  const ROOT = path.resolve(__dirname, '..', '..');
@@ -16,6 +16,20 @@ const { connectEngine } = require('../lib/engine-connection');
16
16
  assert.equal(fs.readFileSync(path.join(old, 'projects.json'), 'utf8'), bytes);
17
17
  const fresh = path.join(root, 'fresh'); initializeProductData(fresh); initializeProductData(fresh);
18
18
  assert.equal(JSON.parse(fs.readFileSync(path.join(fresh, 'product-data.json'))).schema, DATA_SCHEMA);
19
+ const auxiliary = path.join(root, 'auxiliary');
20
+ const session = path.join(auxiliary, 'runtime', 'claude-sessions', 'unregistered-test', 'session.json');
21
+ fs.mkdirSync(path.dirname(session), { recursive: true });
22
+ fs.writeFileSync(session, 'untouched abandoned chat');
23
+ fs.writeFileSync(path.join(auxiliary, 'launcher.log'), 'old startup failure');
24
+ initializeProductData(auxiliary);
25
+ assert.equal(fs.readFileSync(session, 'utf8'), 'untouched abandoned chat');
26
+ for (const entry of ['claims', 'runs', 'promotions', 'transactions', 'unknown-business-state']) {
27
+ const pending = path.join(root, entry);
28
+ fs.mkdirSync(path.join(pending, 'runtime', entry), { recursive: true });
29
+ fs.writeFileSync(path.join(pending, 'runtime', entry, 'record.json'), '{}');
30
+ assert.throws(() => initializeProductData(pending), error => error.code === 'SCHEMA_UNSUPPORTED' && error.message.includes(`runtime/${entry}`));
31
+ assert.equal(fs.existsSync(path.join(pending, 'product-data.json')), false);
32
+ }
19
33
  const paths = [];
20
34
  let activationStatus = 403;
21
35
  const logs = [];
@@ -0,0 +1,80 @@
1
+ 'use strict';
2
+ const assert = require('node:assert/strict');
3
+ const fs = require('node:fs');
4
+ const path = require('node:path');
5
+ const os = require('node:os');
6
+ const net = require('node:net');
7
+ const { spawn } = require('node:child_process');
8
+ const endpoint = require('../lib/runtime-endpoint');
9
+
10
+ const packageRoot = process.env.KB_LAUNCHER_TEST_PACKAGE || path.resolve(__dirname, '../..');
11
+ const bin = path.join(packageRoot, 'bin/project-knowledge.js');
12
+ const root = fs.mkdtempSync(path.join(os.tmpdir(), 'kb-launcher-readiness-'));
13
+ const data = path.join(root, '.project-knowledge-v5');
14
+ const record = path.join(data, 'runtime/claude-sessions/project-a/session.json');
15
+ const recordBytes = JSON.stringify({ schema: 'claude-workbench-session/v1', sessionId: 'old-test', projectSlug: 'project-a', state: 'running', metadata: { automation: true } });
16
+ fs.mkdirSync(path.dirname(record), { recursive: true });
17
+ fs.writeFileSync(record, recordBytes);
18
+ const env = { ...process.env, KB_DATA_DIR: data, KB_MODULES_AUTOSTART: '0', AI_CODING_EVENT_BRIDGE_HOME: path.join(root, 'bridge') };
19
+ let serverPid;
20
+
21
+ function run(args, overrides = {}) {
22
+ return new Promise((resolve, reject) => {
23
+ const child = spawn(process.execPath, [bin, ...args], { cwd: root, env: { ...env, ...overrides }, stdio: ['ignore', 'pipe', 'pipe'], windowsHide: true });
24
+ let stdout = '', stderr = '';
25
+ const timer = setTimeout(() => { child.kill(); reject(new Error(`CLI timed out: ${stdout} ${stderr}`)); }, 25000);
26
+ child.stdout.on('data', chunk => { stdout += chunk; });
27
+ child.stderr.on('data', chunk => { stderr += chunk; });
28
+ child.once('error', reject);
29
+ child.once('exit', code => { clearTimeout(timer); resolve({ code, stdout, stderr }); });
30
+ });
31
+ }
32
+ async function freePort() {
33
+ const server = net.createServer();
34
+ await new Promise(resolve => server.listen(0, '127.0.0.1', resolve));
35
+ const port = server.address().port;
36
+ await new Promise(resolve => server.close(resolve));
37
+ return port;
38
+ }
39
+
40
+ (async () => {
41
+ const port = await freePort();
42
+ const launched = await run(['--no-open'], { KB_SITE_PORT: String(port) });
43
+ serverPid = endpoint.readEndpoint(data)?.pid;
44
+ assert.equal(launched.code, 0, launched.stderr);
45
+ assert.match(launched.stdout, new RegExp(`is ready at http://127.0.0.1:${port}`));
46
+ assert(serverPid && endpoint.isProcessAlive(serverPid));
47
+ const response = await fetch(`http://127.0.0.1:${port}/api/state`);
48
+ assert.equal(response.status, 200, 'ready must mean HTTP is usable, not only a PID exists');
49
+ const state = await response.json();
50
+ assert.deepEqual(state.projects, [], 'abandoned sessions must not import projects or enqueue analysis');
51
+ assert.equal(fs.readFileSync(record, 'utf8'), recordBytes);
52
+ const again = await run(['--no-open']);
53
+ assert.equal(again.code, 0);
54
+ assert.match(again.stdout, /Already running/);
55
+ const stopped = await run(['stop']);
56
+ assert.equal(stopped.code, 0);
57
+ for (let attempt = 0; attempt < 50 && endpoint.isProcessAlive(serverPid); attempt++) await new Promise(resolve => setTimeout(resolve, 100));
58
+ assert.equal(endpoint.isProcessAlive(serverPid), false);
59
+ serverPid = null;
60
+
61
+ const oldData = path.join(root, 'actual-old-data');
62
+ fs.mkdirSync(oldData);
63
+ const original = JSON.stringify({ projectOrder: ['old-project'] });
64
+ fs.writeFileSync(path.join(oldData, 'projects.json'), original);
65
+ const failed = await run(['--no-open', '--port', String(port)], { KB_DATA_DIR: oldData });
66
+ assert.equal(failed.code, 1, 'background failures must return nonzero to the invoking terminal');
67
+ assert.match(failed.stderr, /启动失败/);
68
+ assert.match(failed.stderr, /projects\.json/);
69
+ assert(!/is ready|Listening at/.test(failed.stdout));
70
+ assert.equal(fs.readFileSync(path.join(oldData, 'projects.json'), 'utf8'), original);
71
+ assert.equal(fs.existsSync(path.join(oldData, 'product-data.json')), false);
72
+ const log = fs.readFileSync(path.join(oldData, 'launcher.log'), 'utf8');
73
+ assert.match(log, /startup failed/);
74
+ assert(!/Listening at/.test(log), 'never advertise a listener before initialization succeeds');
75
+ console.log('launcher readiness PASS: unrelated cwd, abandoned records, HTTP readiness, stop, terminal failure, legacy preservation');
76
+ })().catch(error => { console.error(error); process.exitCode = 1; }).finally(() => {
77
+ if (serverPid && endpoint.readEndpoint(data)?.pid === serverPid) {
78
+ try { process.kill(serverPid); } catch {}
79
+ }
80
+ });
@@ -54,13 +54,15 @@ function runOne(file) {
54
54
  // Every test child gets its own Bridge journal sandbox so hook-trigger /
55
55
  // server runtimes never touch the developer's ~/.ai-coding-event-bridge.
56
56
  const bridgeHome = fs.mkdtempSync(path.join(os.tmpdir(), `kb-runall-bridge-${process.pid}-`));
57
+ const dataHome = fs.mkdtempSync(path.join(os.tmpdir(), `kb-runall-data-${process.pid}-`));
57
58
  const result = spawnSync(process.execPath, [path.join(TEST_DIR, file)], {
58
59
  cwd: ROOT,
59
60
  encoding: 'utf-8',
60
61
  timeout: PER_TEST_TIMEOUT_MS,
61
- env: { ...process.env, FORCE_COLOR: '0', AI_CODING_EVENT_BRIDGE_HOME: bridgeHome, KB_MODULES_AUTOSTART: '0' },
62
+ env: { ...process.env, FORCE_COLOR: '0', KB_DATA_DIR: dataHome, AI_CODING_EVENT_BRIDGE_HOME: bridgeHome, KB_MODULES_AUTOSTART: '0' },
62
63
  });
63
64
  try { fs.rmSync(bridgeHome, { recursive: true, force: true, maxRetries: 3, retryDelay: 100 }); } catch {}
65
+ try { fs.rmSync(dataHome, { recursive: true, force: true, maxRetries: 3, retryDelay: 100 }); } catch {}
64
66
  const failure = result.status === 0 ? null : classifyFailure(result);
65
67
  return {
66
68
  file,
@@ -5,6 +5,10 @@ const { DomainError } = require('./contracts');
5
5
 
6
6
  const DATA_SCHEMA = 'project-knowledge/fresh-product-data/v1';
7
7
 
8
+ function hasData(file) {
9
+ return fs.existsSync(file) && (!fs.statSync(file).isDirectory() || fs.readdirSync(file).length > 0);
10
+ }
11
+
8
12
  function initializeProductData(dataPath) {
9
13
  const marker = path.join(dataPath, 'product-data.json');
10
14
  if (fs.existsSync(marker)) {
@@ -13,11 +17,18 @@ function initializeProductData(dataPath) {
13
17
  return;
14
18
  }
15
19
  // Old registries and unfinished claims must never become fresh product work.
16
- const oldData = ['settings.json', 'projects.json', 'projects', 'runtime', 'knowledge-engine', 'workbench', 'vectorhub', 'events', 'knowledge-store.json', 'ai-profiles.json', 'embedding-config.json'].some(name => {
17
- const file = path.join(dataPath, name);
18
- return fs.existsSync(file) && (!fs.statSync(file).isDirectory() || fs.readdirSync(file).length > 0);
19
- });
20
- if (oldData) throw new DomainError('SCHEMA_UNSUPPORTED', '不再迁移旧版知识库数据。请取消旧 KB_DATA_DIR 设置,或指定新的空数据目录,然后重新导入项目。', { status: 409 });
20
+ const oldData = ['settings.json', 'projects.json', 'projects', 'knowledge-engine', 'workbench', 'vectorhub', 'events', 'knowledge-store.json', 'ai-profiles.json', 'embedding-config.json']
21
+ .filter(name => hasData(path.join(dataPath, name)));
22
+ const runtimePath = path.join(dataPath, 'runtime');
23
+ if (fs.existsSync(runtimePath)) {
24
+ if (!fs.statSync(runtimePath).isDirectory()) oldData.push('runtime');
25
+ else for (const name of fs.readdirSync(runtimePath)) {
26
+ // Unregistered legacy chat records cannot be resumed by a fresh registry.
27
+ // They are neither knowledge nor pending maintenance, and stay untouched.
28
+ if (name !== 'claude-sessions' && hasData(path.join(runtimePath, name))) oldData.push(`runtime/${name}`);
29
+ }
30
+ }
31
+ if (oldData.length) throw new DomainError('SCHEMA_UNSUPPORTED', `数据目录 ${dataPath} 包含旧版业务数据(${oldData.join('、')})。新版不会迁移或删除它们;请将 KB_DATA_DIR 指向新的空目录,然后重新导入项目。`, { status: 409, details: { dataPath, entries: oldData } });
21
32
  AtomicFile.writeJsonAtomic(marker, { schema: DATA_SCHEMA, createdAt: new Date().toISOString() });
22
33
  }
23
34
 
package/_site/server.js CHANGED
@@ -3,10 +3,13 @@ const { startServer, installProcessHandlers } = require('./lib/server-app');
3
3
 
4
4
  const instancePromise = startServer();
5
5
  installProcessHandlers(instancePromise);
6
+ module.exports = instancePromise;
6
7
 
7
8
  instancePromise.then(instance => {
8
9
  process.stdout.write(`Project Knowledge listening on http://${instance.host}:${instance.port}\n`);
9
10
  }).catch(error => {
10
- process.stderr.write(`[project-knowledge] startup failed: ${String(error && error.message || error)}\n`);
11
- process.exitCode = 1;
11
+ if (require.main === module) {
12
+ process.stderr.write(`[project-knowledge] startup failed: ${String(error && error.message || error)}\n`);
13
+ process.exitCode = 1;
14
+ }
12
15
  });
@@ -6,7 +6,7 @@
6
6
 
7
7
  const path = require('path');
8
8
  const { spawn, spawnSync, exec, execSync } = require('child_process');
9
- const { existsSync, readFileSync, writeFileSync, unlinkSync, mkdirSync, openSync } = require('fs');
9
+ const { existsSync, readFileSync, writeFileSync, unlinkSync, mkdirSync, openSync, closeSync } = require('fs');
10
10
  const net = require('net');
11
11
  const os = require('os');
12
12
  const { getDataDir } = require('../_site/lib/data-dir');
@@ -71,6 +71,33 @@ function openBrowser(url) {
71
71
  try { exec(cmd); } catch { /* best-effort */ }
72
72
  }
73
73
 
74
+ function notifyLauncher(message) {
75
+ if (process.connected && typeof process.send === 'function') process.send(message);
76
+ }
77
+
78
+ function waitForStartup(child, logPath) {
79
+ return new Promise((resolve, reject) => {
80
+ const finish = (error, value) => {
81
+ clearTimeout(timer);
82
+ child.removeListener('message', onMessage);
83
+ child.removeListener('error', onError);
84
+ child.removeListener('exit', onExit);
85
+ if (child.connected) child.disconnect();
86
+ if (error) reject(error); else resolve(value);
87
+ };
88
+ const onMessage = message => {
89
+ if (message && message.type === 'kb:launcher-ready') finish(null, message);
90
+ else if (message && message.type === 'kb:launcher-failed') finish(new Error(message.message));
91
+ };
92
+ const onError = error => finish(error);
93
+ const onExit = code => finish(new Error(`后台进程在启动完成前退出(${code})。请查看 ${logPath}`));
94
+ const timer = setTimeout(() => finish(new Error(`启动尚未完成,请运行 project-knowledge status 并查看 ${logPath}`)), 45000);
95
+ child.on('message', onMessage);
96
+ child.once('error', onError);
97
+ child.once('exit', onExit);
98
+ });
99
+ }
100
+
74
101
  function isPortFree(port, host = '127.0.0.1') {
75
102
  return new Promise((resolve) => {
76
103
  const tester = net.createServer();
@@ -259,7 +286,7 @@ Options:
259
286
  -v, --version Print version and exit
260
287
  -h, --help Show this help message
261
288
 
262
- Runtime data lives next to the npm global root; PID file at ${PID_FILE}.
289
+ Runtime data: ${DATA_DIR}; PID file at ${PID_FILE}.
263
290
  `);
264
291
  process.exit(0);
265
292
  }
@@ -357,28 +384,44 @@ if (!foreground) {
357
384
  mkdirSync(path.dirname(LAUNCH_LOG), { recursive: true });
358
385
  logFd = openSync(LAUNCH_LOG, 'a');
359
386
  } catch { /* best-effort: fall back to discarding output */ }
360
- const stdio = logFd == null ? 'ignore' : ['ignore', logFd, logFd];
387
+ const stdio = ['ignore', logFd == null ? 'ignore' : logFd, logFd == null ? 'ignore' : logFd, 'ipc'];
361
388
 
362
389
  const child = spawn(
363
390
  process.execPath,
364
391
  [...process.argv.slice(1), '--fg', ...forwarded],
365
392
  { detached: true, stdio, windowsHide: true }
366
393
  );
394
+ if (logFd != null) closeSync(logFd);
367
395
  child.unref();
368
396
 
369
397
  console.log('project-knowledge starting in background');
370
398
  console.log(`Diagnostics: ${LAUNCH_LOG}`);
371
- console.log(`Use "project-knowledge status" to check, "project-knowledge stop" to stop.`);
372
- process.exit(0);
399
+ waitForStartup(child, LAUNCH_LOG).then(ready => {
400
+ console.log(`project-knowledge is ready at ${ready.url}`);
401
+ console.log(`Use "project-knowledge status" to check, "project-knowledge stop" to stop.`);
402
+ process.exit(0);
403
+ }).catch(error => {
404
+ console.error(`启动失败:${error.message}`);
405
+ console.error(`Diagnostics: ${LAUNCH_LOG}`);
406
+ process.exit(1);
407
+ });
408
+ } else {
409
+ main().catch((err) => {
410
+ console.error(`[project-knowledge] startup failed: ${err.message}`);
411
+ notifyLauncher({ type: 'kb:launcher-failed', message: err.message });
412
+ removePid(process.pid);
413
+ runtimeEndpoint.clearEndpoint(DATA_DIR, { pid: process.pid });
414
+ process.exitCode = 1;
415
+ });
373
416
  }
374
417
 
375
- // ── Foreground: start server ──
376
- process.env.KB_SITE_HOST = host;
377
-
378
418
  async function main() {
419
+ process.env.KB_SITE_HOST = host;
379
420
  const existingEndpoint = runtimeEndpoint.readLiveEndpoint(DATA_DIR);
380
421
  if (existingEndpoint && existingEndpoint.pid !== process.pid) {
381
- console.log(`Already running (PID ${existingEndpoint.pid}) at http://${existingEndpoint.host}:${existingEndpoint.port}`);
422
+ const url = `http://${existingEndpoint.host}:${existingEndpoint.port}`;
423
+ console.log(`Already running (PID ${existingEndpoint.pid}) at ${url}`);
424
+ notifyLauncher({ type: 'kb:launcher-ready', url, pid: existingEndpoint.pid, port: existingEndpoint.port });
382
425
  process.exit(0);
383
426
  }
384
427
  const actualPort = portExplicit ? port : await findFreePort(port, host);
@@ -393,7 +436,9 @@ async function main() {
393
436
  if (!claim.claimed) {
394
437
  const active = claim.endpoint;
395
438
  if (active) {
396
- console.log(`Already running (PID ${active.pid}) at http://${active.host}:${active.port}`);
439
+ const url = `http://${active.host}:${active.port}`;
440
+ console.log(`Already running (PID ${active.pid}) at ${url}`);
441
+ notifyLauncher({ type: 'kb:launcher-ready', url, pid: active.pid, port: active.port });
397
442
  } else {
398
443
  console.error('Another project-knowledge process is starting. Please try again in a moment.');
399
444
  }
@@ -405,35 +450,22 @@ async function main() {
405
450
  runtimeEndpoint.clearEndpoint(DATA_DIR, { pid: process.pid });
406
451
  };
407
452
  process.on('exit', cleanup);
408
- process.on('SIGINT', () => { cleanup(); process.exit(0); });
409
- process.on('SIGTERM', () => { cleanup(); process.exit(0); });
410
- // Windows console-close sends SIGBREAK; Node doesn't run exit handlers after
411
- // a forceful TerminateProcess, but SIGBREAK does fire before the process is
412
- // torn down, so we can still clean up here.
413
- if (process.platform === 'win32') {
414
- process.on('SIGBREAK', () => { cleanup(); process.exit(0); });
415
- }
416
453
 
417
- const url = `http://localhost:${actualPort}`;
454
+ const url = `http://${host === '::1' ? '[::1]' : host}:${actualPort}`;
418
455
  if (actualPort !== port) {
419
456
  console.log(`(Port ${port} was busy, using ${actualPort} instead)`);
420
457
  }
421
458
  console.log(`project-knowledge ${pkg.version}`);
422
- console.log(`Listening at ${url}`);
423
459
  // Resolve data dir the same way server.js will, so we can show the user
424
460
  // where their config and KB files live BEFORE the server boots.
425
461
  try {
426
462
  console.log(`Data dir: ${getDataDir()}`);
427
463
  } catch {}
428
464
 
429
- require(path.join(__dirname, '..', '_site', 'server.js'));
465
+ const instance = await require(path.join(__dirname, '..', '_site', 'server.js'));
466
+ if (process.platform === 'win32') process.once('SIGBREAK', () => instance.stop('SIGBREAK').then(() => { process.exitCode = 0; }));
467
+ console.log(`Listening at ${url}`);
468
+ notifyLauncher({ type: 'kb:launcher-ready', url, pid: process.pid, port: actualPort });
430
469
 
431
470
  if (shouldOpen) setTimeout(() => openBrowser(url), 500);
432
471
  }
433
-
434
- main().catch((err) => {
435
- console.error(err.message);
436
- removePid(process.pid);
437
- runtimeEndpoint.clearEndpoint(DATA_DIR, { pid: process.pid });
438
- process.exit(1);
439
- });
@@ -13,6 +13,8 @@ project-knowledge
13
13
 
14
14
  需要 Node ≥22 和 Git。默认使用 `~/.project-knowledge-v5`,四模块的托管数据也在此目录内。若环境变量 `KB_DATA_DIR` 仍指向旧目录,请取消该设置或改为新的空目录。不要把旧 `projects.json`、设置或引擎状态复制进新目录。
15
15
 
16
+ 5.0.6 修复首次启动被未登记的旧工作台会话误拦截:仅有日志、默认提示词及孤立会话的目录可以创建空的新登记,孤立会话保留原字节但不会恢复或触发任务。实际旧项目、设置、引擎状态或维护任务仍拒绝迁移,错误列出具体目录和冲突项。后台启动命令等待 HTTP 就绪后才报成功;启动失败直接在终端显示原因并返回非零退出码,不提前打开浏览器。
17
+
16
18
  在“系统状态”重新配置模型与后台默认模型、embedding 和知识根目录,然后在“领域与项目”导入代码目录及新的空知识目录。原代码目录无需移动。若残留旧知识库托管 Hook,导入预检会要求明确确认替换;不会覆盖第三方 Hook。导入建立 HEAD 基线,不分析已有历史。
17
19
 
18
20
  如果模块是自己独立启动的旧进程,请在它原来的终端中停止后重启新版外壳。外壳不会擅自杀死其他服务;版本或运行期鉴权不一致时显示具体连接阶段,不接管旧服务数据。
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sanqianx/project-knowledge",
3
- "version": "5.0.5",
3
+ "version": "5.0.6",
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.5",
4
+ "version": "5.0.6",
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.5",
3
+ "version": "5.0.6",
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.5",
9
+ "@sanqianx/project-knowledge@5.0.6",
10
10
  "project-knowledge-mcp"
11
11
  ]
12
12
  }