@sanqianx/project-knowledge 5.0.19 → 5.0.20-rc.1

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.
Files changed (200) hide show
  1. package/.agents/plugins/marketplace.json +3 -3
  2. package/.claude-plugin/marketplace.json +1 -1
  3. package/CHANGELOG.md +5 -0
  4. package/LICENSE +201 -201
  5. package/NOTICE +5 -5
  6. package/README.md +349 -349
  7. package/_modules/knowledge-engine/README.md +4 -0
  8. package/_modules/knowledge-engine/src/analysis-failures.js +39 -0
  9. package/_modules/knowledge-engine/src/analysis-worker.js +6 -2
  10. package/_modules/knowledge-engine/src/coordinator.js +7 -0
  11. package/_modules/knowledge-engine/src/headless-analyzer.js +2 -1
  12. package/_modules/knowledge-engine/src/server.js +1 -1
  13. package/_site/_test/agent-capture-mcp-ui-test.js +67 -67
  14. package/_site/_test/ai-profile-test.js +75 -75
  15. package/_site/_test/ai-vendor-presets-test.js +91 -91
  16. package/_site/_test/automatic-context-test.js +70 -70
  17. package/_site/_test/bin-cli-test.js +230 -230
  18. package/_site/_test/bridge-adapter-test.js +95 -95
  19. package/_site/_test/bridge-consumer-service-test.js +310 -310
  20. package/_site/_test/candidate-bin-content-test.js +35 -35
  21. package/_site/_test/candidate-product-assets-test.js +13 -13
  22. package/_site/_test/candidate-release-contract-test.js +14 -14
  23. package/_site/_test/capture-hook-dedup-test.js +158 -158
  24. package/_site/_test/claude-executable-discovery-test.js +28 -28
  25. package/_site/_test/claude-session-lifecycle-sweep-test.js +252 -252
  26. package/_site/_test/cli-instance-management-test.js +62 -62
  27. package/_site/_test/client-capture-restart-e2e-test.js +189 -189
  28. package/_site/_test/codex-conversation-projection-test.js +60 -60
  29. package/_site/_test/codex-marketplace-scoped-path-test.js +186 -186
  30. package/_site/_test/codex-notify-capture-e2e-test.js +101 -101
  31. package/_site/_test/codex-notify-fanout-test.js +134 -134
  32. package/_site/_test/codex-notify-upgrade-test.js +253 -253
  33. package/_site/_test/commit-boundary-freeze-test.js +147 -147
  34. package/_site/_test/commit-conversation-binding-test.js +4 -4
  35. package/_site/_test/configuration-generation-test.js +22 -22
  36. package/_site/_test/context-pack-test.js +200 -200
  37. package/_site/_test/conversation-api-test.js +10 -10
  38. package/_site/_test/conversation-origin-test.js +15 -15
  39. package/_site/_test/cross-repo-isolation-e2e-test.js +187 -187
  40. package/_site/_test/embedding-config-test.js +32 -32
  41. package/_site/_test/fixtures/fake-browser-exit.js +10 -10
  42. package/_site/_test/fixtures/fake-browser-hang.js +10 -10
  43. package/_site/_test/fixtures/integration-busy-worker.cjs +6 -6
  44. package/_site/_test/fixtures/make-git-repos.js +92 -92
  45. package/_site/_test/fixtures/retrieval-gold-queries.json +32 -32
  46. package/_site/_test/fresh-hook-reimport-test.js +53 -53
  47. package/_site/_test/fresh-product-data-test.js +56 -56
  48. package/_site/_test/git-path-compatibility-test.js +80 -80
  49. package/_site/_test/git-validation-test.js +81 -81
  50. package/_site/_test/hook-worktree-test.js +42 -42
  51. package/_site/_test/index-builder-test.js +41 -41
  52. package/_site/_test/index-error-display-contract-test.js +10 -10
  53. package/_site/_test/integration-adapters-test.js +67 -67
  54. package/_site/_test/integration-capture-setup-test.js +118 -118
  55. package/_site/_test/integration-runtime-binding-a2-test.js +566 -566
  56. package/_site/_test/integration-runtime-binding-a3-test.js +227 -227
  57. package/_site/_test/integration-runtime-binding-test.js +473 -473
  58. package/_site/_test/integration-status-scan-test.js +29 -29
  59. package/_site/_test/kb-framework-test.js +57 -57
  60. package/_site/_test/kbpath-follow-test.js +63 -63
  61. package/_site/_test/knowledge-db-test.js +144 -144
  62. package/_site/_test/knowledge-engine-client-test.js +109 -109
  63. package/_site/_test/knowledge-maintenance-test.js +40 -40
  64. package/_site/_test/knowledge-panel-ui-contract-test.js +112 -0
  65. package/_site/_test/knowledge-scopes-test.js +37 -37
  66. package/_site/_test/knowledge-storage-location-test.js +28 -28
  67. package/_site/_test/launcher-readiness-test.js +80 -80
  68. package/_site/_test/legacy-claude-session-alias-test.js +106 -106
  69. package/_site/_test/legacy-routes-removed-test.js +47 -47
  70. package/_site/_test/legacy-workbench-exclusion-test.js +92 -92
  71. package/_site/_test/live-gateway-test.js +48 -48
  72. package/_site/_test/local-marketplace-test.js +16 -16
  73. package/_site/_test/log-redaction-test.js +58 -58
  74. package/_site/_test/markdown-maintenance-test.js +11 -11
  75. package/_site/_test/model-context-windows-test.js +89 -89
  76. package/_site/_test/module-agent-start-proxy-test.js +65 -65
  77. package/_site/_test/module-artifact-integrity-test.js +27 -27
  78. package/_site/_test/module-boundary-test.js +30 -30
  79. package/_site/_test/module-bridge-eventbridge-test.js +202 -202
  80. package/_site/_test/module-dynamic-port-test.js +118 -118
  81. package/_site/_test/module-index-proxy-test.js +33 -33
  82. package/_site/_test/module-model-configuration-test.js +44 -44
  83. package/_site/_test/module-process-lifecycle-test.js +35 -35
  84. package/_site/_test/module-stream-proxy-test.js +34 -34
  85. package/_site/_test/module-terminal-restart-test.js +63 -63
  86. package/_site/_test/module-wiring-unbind-race-test.js +59 -59
  87. package/_site/_test/native-context-integrations-test.js +86 -86
  88. package/_site/_test/orphaned-claim-recovery-test.js +80 -80
  89. package/_site/_test/package-boundary-test.js +43 -42
  90. package/_site/_test/package-startup-test.js +56 -56
  91. package/_site/_test/packaged-ui-smoke-test.js +88 -88
  92. package/_site/_test/pr-consumer-contract-test.js +55 -55
  93. package/_site/_test/product-brand-contract-test.js +25 -25
  94. package/_site/_test/product-diagnostics-ui-test.js +93 -93
  95. package/_site/_test/product-import-ui-test.js +104 -104
  96. package/_site/_test/product-progress-status-test.js +12 -1
  97. package/_site/_test/project-remove-running-guard-test.js +79 -79
  98. package/_site/_test/project-store-test.js +90 -90
  99. package/_site/_test/registry-events-test.js +38 -38
  100. package/_site/_test/release-version-sync-test.js +115 -115
  101. package/_site/_test/requirement-binding-test.js +113 -113
  102. package/_site/_test/runtime-endpoint-test.js +64 -64
  103. package/_site/_test/scanner-test.js +83 -83
  104. package/_site/_test/server-security-test.js +96 -96
  105. package/_site/_test/sessions-stream-test.js +93 -93
  106. package/_site/_test/shared-scope-root-binding-test.js +67 -67
  107. package/_site/_test/simple-import-test.js +98 -98
  108. package/_site/_test/startup-analysis-disabled-test.js +6 -6
  109. package/_site/_test/team-knowledge-store-sparse-checkout-test.js +196 -196
  110. package/_site/_test/team-knowledge-store-test.js +132 -132
  111. package/_site/_test/test-server-cleanup-test.js +35 -35
  112. package/_site/_test/test-server-module-isolation-test.js +37 -37
  113. package/_site/_test/tracking-start-test.js +35 -35
  114. package/_site/_test/workbench-context-receipts-test.js +22 -22
  115. package/_site/_test/workspace-ui-contract-test.js +31 -31
  116. package/_site/_test/zcode-capture-integration-test.js +108 -108
  117. package/_site/lib/ai-vendor-presets.js +83 -83
  118. package/_site/lib/ai-workspace.js +3 -3
  119. package/_site/lib/atomic-file.js +204 -204
  120. package/_site/lib/bridge-adapter.js +260 -260
  121. package/_site/lib/capture-hook-config.js +108 -108
  122. package/_site/lib/claude-cli-runner.js +89 -89
  123. package/_site/lib/cli-instances.js +43 -43
  124. package/_site/lib/codex-context-status.js +17 -17
  125. package/_site/lib/codex-notify-config.js +193 -193
  126. package/_site/lib/commit-conversation-binder.js +12 -12
  127. package/_site/lib/commit-reconciler.js +38 -38
  128. package/_site/lib/context-pack-builder.js +299 -299
  129. package/_site/lib/conversation-exclusions.js +204 -204
  130. package/_site/lib/conversation-query-service.js +17 -17
  131. package/_site/lib/data-dir.js +13 -13
  132. package/_site/lib/embedding-service.js +85 -85
  133. package/_site/lib/engine-connection.js +93 -93
  134. package/_site/lib/engine-knowledge-client.js +72 -72
  135. package/_site/lib/folder-picker-output.js +3 -3
  136. package/_site/lib/git-path.js +14 -14
  137. package/_site/lib/github-team-store.js +3 -3
  138. package/_site/lib/index-builder.js +157 -157
  139. package/_site/lib/integration-status-scan.js +99 -99
  140. package/_site/lib/integration-status-worker.js +14 -14
  141. package/_site/lib/kb-framework.js +15 -15
  142. package/_site/lib/knowledge-db.js +363 -363
  143. package/_site/lib/knowledge-schema.js +121 -121
  144. package/_site/lib/live-gateway.js +78 -78
  145. package/_site/lib/llm-client.js +12 -12
  146. package/_site/lib/model-context-windows.js +82 -82
  147. package/_site/lib/module-bridge.js +874 -874
  148. package/_site/lib/module-port-resolver.js +57 -57
  149. package/_site/lib/post-commit-automation.js +54 -54
  150. package/_site/lib/product-data.js +35 -35
  151. package/_site/lib/project-lifecycle-service.js +14 -14
  152. package/_site/lib/project-registry-store.js +129 -129
  153. package/_site/lib/prompt-registry.js +148 -148
  154. package/_site/lib/registry-events.js +16 -16
  155. package/_site/lib/requirement-adapters.js +10 -10
  156. package/_site/lib/requirement-binder.js +112 -112
  157. package/_site/lib/runtime-control.js +89 -89
  158. package/_site/lib/runtime-endpoint.js +5 -5
  159. package/_site/lib/zcode-context-integration.js +60 -60
  160. package/_site/scripts/codex-notify-fanout.cjs +95 -95
  161. package/_site/scripts/install-module-candidates.js +69 -69
  162. package/_site/scripts/pack-module-candidates.js +89 -89
  163. package/_site/scripts/sync-product-content.js +28 -28
  164. package/_site/scripts/sync-release-version.js +164 -164
  165. package/_site/scripts/vendor-modules.js +49 -49
  166. package/_site/scripts/verify-module-runtime.js +50 -50
  167. package/_site/scripts/verify-product-runtime.js +36 -36
  168. package/_site/scripts/verify-retrieval-gold.js +83 -83
  169. package/_site/server.js +15 -15
  170. package/_site/start.bat +26 -26
  171. package/_site/stop.bat +11 -11
  172. package/bin/project-knowledge-integrations.js +254 -254
  173. package/bin/project-knowledge-kb.js +67 -67
  174. package/bin/project-knowledge-mcp.js +8 -8
  175. package/bin/project-knowledge.js +170 -170
  176. package/claude-prompts.json +21 -21
  177. package/docs/README.zh-CN.md +205 -205
  178. package/docs/assets/architecture.svg +181 -181
  179. package/docs/assets/logo.svg +49 -49
  180. package/docs/fresh-v5-start.md +42 -42
  181. package/docs/leader-glm-workbench-playbook.md +122 -122
  182. package/docs/project-registry-schema.md +11 -11
  183. package/docs/release-5.0.16-acceptance.md +176 -176
  184. package/docs/release-5.0.17-acceptance.md +117 -117
  185. package/docs/release-5.0.19-acceptance.md +39 -25
  186. package/docs/team-knowledge-mode-a-plan.md +73 -73
  187. package/module-runtime-manifest.json +53 -33
  188. package/package.json +3 -2
  189. package/plugins/project-knowledge/.claude-plugin/plugin.json +1 -1
  190. package/plugins/project-knowledge/.codex-plugin/plugin.json +1 -1
  191. package/plugins/project-knowledge/.mcp.json +1 -1
  192. package/plugins/project-knowledge/opencode/project-knowledge.md +3 -3
  193. package/plugins/project-knowledge/skills/project-knowledge/SKILL.md +28 -28
  194. package/templates/change.md +30 -30
  195. package/templates/project-readme.md +18 -18
  196. package/ui/index.html +1 -1
  197. package/ui/knowledge-panel.html +118 -0
  198. package/ui/product.css +8 -0
  199. package/ui/product.js +146 -4
  200. package/vendor-manifest.json +55 -54
