@sanqianx/project-knowledge 5.0.18-rc.4 → 5.0.18

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.18-rc.4",
12
+ "version": "5.0.18",
13
13
  "source": "./plugins/project-knowledge",
14
14
  "description": "Search durable project decisions and implementation history through MCP."
15
15
  }
@@ -1,100 +1,103 @@
1
- // Regression test for CDP browser launch failures (T00):
2
- // - browser that exits immediately must fail fast with exit/stderr diagnostics
3
- // - browser that stays alive but never exposes CDP must hit the launch timeout,
4
- // then be killed and cleaned up
5
- // - in both cases: no child process remains alive, profile dir is removed.
6
- const assert = require('assert');
7
- const fs = require('fs');
8
- const os = require('os');
9
- const path = require('path');
10
- const { isWindowsChromeLauncher, platformLaunchArgs, launchCdpBrowser } = require('./helpers/cdp-browser');
11
-
12
- const FIXTURE_EXIT = path.join(__dirname, 'fixtures', 'fake-browser-exit.js');
13
- const FIXTURE_HANG = path.join(__dirname, 'fixtures', 'fake-browser-hang.js');
14
- const FAST_FAIL_BUDGET_MS = 5000;
15
-
16
- function isPidAlive(pid) {
17
- try { process.kill(pid, 0); return true; }
18
- catch (error) { return error.code === 'EPERM'; }
19
- }
20
-
21
- async function waitForPidExit(pid, timeoutMs = 8000) {
22
- const deadline = Date.now() + timeoutMs;
23
- while (Date.now() < deadline) {
24
- if (!isPidAlive(pid)) return false;
25
- await new Promise(resolve => setTimeout(resolve, 100));
26
- }
27
- return isPidAlive(pid);
28
- }
29
-
30
- async function expectLaunchFailure(scenario) {
31
- const pidFile = path.join(os.tmpdir(), `pk-cdp-failure-${scenario}-pid-${process.pid}.txt`);
32
- const profileDir = path.join(os.tmpdir(), `pk-cdp-failure-${scenario}-profile-${process.pid}`);
33
- process.env.KB_FAKE_BROWSER_PIDFILE = pidFile;
34
- try {
35
- const start = Date.now();
36
- let error = null;
37
- try {
38
- await launchCdpBrowser(scenario.options(profileDir));
39
- } catch (caught) {
40
- error = caught;
41
- }
42
- const durationMs = Date.now() - start;
43
- assert(error, `${scenario.name}: launchCdpBrowser must reject`);
44
- assert(durationMs < FAST_FAIL_BUDGET_MS, `${scenario.name}: must fail fast, took ${durationMs}ms`);
45
- const message = error.message;
46
- assert(message.includes(process.execPath), `${scenario.name}: error must include executable path, got: ${message}`);
47
- assert(message.includes(profileDir), `${scenario.name}: error must include user-data-dir, got: ${message}`);
48
- scenario.assertions(message, durationMs);
49
- assert(!fs.existsSync(profileDir), `${scenario.name}: profile dir must be cleaned after failed launch`);
50
- assert(fs.existsSync(pidFile), `${scenario.name}: fixture pid file missing; cannot verify cleanup`);
51
- const pid = Number.parseInt(fs.readFileSync(pidFile, 'utf8').trim(), 10);
52
- const stillAlive = await waitForPidExit(pid);
53
- assert(!stillAlive, `${scenario.name}: spawned child pid ${pid} still alive after failure cleanup`);
54
- return durationMs;
55
- } finally {
56
- delete process.env.KB_FAKE_BROWSER_PIDFILE;
57
- try { fs.rmSync(pidFile, { force: true }); } catch {}
58
- try { fs.rmSync(profileDir, { recursive: true, force: true }); } catch {}
59
- }
60
- }
61
-
62
- (async () => {
63
- assert.strictEqual(isWindowsChromeLauncher('C:/Program Files/Google/Chrome/Application/chrome.exe'), process.platform === 'win32');
64
- assert.strictEqual(isWindowsChromeLauncher(process.execPath), false);
65
- assert.deepStrictEqual(platformLaunchArgs('linux'), ['--disable-dev-shm-usage', '--no-sandbox']);
66
- assert.deepStrictEqual(platformLaunchArgs('win32'), []);
67
- const exitDuration = await expectLaunchFailure({
68
- name: 'immediate-exit',
69
- options: profileDir => ({
70
- chrome: process.execPath,
71
- prependArgs: [FIXTURE_EXIT],
72
- profileDir,
73
- url: 'http://127.0.0.1:1/',
74
- }),
75
- assertions(message) {
76
- assert(message.includes('exit code: 3'), `immediate-exit: error must contain exit code, got: ${message}`);
77
- assert(message.includes('fake-browser-exit stderr: simulated browser crash before CDP'), `immediate-exit: error must contain stderr tail, got: ${message}`);
78
- },
79
- });
80
- console.log(`cdp-browser immediate-exit failure test PASS (${exitDuration}ms)`);
81
-
82
- const hangDuration = await expectLaunchFailure({
83
- name: 'hang-timeout',
84
- options: profileDir => ({
85
- chrome: process.execPath,
86
- prependArgs: [FIXTURE_HANG],
87
- profileDir,
88
- url: 'http://127.0.0.1:1/',
89
- launchTimeoutMs: 1500,
90
- }),
91
- assertions(message, durationMs) {
92
- assert(message.includes('DevToolsActivePort'), `hang-timeout: error must mention DevToolsActivePort wait, got: ${message}`);
93
- assert(durationMs < FAST_FAIL_BUDGET_MS, `hang-timeout: bounded duration, took ${durationMs}ms`);
94
- },
95
- });
96
- console.log(`cdp-browser hang-timeout failure test PASS (${hangDuration}ms)`);
97
- })().catch(error => {
98
- console.error(error.stack || error.message);
99
- process.exitCode = 1;
100
- });
1
+ // Regression test for CDP browser launch failures (T00):
2
+ // - browser that exits immediately must fail fast with exit/stderr diagnostics
3
+ // - browser that stays alive but never exposes CDP must hit the launch timeout,
4
+ // then be killed and cleaned up
5
+ // - in both cases: no child process remains alive, profile dir is removed.
6
+ const assert = require('assert');
7
+ const fs = require('fs');
8
+ const os = require('os');
9
+ const path = require('path');
10
+ const { isWindowsChromeLauncher, platformLaunchArgs, launchCdpBrowser } = require('./helpers/cdp-browser');
11
+
12
+ const FIXTURE_EXIT = path.join(__dirname, 'fixtures', 'fake-browser-exit.js');
13
+ const FIXTURE_HANG = path.join(__dirname, 'fixtures', 'fake-browser-hang.js');
14
+ const FAST_FAIL_BUDGET_MS = 5000;
15
+
16
+ function isPidAlive(pid) {
17
+ try { process.kill(pid, 0); return true; }
18
+ catch (error) { return error.code === 'EPERM'; }
19
+ }
20
+
21
+ async function waitForPidExit(pid, timeoutMs = 8000) {
22
+ const deadline = Date.now() + timeoutMs;
23
+ while (Date.now() < deadline) {
24
+ if (!isPidAlive(pid)) return false;
25
+ await new Promise(resolve => setTimeout(resolve, 100));
26
+ }
27
+ return isPidAlive(pid);
28
+ }
29
+
30
+ async function expectLaunchFailure(scenario) {
31
+ const pidFile = path.join(os.tmpdir(), `pk-cdp-failure-${scenario}-pid-${process.pid}.txt`);
32
+ const profileDir = path.join(os.tmpdir(), `pk-cdp-failure-${scenario}-profile-${process.pid}`);
33
+ process.env.KB_FAKE_BROWSER_PIDFILE = pidFile;
34
+ try {
35
+ const start = Date.now();
36
+ let error = null;
37
+ try {
38
+ await launchCdpBrowser(scenario.options(profileDir));
39
+ } catch (caught) {
40
+ error = caught;
41
+ }
42
+ const durationMs = Date.now() - start;
43
+ assert(error, `${scenario.name}: launchCdpBrowser must reject`);
44
+ assert(durationMs < FAST_FAIL_BUDGET_MS, `${scenario.name}: must fail fast, took ${durationMs}ms`);
45
+ const message = error.message;
46
+ assert(message.includes(process.execPath), `${scenario.name}: error must include executable path, got: ${message}`);
47
+ assert(message.includes(profileDir), `${scenario.name}: error must include user-data-dir, got: ${message}`);
48
+ scenario.assertions(message, durationMs);
49
+ assert(!fs.existsSync(profileDir), `${scenario.name}: profile dir must be cleaned after failed launch`);
50
+ assert(fs.existsSync(pidFile), `${scenario.name}: fixture pid file missing; cannot verify cleanup`);
51
+ const pid = Number.parseInt(fs.readFileSync(pidFile, 'utf8').trim(), 10);
52
+ const stillAlive = await waitForPidExit(pid);
53
+ assert(!stillAlive, `${scenario.name}: spawned child pid ${pid} still alive after failure cleanup`);
54
+ return durationMs;
55
+ } finally {
56
+ delete process.env.KB_FAKE_BROWSER_PIDFILE;
57
+ try { fs.rmSync(pidFile, { force: true }); } catch {}
58
+ try { fs.rmSync(profileDir, { recursive: true, force: true }); } catch {}
59
+ }
60
+ }
61
+
62
+ (async () => {
63
+ assert.strictEqual(isWindowsChromeLauncher('C:/Program Files/Google/Chrome/Application/chrome.exe'), process.platform === 'win32');
64
+ assert.strictEqual(isWindowsChromeLauncher(process.execPath), false);
65
+ assert.deepStrictEqual(platformLaunchArgs('linux'), ['--disable-dev-shm-usage', '--no-sandbox']);
66
+ assert.deepStrictEqual(platformLaunchArgs('win32'), []);
67
+ const exitDuration = await expectLaunchFailure({
68
+ name: 'immediate-exit',
69
+ options: profileDir => ({
70
+ chrome: process.execPath,
71
+ prependArgs: [FIXTURE_EXIT],
72
+ profileDir,
73
+ url: 'http://127.0.0.1:1/',
74
+ }),
75
+ assertions(message) {
76
+ assert(message.includes('exit code: 3'), `immediate-exit: error must contain exit code, got: ${message}`);
77
+ assert(message.includes('fake-browser-exit stderr: simulated browser crash before CDP'), `immediate-exit: error must contain stderr tail, got: ${message}`);
78
+ },
79
+ });
80
+ console.log(`cdp-browser immediate-exit failure test PASS (${exitDuration}ms)`);
81
+
82
+ const hangDuration = await expectLaunchFailure({
83
+ name: 'hang-timeout',
84
+ options: profileDir => ({
85
+ chrome: process.execPath,
86
+ prependArgs: [FIXTURE_HANG],
87
+ profileDir,
88
+ url: 'http://127.0.0.1:1/',
89
+ launchTimeoutMs: 1500,
90
+ // Single attempt keeps this scenario a bounded fast-failure check; the
91
+ // production default retries transient stalls (see launchCdpBrowser).
92
+ launchAttempts: 1,
93
+ }),
94
+ assertions(message, durationMs) {
95
+ assert(message.includes('DevToolsActivePort'), `hang-timeout: error must mention DevToolsActivePort wait, got: ${message}`);
96
+ assert(durationMs < FAST_FAIL_BUDGET_MS, `hang-timeout: bounded duration, took ${durationMs}ms`);
97
+ },
98
+ });
99
+ console.log(`cdp-browser hang-timeout failure test PASS (${hangDuration}ms)`);
100
+ })().catch(error => {
101
+ console.error(error.stack || error.message);
102
+ process.exitCode = 1;
103
+ });
@@ -9,9 +9,9 @@
9
9
  "plugins/project-knowledge/hooks/codex.json": "216e03c07295606ef6ebb2c4f669ad0d790b5e7c4f3f34600488033db75e4239",
