@sanqianx/project-knowledge 5.0.21 → 5.0.22

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.21",
12
+ "version": "5.0.22",
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,11 @@
1
1
  # Changelog
2
2
 
3
+ ## [5.0.22] - 2026-10-20
4
+
5
+ - 将启动时的 Agent 接入检查和自动修复放入独立进程,避免慢客户端命令阻塞后台就绪通知、网页和实例控制;超时会记录可重试状态,停止服务同时清理自有检查进程。
6
+ - 仅检查用户已连接的客户端;未能核实的状态不再当成未安装并触发自动修复。保留并排工作区、已有接入选择及知识数据。
7
+ - 补充真实后台 CLI 在原生命令阻塞时的就绪、HTTP、状态查询和停止回归,以及超时、崩溃、手动修复结果优先级验证。
8
+
3
9
  ## [5.0.21] - 2026-10-20
4
10
 
5
11
  - 开始工作采用并排工作区:从加号新建子代理或按需添加知识引用,使用统一的对话、工具步骤、文件修改和输入框;移除面向客户的任务标识、派发阶段及诊断信息。
@@ -0,0 +1,12 @@
1
+ 'use strict';
2
+ const fs = require('node:fs');
3
+ const path = require('node:path');
4
+ const root = process.env.KB_LAUNCHER_TEST_PACKAGE || path.resolve(__dirname, '../../..');
5
+ const { IntegrationManager } = require(path.join(root, '_site/lib/integration-installer'));
6
+ IntegrationManager.prototype.statusAll = async function () {
7
+ fs.writeFileSync(process.env.KB_STARTUP_INTEGRATION_MARKER, String(process.pid));
8
+ // Models a synchronous native CLI probe, without running or repairing any
9
+ // real client integration on the developer or CI machine.
10
+ Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, 8000);
11
+ return { clients: [{ client: 'codex', developmentCapture: { installed: true } }] };
12
+ };
@@ -0,0 +1,13 @@
1
+ 'use strict';
2
+ process.once('message', input => {
3
+ const mode = input.managerOptions.homeDir;
4
+ if (mode === 'crash') return process.exit(1);
5
+ if (mode === 'hang') return setInterval(() => {}, 1000);
6
+ setTimeout(() => {
7
+ process.send({ ok: true, results: {
8
+ claude: { state: 'failed', error: 'native repair failed' },
9
+ codex: { state: 'repaired', error: null },
10
+ zcode: { state: 'failed', error: 'unrequested result' },
11
+ } }, () => process.disconnect());
12
+ }, 80);
13
+ });
@@ -88,5 +88,26 @@ const makeManager = ({ statusClients, onInstall }) => {
88
88
  snapshot.claude = { state: 'tampered' };
89
89
  assert.equal(repair.status().claude.state, 'repaired', 'status() returns a copy');
90
90
  }
91
+ {
92
+ const dir = tmpDataDir();
93
+ markIntent(dir, 'codex', 'disconnected');
94
+ const manager = makeManager({ statusClients: [], onInstall: () => { throw new Error('must not repair'); } });
95
+ const repair = new IntegrationAutoRepair({ dataDir: dir, createManager: () => manager });
96
+ await repair.runOnce();
97
+ assert.equal(manager.calls.statusAll, 0, 'disconnected clients must not run native startup probes');
98
+ markIntent(dir, 'codex', 'connected');
99
+ manager.statusAll = async options => {
100
+ assert.deepEqual(options.clients, ['codex']);
101
+ return { clients: [{ client: 'codex', developmentCapture: { state: 'check-failed' } }] };
102
+ };
103
+ await repair.runOnce();
104
+ assert.equal(manager.calls.installAll.length, 0, 'an unknown status must not be treated as an absent integration');
105
+ manager.statusAll = async () => {
106
+ markIntent(dir, 'codex', 'disconnected');
107
+ return { clients: [{ client: 'codex', developmentCapture: { installed: false } }] };
108
+ };
109
+ await repair.runOnce();
110
+ assert.equal(manager.calls.installAll.length, 0, 'a user disconnect during status scanning must cancel the repair');
111
+ }
91
112
  console.log('integration auto repair test PASS');
92
113
  })().catch(error => { console.error(error); process.exitCode = 1; });