@@ -1,89 +1,89 @@
1
- 'use strict';
2
- const net = require('node:net');
3
- const fs = require('node:fs');
4
- const path = require('node:path');
5
- const crypto = require('node:crypto');
6
-
7
- const SCHEMA = 'project-knowledge/runtime-control/v1';
8
- function normalizeDataDir(value) {
9
- const absolute = path.resolve(value);
10
- return process.platform === 'win32' ? absolute.toLowerCase() : absolute;
11
- }
12
- function controlPath(dataDir) {
13
- const hash = crypto.createHash('sha256').update(normalizeDataDir(dataDir)).digest('hex').slice(0, 32);
14
- return process.platform === 'win32' ? `\\\\.\\pipe\\project-knowledge-${hash}` : path.join(dataDir, 'runtime-control.sock');
15
- }
16
- function identity(dataDir, endpoint = {}) {
17
- return { schema: SCHEMA, name: 'project-knowledge', pid: process.pid, dataDir: path.resolve(dataDir), ...endpoint };
18
- }
19
- function request(dataDir, message, timeoutMs = 45000) {
20
- return new Promise((resolve, reject) => {
21
- const socket = net.createConnection(controlPath(dataDir));
22
- let buffer = '', done = false;
23
- const timer = setTimeout(() => socket.destroy(new Error('知识库控制请求超时,停止尚未确认。')), timeoutMs);
24
- const finish = (error, result) => {
25
- if (done) return; done = true;
26
- clearTimeout(timer); socket.destroy();
27
- if (error) reject(error); else resolve(result);
28
- };
29
- socket.once('connect', () => socket.write(JSON.stringify(message) + '\n'));
30
- socket.on('data', chunk => {
31
- buffer += chunk.toString();
32
- if (buffer.length > 65536) return finish(new Error('无效的实例响应。'));
33
- if (!buffer.includes('\n')) return;
34
- try {
35
- const value = JSON.parse(buffer.slice(0, buffer.indexOf('\n')));
36
- if (!value.ok) return finish(new Error(value.message || '实例控制失败。'));
37
- if (value.instance?.schema !== SCHEMA || normalizeDataDir(value.instance.dataDir) !== normalizeDataDir(dataDir)) return finish(new Error('实例身份不匹配,已拒绝控制。'));
38
- finish(null, value);
39
- } catch (error) { finish(error); }
40
- });
41
- socket.once('error', error => finish(error));
42
- socket.once('end', () => { if (!buffer.includes('\n')) finish(new Error('实例断开连接,停止尚未确认。')); });
43
- });
44
- }
45
-
46
- async function listen(dataDir, info, stop) {
47
- const address = controlPath(dataDir);
48
- if (process.platform !== 'win32' && fs.existsSync(address)) {
49
- try { await request(dataDir, { action: 'status' }, 1000); throw new Error('另一个知识库实例已占用控制通道。'); }
50
- catch (error) {
51
- if (!['ECONNREFUSED', 'ENOENT'].includes(error.code)) throw error;
52
- fs.unlinkSync(address);
53
- }
54
- }
55
- const sockets = new Set();
56
- let stopping = false, stoppingSocket;
57
- const server = net.createServer(socket => {
58
- sockets.add(socket); socket.once('close', () => sockets.delete(socket));
59
- socket.on('error', () => {});
60
- socket.setTimeout(45000, () => socket.destroy());
61
- let buffer = '';
62
- socket.on('data', chunk => {
63
- buffer += chunk.toString();
64
- if (buffer.length > 16384) return socket.destroy();
65
- if (!buffer.includes('\n')) return;
66
- socket.removeAllListeners('data');
67
- const respond = value => { if (!socket.destroyed) socket.end(JSON.stringify({ ...value, instance: info }) + '\n'); };
68
- let message;
69
- try { message = JSON.parse(buffer.slice(0, buffer.indexOf('\n'))); }
70
- catch { return respond({ ok: false, message: '无效的控制请求。' }); }
71
- if (message.action === 'status') return respond({ ok: true, status: stopping ? 'stopping' : 'running' });
72
- // A stale endpoint/port must never become a signal to an unrelated PID.
73
- if (message.action !== 'stop' || message.pid !== info.pid || message.port !== info.port || message.startedAt !== info.startedAt) return respond({ ok: false, message: '实例身份已变化,已拒绝停止。' });
74
- if (stopping) return respond({ ok: false, message: '实例正在停止,请稍后查询状态。' });
75
- stopping = true; stoppingSocket = socket;
76
- Promise.resolve().then(stop).then(() => respond({ ok: true, status: 'stopped' })).catch(error => respond({ ok: false, message: error.message }));
77
- });
78
- });
79
- await new Promise((resolve, reject) => { server.once('error', reject); server.listen(address, resolve); });
80
- if (process.platform !== 'win32') fs.chmodSync(address, 0o600);
81
- return {
82
- close() {
83
- // Keep only the requesting socket for the final shutdown acknowledgement.
84
- return new Promise(resolve => { server.close(resolve); for (const socket of sockets) if (socket !== stoppingSocket) socket.destroy(); if (stopping) resolve(); });
85
- },
86
- };
87
- }
88
-
89
- module.exports = { SCHEMA, normalizeDataDir, identity, controlPath, request, listen };
1
+ 'use strict';
2
+ const net = require('node:net');
3
+ const fs = require('node:fs');
4
+ const path = require('node:path');
5
+ const crypto = require('node:crypto');
6
+
7
+ const SCHEMA = 'project-knowledge/runtime-control/v1';
8
+ function normalizeDataDir(value) {
9
+ const absolute = path.resolve(value);
10
+ return process.platform === 'win32' ? absolute.toLowerCase() : absolute;
11
+ }
12
+ function controlPath(dataDir) {
13
+ const hash = crypto.createHash('sha256').update(normalizeDataDir(dataDir)).digest('hex').slice(0, 32);
14
+ return process.platform === 'win32' ? `\\\\.\\pipe\\project-knowledge-${hash}` : path.join(dataDir, 'runtime-control.sock');
15
+ }
16
+ function identity(dataDir, endpoint = {}) {
17
+ return { schema: SCHEMA, name: 'project-knowledge', pid: process.pid, dataDir: path.resolve(dataDir), ...endpoint };
18
+ }
19
+ function request(dataDir, message, timeoutMs = 45000) {
20
+ return new Promise((resolve, reject) => {
21
+ const socket = net.createConnection(controlPath(dataDir));
22
+ let buffer = '', done = false;
23
+ const timer = setTimeout(() => socket.destroy(new Error('知识库控制请求超时,停止尚未确认。')), timeoutMs);
24
+ const finish = (error, result) => {
25
+ if (done) return; done = true;
26
+ clearTimeout(timer); socket.destroy();
27
+ if (error) reject(error); else resolve(result);
28
+ };
29
+ socket.once('connect', () => socket.write(JSON.stringify(message) + '\n'));
30
+ socket.on('data', chunk => {
31
+ buffer += chunk.toString();
32
+ if (buffer.length > 65536) return finish(new Error('无效的实例响应。'));
33
+ if (!buffer.includes('\n')) return;
34
+ try {
35
+ const value = JSON.parse(buffer.slice(0, buffer.indexOf('\n')));
36
+ if (!value.ok) return finish(new Error(value.message || '实例控制失败。'));
37
+ if (value.instance?.schema !== SCHEMA || normalizeDataDir(value.instance.dataDir) !== normalizeDataDir(dataDir)) return finish(new Error('实例身份不匹配,已拒绝控制。'));
38
+ finish(null, value);
39
+ } catch (error) { finish(error); }
40
+ });
41
+ socket.once('error', error => finish(error));
42
+ socket.once('end', () => { if (!buffer.includes('\n')) finish(new Error('实例断开连接,停止尚未确认。')); });
43
+ });
44
+ }
45
+
46
+ async function listen(dataDir, info, stop) {
47
+ const address = controlPath(dataDir);
48
+ if (process.platform !== 'win32' && fs.existsSync(address)) {
49
+ try { await request(dataDir, { action: 'status' }, 1000); throw new Error('另一个知识库实例已占用控制通道。'); }
50
+ catch (error) {
51
+ if (!['ECONNREFUSED', 'ENOENT'].includes(error.code)) throw error;
52
+ fs.unlinkSync(address);
53
+ }
54
+ }
55
+ const sockets = new Set();
56
+ let stopping = false, stoppingSocket;
57
+ const server = net.createServer(socket => {
58
+ sockets.add(socket); socket.once('close', () => sockets.delete(socket));
59
+ socket.on('error', () => {});
60
+ socket.setTimeout(45000, () => socket.destroy());
61
+ let buffer = '';
62
+ socket.on('data', chunk => {
63
+ buffer += chunk.toString();
64
+ if (buffer.length > 16384) return socket.destroy();
65
+ if (!buffer.includes('\n')) return;
66
+ socket.removeAllListeners('data');
67
+ const respond = value => { if (!socket.destroyed) socket.end(JSON.stringify({ ...value, instance: info }) + '\n'); };
68
+ let message;
69
+ try { message = JSON.parse(buffer.slice(0, buffer.indexOf('\n'))); }
70
+ catch { return respond({ ok: false, message: '无效的控制请求。' }); }
71
+ if (message.action === 'status') return respond({ ok: true, status: stopping ? 'stopping' : 'running' });
72
+ // A stale endpoint/port must never become a signal to an unrelated PID.
73
+ if (message.action !== 'stop' || message.pid !== info.pid || message.port !== info.port || message.startedAt !== info.startedAt) return respond({ ok: false, message: '实例身份已变化,已拒绝停止。' });
74
+ if (stopping) return respond({ ok: false, message: '实例正在停止,请稍后查询状态。' });
75
+ stopping = true; stoppingSocket = socket;
76
+ Promise.resolve().then(stop).then(() => respond({ ok: true, status: 'stopped' })).catch(error => respond({ ok: false, message: error.message }));
77
+ });
78
+ });
79
+ await new Promise((resolve, reject) => { server.once('error', reject); server.listen(address, resolve); });
80
+ if (process.platform !== 'win32') fs.chmodSync(address, 0o600);
81
+ return {
82
+ close() {
83
+ // Keep only the requesting socket for the final shutdown acknowledgement.
84
+ return new Promise(resolve => { server.close(resolve); for (const socket of sockets) if (socket !== stoppingSocket) socket.destroy(); if (stopping) resolve(); });
85
+ },
86
+ };
87
+ }
88
+
89
+ module.exports = { SCHEMA, normalizeDataDir, identity, controlPath, request, listen };
@@ -88,8 +88,8 @@ function writeEndpoint(dataDir, value) {
88
88
  } catch (error) {
89
89
  // Windows cannot always replace an existing file with renameSync.
90
90
  try { fs.unlinkSync(target); } catch {
91
- // The following rename is authoritative and reports endpoint replacement failure.
92
- }
91
+ // The following rename is authoritative and reports endpoint replacement failure.
92
+ }
93
93
  fs.renameSync(temp, target);
