@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.
- package/.claude-plugin/marketplace.json +1 -1
- package/CHANGELOG.md +11 -0
- package/_site/_test/claude-workbench-test.js +4 -0
- package/_site/_test/fresh-product-data-test.js +14 -0
- package/_site/_test/launcher-readiness-test.js +80 -0
- package/_site/_test/run-all-tests.js +3 -1
- package/_site/lib/product-data.js +16 -5
- package/_site/server.js +5 -2
- package/bin/project-knowledge.js +60 -28
- package/docs/fresh-v5-start.md +2 -0
- package/package.json +1 -1
- package/plugins/project-knowledge/.claude-plugin/plugin.json +1 -1
- package/plugins/project-knowledge/.codex-plugin/plugin.json +1 -1
- package/plugins/project-knowledge/.mcp.json +1 -1
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', '
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
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
|
-
|
|
11
|
-
|
|
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
|
});
|
package/bin/project-knowledge.js
CHANGED
|
@@ -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
|
|
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' :
|
|
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
|
-
|
|
372
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
});
|
package/docs/fresh-v5-start.md
CHANGED
|
@@ -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,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.
|
|
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",
|