@@ -0,0 +1,59 @@
1
+ 'use strict';
2
+ const assert = require('node:assert/strict');
3
+ const fs = require('node:fs');
4
+ const os = require('node:os');
5
+ const path = require('node:path');
6
+ const net = require('node:net');
7
+ const { spawn } = require('node:child_process');
8
+ const control = require('../lib/runtime-control');
9
+ const { markIntent } = require('../lib/integration-intent');
10
+ const root = process.env.KB_LAUNCHER_TEST_PACKAGE || path.resolve(__dirname, '../..');
11
+ const data = fs.mkdtempSync(path.join(os.tmpdir(), 'kb-startup-integration-'));
12
+ const marker = path.join(data, 'slow-native-check.pid');
13
+ const preload = path.join(__dirname, 'fixtures/slow-startup-integration.cjs').replace(/\\/g, '/');
14
+ let instance;
15
+ async function freePort() {
16
+ const server = net.createServer();
17
+ await new Promise(resolve => server.listen(0, '127.0.0.1', resolve));
18
+ const port = server.address().port;
19
+ await new Promise(resolve => server.close(resolve)); return port;
20
+ }
21
+ async function main() {
22
+ markIntent(data, 'codex', 'connected');
23
+ const port = await freePort();
24
+ const started = Date.now();
25
+ const launched = await new Promise((resolve, reject) => {
26
+ const child = spawn(process.execPath, [path.join(root, 'bin/project-knowledge.js'), '--no-open', '--port', String(port)], {
27
+ windowsHide: true, stdio: ['ignore', 'pipe', 'pipe'],
28
+ env: { ...process.env, KB_DATA_DIR: data, KB_MODULES_AUTOSTART: '0',
29
+ KB_LAUNCHER_TEST_PACKAGE: root, KB_STARTUP_INTEGRATION_MARKER: marker,
30
+ AI_CODING_EVENT_BRIDGE_HOME: path.join(data, 'events'), NODE_OPTIONS: `--require="${preload}"` },
31
+ });
32
+ let stdout = '', stderr = '';
33
+ const timer = setTimeout(() => { child.kill(); reject(new Error('Background launch timed out')); }, 20000);
34
+ child.stdout.on('data', value => { stdout += value; }); child.stderr.on('data', value => { stderr += value; });
35
+ child.once('error', reject); child.once('exit', code => { clearTimeout(timer); resolve({ code, stdout, stderr }); });
36
+ });
37
+ assert.equal(launched.code, 0, launched.stderr);
38
+ instance = (await control.request(data, { action: 'status' }, 1000)).instance;
39
+ assert(Date.now() - started < 5000, 'native integration checks must not delay the launcher ready acknowledgement');
40
+ for (let i = 0; i < 60 && !fs.existsSync(marker); i++) await new Promise(resolve => setTimeout(resolve, 50));
41
+ assert(fs.existsSync(marker), 'startup auto repair must still run');
42
+ assert.notEqual(Number(fs.readFileSync(marker, 'utf8')), instance.pid, 'native checks must run outside the HTTP process');
43
+ for (let i = 0; i < 3; i++) {
44
+ const response = await fetch(`http://127.0.0.1:${port}/api/health`, { signal: AbortSignal.timeout(1000) });
45
+ assert.equal(response.status, 200, 'HTTP must remain usable while a native check is blocked');
46
+ assert.equal((await control.request(data, { action: 'status' }, 1000)).instance.pid, instance.pid);
47
+ }
48
+ const stopAt = Date.now();
49
+ await control.request(data, { action: 'stop', pid: instance.pid, port, startedAt: instance.startedAt }, 4000);
50
+ assert(Date.now() - stopAt < 4000, 'shutdown must cancel the blocked repair worker');
51
+ instance = null;
52
+ console.log('Startup integration isolation PASS: background ready, HTTP/control usable during a blocked native probe, owned worker stopped');
53
+ }
54
+ main().catch(error => { console.error(error); process.exitCode = 1; }).finally(async () => {
55
+ try {
56
+ const info = instance || (await control.request(data, { action: 'status' }, 1000)).instance;
57
+ await control.request(data, { action: 'stop', pid: info.pid, port: info.port, startedAt: info.startedAt });
58
+ } catch {}
59
+ });
@@ -0,0 +1,43 @@
1
+ 'use strict';
2
+ const assert = require('node:assert/strict');
3
+ const fs = require('node:fs');
4
+ const os = require('node:os');
5
+ const path = require('node:path');
6
+ const { StartupIntegrationRepair } = require('../lib/startup-integration-repair');
7
+ const { markIntent } = require('../lib/integration-intent');
8
+ const { isProcessAlive } = require('../lib/runtime-endpoint');
9
+ const workerFile = path.join(__dirname, 'fixtures/startup-repair-worker.cjs');
10
+ async function main() {
11
+ const empty = fs.mkdtempSync(path.join(os.tmpdir(), 'kb-startup-repair-empty-'));
12
+ markIntent(empty, 'codex', 'disconnected');
13
+ const idle = new StartupIntegrationRepair({ dataDir: empty, workerFile: 'does-not-exist' });
14
+ assert.deepEqual(await idle.runOnce(), {}); assert.equal(idle.child, null); await idle.close();
15
+ for (const mode of ['normal', 'manual', 'crash', 'hang']) {
16
+ const dataDir = fs.mkdtempSync(path.join(os.tmpdir(), 'kb-startup-repair-'));
17
+ markIntent(dataDir, 'claude', 'connected'); markIntent(dataDir, 'codex', 'connected');
18
+ markIntent(dataDir, 'zcode', 'disconnected');
19
+ const logs = [];
20
+ const repair = new StartupIntegrationRepair({ dataDir, managerOptions: { homeDir: mode }, workerFile,
21
+ timeoutMs: mode === 'hang' ? 150 : 3000, logger: { warn: (...args) => logs.push(args) } });
22
+ try {
23
+ const result = repair.runOnce();
24
+ assert.equal(repair.runOnce(), result, 'one startup event must not spawn duplicate repair jobs');
25
+ if (mode === 'manual') repair.markSucceeded(['claude']);
26
+ const status = await result;
27
+ assert.equal(status.zcode, undefined, 'disconnected clients must not acquire repair state');
28
+ if (mode === 'normal') {
29
+ assert.equal(status.claude.state, 'failed'); assert.equal(status.codex.state, 'repaired');
30
+ repair.markSucceeded(['claude']); assert.equal(repair.status().claude.state, 'repaired');
31
+ } else if (mode === 'manual') {
32
+ assert.equal(status.claude, undefined, 'late startup failure must not overwrite a manual install');
33
+ assert.equal(status.codex.state, 'repaired');
34
+ } else {
35
+ assert.equal(status.claude.state, 'failed'); assert.equal(status.codex.state, 'failed');
36
+ assert.equal(logs.length, 1, 'worker failure must be reported without automatic retries');
37
+ }
38
+ } finally { await repair.close(); }
39
+ assert.equal(isProcessAlive(repair.child.pid), false, 'owned worker must be reaped on close');
40
+ }
41
+ console.log('Startup repair worker PASS: intent isolation, one attempt, manual success precedence, timeout, crash and owned cleanup');
42
+ }
43
+ main().catch(error => { console.error(error); process.exitCode = 1; });
@@ -59,6 +59,8 @@ const { runCli } = require('../../bin/project-knowledge-integrations');
59
59
  assert.equal(fs.readFileSync(configFile, 'utf8'), untouched);