94
94
  }
95
95
  return endpoint;
@@ -122,9 +122,9 @@ function claimEndpoint(dataDir, value) {
122
122
  try {
123
123
  const ageMs = Date.now() - fs.statSync(target).mtimeMs;
124
124
  if (!existing && ageMs < 2000) return { claimed: false, endpoint: null };
125
- } catch {
126
- // clearEndpoint performs the ownership-safe final check when stat is unavailable.
127
- }
125
+ } catch {
126
+ // clearEndpoint performs the ownership-safe final check when stat is unavailable.
127
+ }
128
128
  clearEndpoint(dataDir, existing ? { pid: existing.pid } : {});
129
129
  }
130
130
  }
@@ -1,60 +1,60 @@
1
- 'use strict';
2
- const fs = require('node:fs');
3
- const path = require('node:path');
4
- const AtomicFile = require('./atomic-file');
5
-
6
- function configuration(file) {
7
- if (!fs.existsSync(file)) return {};
8
- let value; try { value = JSON.parse(fs.readFileSync(file, 'utf8')); } catch { throw new Error('ZCode 配置不是有效 JSON,未覆盖原文件。'); }
9
- if (!value || typeof value !== 'object' || Array.isArray(value)) throw new Error('ZCode 配置必须是 JSON 对象,未覆盖原文件。');
10
- return value;
11
- }
12
- const scriptFor = root => path.join(root, 'plugins/project-knowledge/scripts/context-hook.cjs');
13
- const normalize = file => { const value = path.resolve(file).replace(/\\/g, '/'); return process.platform === 'win32' ? value.toLowerCase() : value; };
14
- function owned(hook, root) {
15
- if (hook?.type !== 'process' || hook.command !== 'node' || !Array.isArray(hook.args) || hook.args.length !== 3 || typeof hook.args[0] !== 'string' || !path.isAbsolute(hook.args[0]) || hook.args[1] !== '--client' || hook.args[2] !== 'zcode') return false;
16
- const file = normalize(hook.args[0]);
17
- // Only the exact current script or a canonical managed npm installation is
18
- // ours. An unrelated script with a similar basename remains untouched.
19
- return file === normalize(scriptFor(root)) || /\/node_modules\/(?:@sanqianx\/)?project-knowledge\/plugins\/project-knowledge\/scripts\/context-hook\.cjs$/i.test(file);
20
- }
21
- function status({ configFile, rootDir }) {
22
- const config = configuration(configFile);
23
- const entries = (config.hooks?.events?.UserPromptSubmit || []).flatMap(g => Array.isArray(g?.hooks) ? g.hooks.filter(h => owned(h, rootDir)) : []);
24
- const installed = entries.length === 1 && normalize(entries[0].args[0]) === normalize(scriptFor(rootDir)) && fs.existsSync(scriptFor(rootDir));
25
- const disabled = config.hooks?.enabled === false;
26
- return { state: disabled ? 'disabled' : installed && config.hooks?.enabled === true ? 'installed' : entries.length ? 'needs-repair' : 'not-installed', supported: true, installed: installed && !disabled && config.hooks?.enabled === true, detail: disabled ? 'ZCode 配置 Hook 已被用户禁用,未擅自启用。' : installed ? '原生 UserPromptSubmit 只读上下文入口已配置;MCP 插件状态不作已安装承诺。' : entries.length ? '知识读取入口仍指向旧安装或重复配置,请接入/修复;未误报就绪。' : '开发前知识读取尚未接入;与会话采集独立。' };
27
- }
28
- function install({ configFile, rootDir, dryRun }) {
29
- const config = configuration(configFile);
30
- if (config.hooks?.enabled === false) throw new Error('ZCode Hook 已被用户禁用,请在客户端明确启用后重试;未覆盖配置。');
31
- if (!fs.existsSync(scriptFor(rootDir))) throw new Error('缺少已安装的知识读取脚本。');
32
- if (!dryRun) {
33
- if (config.hooks && (typeof config.hooks !== 'object' || Array.isArray(config.hooks))) throw new Error('ZCode Hook 配置格式无效,未覆盖。');
34
- config.hooks ||= {}; config.hooks.events ||= {};
35
- if (typeof config.hooks.events !== 'object' || Array.isArray(config.hooks.events)) throw new Error('ZCode Hook 事件配置格式无效,未覆盖。');
36
- const groups = config.hooks.events.UserPromptSubmit || [];
37
- if (!Array.isArray(groups)) throw new Error('ZCode UserPromptSubmit 配置格式无效,未覆盖。');
38
- const hook = { type: 'process', command: 'node', args: [scriptFor(rootDir), '--client', 'zcode'], timeoutMs: 12000 };
39
- let found = false;
40
- const updated = groups.map(group => {
41
- if (!Array.isArray(group?.hooks)) return group;
42
- const hooks = group.hooks.flatMap(h => { if (!owned(h, rootDir)) return [h]; if (found) return []; found = true; return [hook]; });
43
- return hooks.length || !group.hooks.length ? { ...group, hooks } : null;
44
- }).filter(Boolean);
45
- if (!found) updated.push({ hooks: [hook] });
46
- config.hooks.events.UserPromptSubmit = updated; config.hooks.enabled = true;
47
- AtomicFile.writeJsonAtomic(configFile, config);
48
- }
49
- return { state: dryRun ? 'planned' : 'installed', supported: true, installed: !dryRun, detail: 'ZCode 原生前置读取已配置,保留原模型、第三方 Hook 与采集入口;请重新打开会话。' };
50
- }
51
- function uninstall({ configFile, rootDir, dryRun }) {
52
- const config = configuration(configFile), groups = config.hooks?.events?.UserPromptSubmit;
53
- if (!dryRun && Array.isArray(groups)) {
54
- const kept = groups.map(group => { if (!Array.isArray(group?.hooks)) return group; const hooks = group.hooks.filter(h => !owned(h, rootDir)); return hooks.length ? { ...group, hooks } : null; }).filter(Boolean);
55
- if (kept.length) config.hooks.events.UserPromptSubmit = kept; else delete config.hooks.events.UserPromptSubmit;
56
- AtomicFile.writeJsonAtomic(configFile, config);
57
- }
58
- return { state: dryRun ? 'planned' : 'removed', supported: true, installed: false, detail: '已移除知识上下文入口,采集、模型和第三方设置保留。' };
59
- }
60
- module.exports = { install, status, uninstall };
1
+ 'use strict';
2
+ const fs = require('node:fs');
3
+ const path = require('node:path');
4
+ const AtomicFile = require('./atomic-file');
5
+
6
+ function configuration(file) {
7
+ if (!fs.existsSync(file)) return {};
8
+ let value; try { value = JSON.parse(fs.readFileSync(file, 'utf8')); } catch { throw new Error('ZCode 配置不是有效 JSON,未覆盖原文件。'); }
9
+ if (!value || typeof value !== 'object' || Array.isArray(value)) throw new Error('ZCode 配置必须是 JSON 对象,未覆盖原文件。');
10
+ return value;
11
+ }
12
+ const scriptFor = root => path.join(root, 'plugins/project-knowledge/scripts/context-hook.cjs');
13
+ const normalize = file => { const value = path.resolve(file).replace(/\\/g, '/'); return process.platform === 'win32' ? value.toLowerCase() : value; };
14
+ function owned(hook, root) {
15
+ if (hook?.type !== 'process' || hook.command !== 'node' || !Array.isArray(hook.args) || hook.args.length !== 3 || typeof hook.args[0] !== 'string' || !path.isAbsolute(hook.args[0]) || hook.args[1] !== '--client' || hook.args[2] !== 'zcode') return false;
16
+ const file = normalize(hook.args[0]);
17
+ // Only the exact current script or a canonical managed npm installation is
18
+ // ours. An unrelated script with a similar basename remains untouched.
19
+ return file === normalize(scriptFor(root)) || /\/node_modules\/(?:@sanqianx\/)?project-knowledge\/plugins\/project-knowledge\/scripts\/context-hook\.cjs$/i.test(file);
20
+ }
21
+ function status({ configFile, rootDir }) {
22
+ const config = configuration(configFile);
23
+ const entries = (config.hooks?.events?.UserPromptSubmit || []).flatMap(g => Array.isArray(g?.hooks) ? g.hooks.filter(h => owned(h, rootDir)) : []);
24
+ const installed = entries.length === 1 && normalize(entries[0].args[0]) === normalize(scriptFor(rootDir)) && fs.existsSync(scriptFor(rootDir));
25
+ const disabled = config.hooks?.enabled === false;
26
+ return { state: disabled ? 'disabled' : installed && config.hooks?.enabled === true ? 'installed' : entries.length ? 'needs-repair' : 'not-installed', supported: true, installed: installed && !disabled && config.hooks?.enabled === true, detail: disabled ? 'ZCode 配置 Hook 已被用户禁用,未擅自启用。' : installed ? '原生 UserPromptSubmit 只读上下文入口已配置;MCP 插件状态不作已安装承诺。' : entries.length ? '知识读取入口仍指向旧安装或重复配置,请接入/修复;未误报就绪。' : '开发前知识读取尚未接入;与会话采集独立。' };
27
+ }
28
+ function install({ configFile, rootDir, dryRun }) {
29
+ const config = configuration(configFile);
30
+ if (config.hooks?.enabled === false) throw new Error('ZCode Hook 已被用户禁用,请在客户端明确启用后重试;未覆盖配置。');
31
+ if (!fs.existsSync(scriptFor(rootDir))) throw new Error('缺少已安装的知识读取脚本。');
32
+ if (!dryRun) {
33
+ if (config.hooks && (typeof config.hooks !== 'object' || Array.isArray(config.hooks))) throw new Error('ZCode Hook 配置格式无效,未覆盖。');
34
+ config.hooks ||= {}; config.hooks.events ||= {};
35
+ if (typeof config.hooks.events !== 'object' || Array.isArray(config.hooks.events)) throw new Error('ZCode Hook 事件配置格式无效,未覆盖。');
36
+ const groups = config.hooks.events.UserPromptSubmit || [];
37
+ if (!Array.isArray(groups)) throw new Error('ZCode UserPromptSubmit 配置格式无效,未覆盖。');
38
+ const hook = { type: 'process', command: 'node', args: [scriptFor(rootDir), '--client', 'zcode'], timeoutMs: 12000 };
39
+ let found = false;
40
+ const updated = groups.map(group => {
41
+ if (!Array.isArray(group?.hooks)) return group;
42
+ const hooks = group.hooks.flatMap(h => { if (!owned(h, rootDir)) return [h]; if (found) return []; found = true; return [hook]; });
43
+ return hooks.length || !group.hooks.length ? { ...group, hooks } : null;
44
+ }).filter(Boolean);
45
+ if (!found) updated.push({ hooks: [hook] });
46
+ config.hooks.events.UserPromptSubmit = updated; config.hooks.enabled = true;
47
+ AtomicFile.writeJsonAtomic(configFile, config);
48
+ }
49
+ return { state: dryRun ? 'planned' : 'installed', supported: true, installed: !dryRun, detail: 'ZCode 原生前置读取已配置,保留原模型、第三方 Hook 与采集入口;请重新打开会话。' };
50
+ }
51
+ function uninstall({ configFile, rootDir, dryRun }) {
52
+ const config = configuration(configFile), groups = config.hooks?.events?.UserPromptSubmit;
53
+ if (!dryRun && Array.isArray(groups)) {
54
+ const kept = groups.map(group => { if (!Array.isArray(group?.hooks)) return group; const hooks = group.hooks.filter(h => !owned(h, rootDir)); return hooks.length ? { ...group, hooks } : null; }).filter(Boolean);
55
+ if (kept.length) config.hooks.events.UserPromptSubmit = kept; else delete config.hooks.events.UserPromptSubmit;
56
+ AtomicFile.writeJsonAtomic(configFile, config);
57
+ }
58
+ return { state: dryRun ? 'planned' : 'removed', supported: true, installed: false, detail: '已移除知识上下文入口,采集、模型和第三方设置保留。' };
59
+ }
60
+ module.exports = { install, status, uninstall };
@@ -1,95 +1,95 @@
1
- #!/usr/bin/env node
2
- 'use strict';
3
-
4
- // Codex supports one notify command. This fail-open fan-out preserves an
5
- // existing notifier and invokes the Bridge beside it.
6
- const { spawn } = require('child_process');
7
- const os = require('os');
8
- const path = require('path');
9
-
10
- function commandFrom(args, marker) {
11
- const index = args.indexOf(marker);
12
- if (index < 0 || !args[index + 1]) return null;
13
- try {
14
- const command = JSON.parse(Buffer.from(args[index + 1], 'base64').toString('utf8'));
15
- return Array.isArray(command) && command.length && command.every(part => typeof part === 'string') ? command : null;
16
- } catch {
17
- return null;
18
- }
19
- }
20
-
21
- function readStdin() {
22
- return new Promise(resolve => {
23
- let value = '';
24
- process.stdin.setEncoding('utf8');
25
- process.stdin.on('data', chunk => { value += chunk; });
26
- process.stdin.on('end', () => resolve(value));
27
- process.stdin.on('error', () => resolve(value));
28
- });
29
- }
30
-
31
- function payloadArgument(args) {
32
- const last = args.length - 1;
33
- if (last < 0 || ['--next-base64', '--bridge-base64'].includes(args[last - 1])) return null;
34
- try {
35
- const payload = JSON.parse(args[last]);
36
- return payload && typeof payload === 'object' && !Array.isArray(payload) ? args[last] : null;
37
- } catch {
38
- return null;
39
- }
40
- }
41
-
42
- function bridgePayload(raw) {
43
- try {
44
- const payload = JSON.parse(raw);
45
- if (!payload || typeof payload !== 'object' || Array.isArray(payload)) return raw;
46
- let changed = false;
47
- if (!payload.session_id && !payload.sessionId && typeof payload['thread-id'] === 'string') {
48
- payload.session_id = payload['thread-id'];
49
- changed = true;
50
- }
51
- if (!payload.turn_id && !payload.turnId && typeof payload['turn-id'] === 'string') {
52
- payload.turn_id = payload['turn-id'];
53
- changed = true;
54
- }
55
- // The notification only wakes the authoritative rollout reader. It must
56
- // never substitute its summary fields for captured conversation evidence.
57
- return changed ? JSON.stringify(payload) : raw;
58
- } catch {
59
- return raw;
60
- }
61
- }
62
-
63
- function invoke(command, payload, env, transport = 'stdin') {
64
- if (!command) return;
65
- try {
66
- const [executable, ...args] = command;
67
- const child = spawn(executable, transport === 'argv' ? [...args, payload] : args, {
68
- detached: true,
69
- stdio: [transport === 'argv' ? 'ignore' : 'pipe', 'ignore', 'ignore'],
70
- windowsHide: true,
71
- env,
72
- });
73
- child.on('error', () => {});
74
- if (child.stdin) {
75
- child.stdin.on('error', () => {});
76
- child.stdin.end(payload || '');
77
- }
78
- child.unref();
79
- } catch {
80
- // A notifier must never block Codex itself.
81
- }
82
- }
83
-
84
- (async () => {
85
- const args = process.argv.slice(2);
86
- const argument = payloadArgument(args);
87
- // Codex passes one JSON argument; legacy callers used stdin. Do not wait for
88
- // an open stdin in argv mode, and keep the third-party transport unchanged.
89
- const payload = argument === null ? await readStdin() : argument;
90
- invoke(commandFrom(args, '--next-base64'), payload, process.env, argument === null ? 'stdin' : 'argv');
91
- invoke(commandFrom(args, '--bridge-base64'), bridgePayload(payload), {
92
- ...process.env,
93
- CODEX_SESSIONS_ROOT: process.env.CODEX_SESSIONS_ROOT || path.join(process.env.CODEX_HOME || path.join(os.homedir(), '.codex'), 'sessions'),
94
- });
95
- })();
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+
4
+ // Codex supports one notify command. This fail-open fan-out preserves an
5
+ // existing notifier and invokes the Bridge beside it.
6
+ const { spawn } = require('child_process');
7
+ const os = require('os');
8
+ const path = require('path');
9
+
10
+ function commandFrom(args, marker) {
11
+ const index = args.indexOf(marker);
12
+ if (index < 0 || !args[index + 1]) return null;
13
+ try {
14
+ const command = JSON.parse(Buffer.from(args[index + 1], 'base64').toString('utf8'));
15
+ return Array.isArray(command) && command.length && command.every(part => typeof part === 'string') ? command : null;
16
+ } catch {
17
+ return null;
18
+ }
19
+ }
20
+
21
+ function readStdin() {
22
+ return new Promise(resolve => {
23
+ let value = '';
24
+ process.stdin.setEncoding('utf8');
25
+ process.stdin.on('data', chunk => { value += chunk; });
26
+ process.stdin.on('end', () => resolve(value));
27
+ process.stdin.on('error', () => resolve(value));
28
+ });
29
+ }
30
+
31
+ function payloadArgument(args) {
32
+ const last = args.length - 1;
33
+ if (last < 0 || ['--next-base64', '--bridge-base64'].includes(args[last - 1])) return null;
34
+ try {
35
+ const payload = JSON.parse(args[last]);
36
+ return payload && typeof payload === 'object' && !Array.isArray(payload) ? args[last] : null;
37
+ } catch {
38
+ return null;
39
+ }
40
+ }
41
+
42
+ function bridgePayload(raw) {
43
+ try {
44
+ const payload = JSON.parse(raw);
45
+ if (!payload || typeof payload !== 'object' || Array.isArray(payload)) return raw;
46
+ let changed = false;
47
+ if (!payload.session_id && !payload.sessionId && typeof payload['thread-id'] === 'string') {
48
+ payload.session_id = payload['thread-id'];
49
+ changed = true;
50
+ }
51
+ if (!payload.turn_id && !payload.turnId && typeof payload['turn-id'] === 'string') {
52
+ payload.turn_id = payload['turn-id'];
53
+ changed = true;
54
+ }
55
+ // The notification only wakes the authoritative rollout reader. It must
56
+ // never substitute its summary fields for captured conversation evidence.
57
+ return changed ? JSON.stringify(payload) : raw;
58
+ } catch {
59
+ return raw;
60
+ }
61
+ }
62
+
63
+ function invoke(command, payload, env, transport = 'stdin') {
64
+ if (!command) return;
65
+ try {
66
+ const [executable, ...args] = command;
67
+ const child = spawn(executable, transport === 'argv' ? [...args, payload] : args, {
68
+ detached: true,
69
+ stdio: [transport === 'argv' ? 'ignore' : 'pipe', 'ignore', 'ignore'],
70
+ windowsHide: true,
71
+ env,
72
+ });
73
+ child.on('error', () => {});
74
+ if (child.stdin) {
75
+ child.stdin.on('error', () => {});
76
+ child.stdin.end(payload || '');
77
+ }
78
+ child.unref();
79
+ } catch {
80
+ // A notifier must never block Codex itself.
81
+ }
82
+ }
83
+
84
+ (async () => {
85
+ const args = process.argv.slice(2);
86
+ const argument = payloadArgument(args);
87
+ // Codex passes one JSON argument; legacy callers used stdin. Do not wait for
88
+ // an open stdin in argv mode, and keep the third-party transport unchanged.
89
+ const payload = argument === null ? await readStdin() : argument;
90
+ invoke(commandFrom(args, '--next-base64'), payload, process.env, argument === null ? 'stdin' : 'argv');
91
+ invoke(commandFrom(args, '--bridge-base64'), bridgePayload(payload), {
92
+ ...process.env,
93
+ CODEX_SESSIONS_ROOT: process.env.CODEX_SESSIONS_ROOT || path.join(process.env.CODEX_HOME || path.join(os.homedir(), '.codex'), 'sessions'),
94
+ });
95
+ })();