10
10
  "plugins/project-knowledge/scripts/context-hook.cjs": "da666153ad5723592cce42d25337ae649f7d93e43fab76b3c6d1263933de063f",
11
11
  "plugins/project-knowledge/scripts/mcp-launcher.cjs": "7c68830b5c73eb53ef030756a4de33987f7eab6b1a6cc15602835aac3f954893",
12
- "plugins/project-knowledge/.claude-plugin/plugin.json": "9d62789c8928d1ba03770fe6baeeb3c8d78148917cbb919113a29d5213ac2dab",
13
- "plugins/project-knowledge/.codex-plugin/plugin.json": "ada70f6fdc68f254239d68e41572a7615e08dc1780c1ad86fb64c5c1cd5abeb6",
14
- "plugins/project-knowledge/.mcp.json": "8774ced0ad45fceee59721002a195062f2c1a2bee0c9f78c47dc10cf10971547",
12
+ "plugins/project-knowledge/.claude-plugin/plugin.json": "40a10c34cbc5adb4a6a8a2d5f2d9ecad81705a23b884ae5fb0c3d86e75d7b1c8",
13
+ "plugins/project-knowledge/.codex-plugin/plugin.json": "6be99f95fb81d225d07ba76c08af8f0dcc12d0eb705b105e38178e530fca076e",
14
+ "plugins/project-knowledge/.mcp.json": "c6077d29401f0f85b19830358e5be2f4667b100834ec265b0f39b96049b87e1e",
15
15
  "ui/live-client.js": "0d81fa894c629bb839e1931fc3c5ebad4d831aae8232d6ce8c0f542e9e3559e8"
16
16
  },
17
17
  "modules": {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sanqianx/project-knowledge",
3
- "version": "5.0.18-rc.4",
3
+ "version": "5.0.18",
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.18-rc.4",
5
+ "version": "5.0.18",
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.18-rc.4",
3
+ "version": "5.0.18",
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.18-rc.4"
11
+ "KB_PLUGIN_VERSION": "5.0.18"
12
12
  }
13
13
  }
14
14
  }