60
60
 
61
61
  const runtime = createRuntime({ dataDir: layout.getDataDir(), logger, analysisOwner: 'engine', bridgeAdapter: new BridgeAdapter({ dataDir: layout.getDataDir(), bridgeHomeDir }) });
62
+ let startupRepairStops = 0;
63
+ runtime.integrationAutoRepair = { close: async () => { startupRepairStops++; }, status: () => ({}) };
62
64
  server = http.createServer(createRequestHandler(runtime, { integrationOptions: options }));
63
65
  await new Promise(resolve => server.listen(0, '127.0.0.1', resolve));
64
66
  const base = `http://127.0.0.1:${server.address().port}`;
@@ -68,6 +70,7 @@ const { runCli } = require('../../bin/project-knowledge-integrations');
68
70
  return response.json();
69
71
  };
70
72
  const apiInstall = await request('/api/integrations/install', { clients: ['zcode'], captureOnly: true });
73
+ assert.equal(startupRepairStops, 1, 'manual install cancels concurrent startup repair');
71
74
  assert.equal(apiInstall.complete, true);
72
75
  assert(apiInstall.operationId);
73
76
  const apiStatus = await request('/api/integrations/status');
@@ -82,6 +85,7 @@ const { runCli } = require('../../bin/project-knowledge-integrations');
82
85
  assert(errors.some(entry => entry.operationId === failed.operationId && entry.context.client === 'zcode' && entry.error.message.includes('JSON')), 'actual failed phase and operation ID are persisted');
83
86
  fs.writeFileSync(configFile, JSON.stringify(twice));
84
87
  const removed = await request('/api/integrations/uninstall', { clients: ['zcode'], captureOnly: true });
88
+ assert.equal(startupRepairStops, 3, 'manual uninstall cancels startup repair before removing client hooks');
85
89
  assert.equal(removed.complete, true);
86
90
  assert.deepEqual(removed.clients, [], 'capture-only removal does not invoke knowledge plugin operations');
87
91
  assert.equal(removed.capture[0].state, 'removed');
@@ -44,10 +44,12 @@ class IntegrationAutoRepair {
44
44
  async runOnce() {
45
45
  const intent = readIntent(this.dataDir);
46
46
  if (!intent) return this.status();
47
- const status = await this.createManager().statusAll({});
47
+ const clients = Object.entries(intent.clients).filter(([, state]) => state === 'connected').map(([client]) => client);
48
+ if (!clients.length) return this.status();
49
+ const status = await this.createManager().statusAll({ clients });
48
50
  for (const entry of status?.clients || []) {
49
- if (intent.clients[entry.client] !== 'connected') continue;
50
- if (entry.developmentCapture?.installed === true) continue;
51
+ if (readIntent(this.dataDir)?.clients[entry.client] !== 'connected') continue;
52
+ if (entry.developmentCapture?.installed !== false) continue;
51
53
  if (this.attempted.has(entry.client)) continue;
52
54
  await this._attempt(entry.client);
53
55
  }
@@ -0,0 +1,16 @@
1
+ 'use strict';
2
+ const { IntegrationManager } = require('./integration-installer');
3
+ const { IntegrationAutoRepair } = require('./integration-auto-repair');
4
+ process.once('message', async input => {
5
+ const logger = Object.fromEntries(['info', 'warn', 'error'].map(level => [level, (...args) => {
6
+ if (process.connected) process.send({ kind: 'log', level, args });
7
+ }]));
8
+ try {
9
+ const repair = new IntegrationAutoRepair({ dataDir: input.dataDir, logger,
10
+ createManager: () => new IntegrationManager({ ...input.managerOptions, logger }) });
11
+ const results = await repair.runOnce();
12
+ process.send({ ok: true, results }, () => process.disconnect());
13
+ } catch {
14
+ process.send({ ok: false }, () => process.disconnect());
15
+ }
16
+ });
@@ -1332,6 +1332,7 @@ function createRequestHandler(runtime, options = {}) {
1332
1332
  if (method === 'POST' && pathname === '/api/integrations/install') {
1333
1333
  runtime.integrationStatusScan?.invalidate();
1334
1334
  const body = await readJsonBody(req);
1335
+ await runtime.integrationAutoRepair?.close();
1335
1336
  const manager = new IntegrationManager({ ...options.integrationOptions, rootDir: runtime.rootDir, logger: runtime.logger, operationId: requestOperationId, bridgeHomeDir: runtime.bridgeAdapter.hookBinding().bridgeHomeDir });
1336
1337
  const clients = Array.isArray(body.clients) && body.clients.length
1337
1338
  ? body.clients.map(client => String(client))
@@ -1355,6 +1356,7 @@ function createRequestHandler(runtime, options = {}) {
1355
1356
  }
1356
1357
  if (method === 'POST' && pathname === '/api/integrations/uninstall') {
1357
1358
  const body = await readJsonBody(req);
1359
+ await runtime.integrationAutoRepair?.close();
1358
1360
  const manager = new IntegrationManager({ ...options.integrationOptions, rootDir: runtime.rootDir, logger: runtime.logger, operationId: requestOperationId, bridgeHomeDir: runtime.bridgeAdapter.hookBinding().bridgeHomeDir });
1359
1361
  if (body.disableAllCapture === true) {
1360
1362
  const summary = await manager.disableAllCapture({});
@@ -1563,17 +1565,17 @@ async function startServer(options = {}) {
1563
1565
  }
1564
1566
 
1565
1567
  // Startup-event integration repair: one attempt per connected-but-broken
1566
- // client, failures surfaced and never retried in this run. No timers.
1568
+ // client, failures surfaced and never retried in this run. Native checks
1569
+ // run in an owned worker so they cannot block HTTP or launcher readiness.
1567
1570
  try {
1568
- runtime.integrationAutoRepair = new (require('./integration-auto-repair').IntegrationAutoRepair)({
1571
+ runtime.integrationAutoRepair = new (require('./startup-integration-repair').StartupIntegrationRepair)({
1569
1572
  dataDir: runtime.dataPath,
1570
1573
  logger: runtime.logger,
1571
- createManager: () => new IntegrationManager({
1574
+ managerOptions: {
1572
1575
  ...options.integrationOptions,
1573
1576
  rootDir: runtime.rootDir,
1574
- logger: runtime.logger,
1575
1577
  bridgeHomeDir: runtime.bridgeAdapter.hookBinding().bridgeHomeDir,
1576
- }),
1578
+ },
1577
1579
  });
1578
1580
  runtime.integrationAutoRepair.runOnce().catch(error => {
1579
1581
  runtime.logger.warn('integrations.auto_repair_skipped', '启动自动修复未完成。', { error });
@@ -1587,6 +1589,7 @@ async function startServer(options = {}) {
1587
1589
  if (stopping) return stopping;
1588
1590
  stopping = (async () => {
1589
1591
  runtime.integrationStatusScan?.close();
1592
+ await runtime.integrationAutoRepair?.close();
1590
1593
  clearInterval(maintenanceTimer);
1591
1594
  clearInterval(configurationTimer);
1592
1595
  clearTimeout(readyTimer); runtime.moduleBridge.onReady = null;
@@ -0,0 +1,77 @@
1
+ 'use strict';
2
+ const path = require('node:path');
3
+ const { fork, spawn } = require('node:child_process');
4
+ const { readIntent } = require('./integration-intent');
5
+
6
+ // Native client commands can block for minutes. Keep the whole startup
7
+ // status/repair operation outside the HTTP and launcher readiness process.
8
+ class StartupIntegrationRepair {
9
+ constructor({ dataDir, managerOptions = {}, logger, workerFile, timeoutMs = 120000 } = {}) {
10
+ this.dataDir = dataDir; this.managerOptions = managerOptions; this.logger = logger;
11
+ this.workerFile = workerFile || path.join(__dirname, 'integration-repair-worker.js');
12
+ this.timeoutMs = timeoutMs; this.results = {}; this.manualSuccess = new Set();
13
+ this.closed = false; this.promise = null; this.child = null;
14
+ }
15
+ status() { return { ...this.results }; }
16
+ markSucceeded(clients) {
17
+ for (const client of clients || []) {
18
+ this.manualSuccess.add(client);
19
+ if (this.results[client]?.state === 'failed') this.results[client] = { state: 'repaired', at: new Date().toISOString(), error: null };
20
+ }
21
+ }
22
+ runOnce() {
23
+ if (this.promise) return this.promise;
24
+ const clients = Object.entries(readIntent(this.dataDir)?.clients || {}).filter(([, state]) => state === 'connected').map(([client]) => client);
25
+ if (this.closed || !clients.length) return Promise.resolve(this.status());
26
+ this.promise = new Promise(resolve => {
27
+ const child = fork(this.workerFile, [], { stdio: ['ignore', 'ignore', 'ignore', 'ipc'], windowsHide: true, detached: process.platform !== 'win32' });
28
+ this.child = child;
29
+ let settled = false;
30
+ const finish = (results, code) => {
31
+ if (settled) return; settled = true; clearTimeout(timer);
32
+ if (!this.closed) {
33
+ for (const [client, result] of Object.entries(results || {})) {
34
+ if (clients.includes(client) && !this.manualSuccess.has(client)) this.results[client] = result;
35
+ }
36
+ if (code) {
37
+ for (const client of clients) if (!this.manualSuccess.has(client)) this.results[client] = { state: 'failed', at: new Date().toISOString(), error: '启动接入检查未完成,请在 Agent 接入中重试。' };
38
+ Promise.resolve(this.logger?.warn?.('integrations.auto_repair_skipped', '启动接入检查未完成,主界面继续可用。', { context: { code, clients } })).catch(() => {});
39
+ }
40
+ }
41
+ resolve(this.status());
42
+ };
43
+ const timer = setTimeout(() => { void this.terminate().catch(() => {}); finish(null, 'INTEGRATION_REPAIR_TIMEOUT'); }, this.timeoutMs);
44
+ child.on('message', message => {
45
+ if (message?.kind === 'log') {
46
+ if (['info', 'warn', 'error'].includes(message.level) && Array.isArray(message.args) && /^integrations?\./.test(message.args[0] || '')) {
47
+ Promise.resolve(this.logger?.[message.level]?.(...message.args)).catch(() => {});
48
+ }
49
+ return;
50
+ }
51
+ finish(message?.results, message?.ok ? null : 'INTEGRATION_REPAIR_FAILED');
52
+ });
53
+ child.once('error', () => finish(null, 'INTEGRATION_REPAIR_SPAWN_FAILED'));
54
+ child.once('exit', () => finish(null, this.closed ? null : 'INTEGRATION_REPAIR_EXITED'));
55
+ const allowed = ['rootDir', 'homeDir', 'version', 'packageSpec', 'marketplaceSource', 'codexMarketplaceSource', 'bridgeHomeDir', 'bridgeConsumerName', 'claudeSettingsFile', 'codexConfigFile', 'zcodeConfigFile', 'openCodePluginsDir', 'env'];
56
+ child.send({ dataDir: this.dataDir, managerOptions: Object.fromEntries(allowed.filter(key => this.managerOptions[key] !== undefined).map(key => [key, this.managerOptions[key]])) }, error => { if (error) finish(null, 'INTEGRATION_REPAIR_SEND_FAILED'); });
57
+ });
58
+ return this.promise;
59
+ }
60
+ terminate() {
61
+ const child = this.child;
62
+ if (!child?.pid || child.exitCode !== null || child.signalCode !== null) return Promise.resolve();
63
+ return new Promise((resolve, reject) => {
64
+ const timer = setTimeout(() => reject(new Error('启动接入检查进程尚未停止。')), 5000);
65
+ child.once('exit', () => { clearTimeout(timer); resolve(); });
66
+ if (process.platform === 'win32') {
67
+ // Only this live worker and its native probe children are owned here.
68
+ const killer = spawn('taskkill.exe', ['/PID', String(child.pid), '/T', '/F'], { windowsHide: true, stdio: 'ignore' });
69
+ killer.once('error', () => child.kill());
70
+ } else {
71
+ try { process.kill(-child.pid, 'SIGTERM'); } catch { child.kill('SIGTERM'); }
72
+ }
73
+ });
74
+ }
75
+ async close() { this.closed = true; await this.terminate(); }
76
+ }
77
+ module.exports = { StartupIntegrationRepair };
@@ -15,6 +15,8 @@ project-knowledge
15
15
 
16
16
  5.0.6 修复首次启动被未登记的旧工作台会话误拦截:仅有日志、默认提示词及孤立会话的目录可以创建空的新登记,孤立会话保留原字节但不会恢复或触发任务。实际旧项目、设置、引擎状态或维护任务仍拒绝迁移,错误列出具体目录和冲突项。后台启动命令等待 HTTP 就绪后才报成功;启动失败直接在终端显示原因并返回非零退出码,不提前打开浏览器。
17
17
 
18
+ 5.0.22 将启动时的 Agent 接入检查和自动修复放入独立进程,慢客户端命令不再占住后台就绪通知、网页或实例控制。检查失败可在 Agent 接入中重试,停止服务会同时结束自有检查进程;无需清空数据。若仍提示启动未完成,请同时保留 `project-knowledge --version`、`project-knowledge status` 和报错所指 `launcher.log` 的末尾记录,以区分进程失败与慢启动。
19
+
18
20
  在“系统状态”重新配置模型与后台默认模型、embedding 和知识根目录,然后在“领域与项目”导入代码目录及新的空知识目录。原代码目录无需移动。若残留旧知识库托管 Hook,导入预检会要求明确确认替换;不会覆盖第三方 Hook。导入建立 HEAD 基线,不分析已有历史。
19
21
 
20
22
  如果模块是自己独立启动的旧进程,请在它原来的终端中停止后重启新版外壳。外壳不会擅自杀死其他服务;版本或运行期鉴权不一致时显示具体连接阶段,不接管旧服务数据。
@@ -11,9 +11,9 @@
11
11
  "plugins/project-knowledge/hooks/codex.json": "216e03c07295606ef6ebb2c4f669ad0d790b5e7c4f3f34600488033db75e4239",
12
12
  "plugins/project-knowledge/scripts/context-hook.cjs": "da666153ad5723592cce42d25337ae649f7d93e43fab76b3c6d1263933de063f",
13
13
  "plugins/project-knowledge/scripts/mcp-launcher.cjs": "7c68830b5c73eb53ef030756a4de33987f7eab6b1a6cc15602835aac3f954893",
14
- "plugins/project-knowledge/.claude-plugin/plugin.json": "c4069b87a8f1364bd6732fe9ec03b581bef06c14e80f910b550ec81ef6a0f00f",
15
- "plugins/project-knowledge/.codex-plugin/plugin.json": "caa287c40fe42fae5a9e2faaae3ff4e0ff7fab64a45a27bd7a1193e429a175a9",
16
- "plugins/project-knowledge/.mcp.json": "01edf4f4b4fc1db43fbe59892120f15b32d38e4b1460c431a454e5a4cdff70ef"
14
+ "plugins/project-knowledge/.claude-plugin/plugin.json": "acdbc819e7e3949a2b8e01380f3e8811d06f74e5578cc872d736faa0db4e2106",
15
+ "plugins/project-knowledge/.codex-plugin/plugin.json": "915cae73db9f32bbbcf9c5aee24aca9356bf730914489c8611f734616f1e4b6c",
16
+ "plugins/project-knowledge/.mcp.json": "9c493221141723265d80ebff5e68029e159d1688a8bfb121678ad93bf78ff6d8"
17
17
  },
18
18
  "modules": {
19
19
  "terminal": {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sanqianx/project-knowledge",
3
- "version": "5.0.21",
3
+ "version": "5.0.22",
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": {
@@ -2,7 +2,7 @@
2
2
  "$schema": "https://json.schemastore.org/claude-code-plugin-manifest.json",
3
3
  "name": "project-knowledge",
4
4
  "hooks": "./hooks/claude.json",
5
- "version": "5.0.21",
5
+ "version": "5.0.22",
6
6
  "description": "Read durable project decisions and implementation history from Project Knowledge.",
7
7
  "author": {
8
8
  "name": "SanQianX",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "project-knowledge",
3
- "version": "5.0.21",
3
+ "version": "5.0.22",
4
4
  "description": "Read durable project decisions and implementation history from Project Knowledge.",
5
5
  "author": {
6
6
  "name": "SanQianX",
@@ -8,7 +8,7 @@
8
8
  "const fs=require('fs'),path=require('path'),os=require('os');const PKG='@sanqianx/project-knowledge',SC='project-knowledge/integration-runtime/v1';let f='';const root=process.env.CLAUDE_PLUGIN_ROOT||process.env.PLUGIN_ROOT;if(root){f=path.join(root,'scripts','mcp-launcher.cjs');if(!fs.existsSync(f))f=''}if(!f){const dd=process.env.KB_DATA_DIR||path.join(os.homedir(),'.project-knowledge-v5');try{const bp=path.join(dd,'integration-runtime.json');const st=fs.statSync(bp);if(st.isFile()&&st.size<=32768){const b=JSON.parse(fs.readFileSync(bp,'utf8'));if(b&&b.schema===SC&&b.packageName===PKG&&typeof b.packageRoot==='string'&&path.isAbsolute(b.packageRoot)){const c=path.join(b.packageRoot,'plugins','project-knowledge','scripts','mcp-launcher.cjs');const rr=fs.realpathSync(b.packageRoot),cr=fs.realpathSync(c);const rel=path.relative(rr,cr);if(rel&&rel!=='..'&&!rel.startsWith('..'+path.sep)&&!path.isAbsolute(rel)&&fs.statSync(cr).isFile())f=cr}}}catch(e){}}if(!f){console.error('[project-knowledge-mcp] 未能通过受信任插件根或已验证的安装绑定定位启动器,已停止;不访问网络,也不加载工作目录源码。');process.exit(127)}require(f);"
9
9
  ],
10
10
  "env": {
11
- "KB_PLUGIN_VERSION": "5.0.21"
11
+ "KB_PLUGIN_VERSION": "5.0.22"
12
12
  }
13
13
  }
14
14
  }