@sanqianx/project-knowledge 5.0.18 → 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 (209) hide show
  1. package/.agents/plugins/marketplace.json +3 -3
  2. package/.claude-plugin/marketplace.json +1 -1
  3. package/CHANGELOG.md +14 -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-contract.js +4 -3
  9. package/_modules/knowledge-engine/src/analysis-failures.js +39 -0
  10. package/_modules/knowledge-engine/src/analysis-worker.js +6 -2
  11. package/_modules/knowledge-engine/src/coordinator.js +8 -0
  12. package/_modules/knowledge-engine/src/headless-analyzer.js +2 -1
  13. package/_modules/knowledge-engine/src/memories.js +10 -5
  14. package/_modules/knowledge-engine/src/memory-eligibility.js +74 -0
  15. package/_modules/knowledge-engine/src/retrieval-ranking.js +19 -1
  16. package/_modules/knowledge-engine/src/runtime/commit-conversation-binder.js +1 -0
  17. package/_modules/knowledge-engine/src/runtime/commit-prompt.js +12 -9
  18. package/_modules/knowledge-engine/src/runtime/conversation-store.js +2 -0
  19. package/_modules/knowledge-engine/src/server.js +1 -1
  20. package/_site/_test/agent-capture-mcp-ui-test.js +67 -67
  21. package/_site/_test/ai-profile-test.js +75 -75
  22. package/_site/_test/ai-vendor-presets-test.js +91 -91
  23. package/_site/_test/automatic-context-test.js +70 -70
  24. package/_site/_test/bin-cli-test.js +230 -230
  25. package/_site/_test/bridge-adapter-test.js +95 -95
  26. package/_site/_test/bridge-consumer-service-test.js +310 -310
  27. package/_site/_test/candidate-bin-content-test.js +35 -0
  28. package/_site/_test/candidate-product-assets-test.js +13 -0
  29. package/_site/_test/candidate-release-contract-test.js +14 -14
  30. package/_site/_test/capture-hook-dedup-test.js +158 -158
  31. package/_site/_test/claude-executable-discovery-test.js +28 -28
  32. package/_site/_test/claude-session-lifecycle-sweep-test.js +252 -252
  33. package/_site/_test/cli-instance-management-test.js +62 -62
  34. package/_site/_test/client-capture-restart-e2e-test.js +189 -189
  35. package/_site/_test/codex-conversation-projection-test.js +60 -60
  36. package/_site/_test/codex-marketplace-scoped-path-test.js +186 -186
  37. package/_site/_test/codex-notify-capture-e2e-test.js +101 -101
  38. package/_site/_test/codex-notify-fanout-test.js +134 -134
  39. package/_site/_test/codex-notify-upgrade-test.js +253 -253
  40. package/_site/_test/commit-boundary-freeze-test.js +147 -147
  41. package/_site/_test/commit-conversation-binding-test.js +4 -4
  42. package/_site/_test/configuration-generation-test.js +22 -22
  43. package/_site/_test/context-pack-test.js +200 -200
  44. package/_site/_test/conversation-api-test.js +10 -10
  45. package/_site/_test/conversation-origin-test.js +15 -0
  46. package/_site/_test/cross-repo-isolation-e2e-test.js +187 -187
  47. package/_site/_test/embedding-config-test.js +32 -32
  48. package/_site/_test/fixtures/fake-browser-exit.js +10 -10
  49. package/_site/_test/fixtures/fake-browser-hang.js +10 -10
  50. package/_site/_test/fixtures/integration-busy-worker.cjs +6 -6
  51. package/_site/_test/fixtures/make-git-repos.js +92 -92
  52. package/_site/_test/fixtures/retrieval-gold-queries.json +32 -0
  53. package/_site/_test/fresh-hook-reimport-test.js +53 -53
  54. package/_site/_test/fresh-product-data-test.js +56 -56
  55. package/_site/_test/git-path-compatibility-test.js +80 -80
  56. package/_site/_test/git-validation-test.js +81 -81
  57. package/_site/_test/hook-worktree-test.js +42 -42
  58. package/_site/_test/index-builder-test.js +41 -41
  59. package/_site/_test/index-error-display-contract-test.js +10 -10
  60. package/_site/_test/integration-adapters-test.js +67 -67
  61. package/_site/_test/integration-capture-setup-test.js +118 -118
  62. package/_site/_test/integration-runtime-binding-a2-test.js +566 -566
  63. package/_site/_test/integration-runtime-binding-a3-test.js +227 -227
  64. package/_site/_test/integration-runtime-binding-test.js +473 -473
  65. package/_site/_test/integration-status-scan-test.js +29 -29
  66. package/_site/_test/kb-framework-test.js +57 -57
  67. package/_site/_test/kbpath-follow-test.js +63 -63
  68. package/_site/_test/knowledge-db-test.js +144 -144
  69. package/_site/_test/knowledge-engine-client-test.js +109 -109
  70. package/_site/_test/knowledge-maintenance-test.js +40 -40
  71. package/_site/_test/knowledge-panel-ui-contract-test.js +112 -0
  72. package/_site/_test/knowledge-scopes-test.js +37 -37
  73. package/_site/_test/knowledge-storage-location-test.js +28 -28
  74. package/_site/_test/launcher-readiness-test.js +80 -80
  75. package/_site/_test/legacy-claude-session-alias-test.js +106 -106
  76. package/_site/_test/legacy-routes-removed-test.js +47 -47
  77. package/_site/_test/legacy-workbench-exclusion-test.js +92 -92
  78. package/_site/_test/live-gateway-test.js +48 -48
  79. package/_site/_test/local-marketplace-test.js +16 -16
  80. package/_site/_test/log-redaction-test.js +58 -58
  81. package/_site/_test/markdown-maintenance-test.js +11 -11
  82. package/_site/_test/model-context-windows-test.js +89 -89
  83. package/_site/_test/module-agent-start-proxy-test.js +65 -65
  84. package/_site/_test/module-artifact-integrity-test.js +27 -27
  85. package/_site/_test/module-boundary-test.js +30 -30
  86. package/_site/_test/module-bridge-eventbridge-test.js +202 -202
  87. package/_site/_test/module-dynamic-port-test.js +118 -118
  88. package/_site/_test/module-index-proxy-test.js +33 -0
  89. package/_site/_test/module-model-configuration-test.js +44 -44
  90. package/_site/_test/module-process-lifecycle-test.js +35 -35
  91. package/_site/_test/module-stream-proxy-test.js +34 -34
  92. package/_site/_test/module-terminal-restart-test.js +63 -0
  93. package/_site/_test/module-wiring-unbind-race-test.js +59 -59
  94. package/_site/_test/native-context-integrations-test.js +86 -86
  95. package/_site/_test/orphaned-claim-recovery-test.js +80 -80
  96. package/_site/_test/package-boundary-test.js +43 -42
  97. package/_site/_test/package-startup-test.js +56 -56
  98. package/_site/_test/packaged-ui-smoke-test.js +88 -88
  99. package/_site/_test/pr-consumer-contract-test.js +55 -55
  100. package/_site/_test/product-brand-contract-test.js +25 -25
  101. package/_site/_test/product-diagnostics-ui-test.js +93 -93
  102. package/_site/_test/product-import-ui-test.js +104 -104
  103. package/_site/_test/product-progress-status-test.js +12 -1
  104. package/_site/_test/project-remove-running-guard-test.js +79 -79
  105. package/_site/_test/project-store-test.js +90 -90
  106. package/_site/_test/registry-events-test.js +38 -0
  107. package/_site/_test/release-version-sync-test.js +115 -115
  108. package/_site/_test/requirement-binding-test.js +113 -113
  109. package/_site/_test/runtime-endpoint-test.js +64 -64
  110. package/_site/_test/scanner-test.js +83 -83
  111. package/_site/_test/server-security-test.js +96 -96
  112. package/_site/_test/sessions-stream-test.js +93 -93
  113. package/_site/_test/shared-scope-root-binding-test.js +67 -67
  114. package/_site/_test/simple-import-test.js +98 -98
  115. package/_site/_test/startup-analysis-disabled-test.js +6 -6
  116. package/_site/_test/team-knowledge-store-sparse-checkout-test.js +196 -196
  117. package/_site/_test/team-knowledge-store-test.js +132 -132
  118. package/_site/_test/test-server-cleanup-test.js +35 -35
  119. package/_site/_test/test-server-module-isolation-test.js +37 -37
  120. package/_site/_test/tracking-start-test.js +35 -35
  121. package/_site/_test/workbench-context-receipts-test.js +22 -0
  122. package/_site/_test/workspace-ui-contract-test.js +31 -31
  123. package/_site/_test/zcode-capture-integration-test.js +108 -108
  124. package/_site/lib/ai-vendor-presets.js +83 -83
  125. package/_site/lib/ai-workspace.js +3 -3
  126. package/_site/lib/atomic-file.js +204 -204
  127. package/_site/lib/bridge-adapter.js +260 -260
  128. package/_site/lib/capture-hook-config.js +108 -108
  129. package/_site/lib/claude-cli-runner.js +89 -89
  130. package/_site/lib/cli-instances.js +43 -43
  131. package/_site/lib/codex-context-status.js +17 -17
  132. package/_site/lib/codex-notify-config.js +193 -193
  133. package/_site/lib/commit-conversation-binder.js +13 -12
  134. package/_site/lib/commit-reconciler.js +38 -38
  135. package/_site/lib/context-pack-builder.js +299 -299
  136. package/_site/lib/conversation-exclusions.js +204 -204
  137. package/_site/lib/conversation-query-service.js +17 -17
  138. package/_site/lib/conversation-store.js +2 -0
  139. package/_site/lib/data-dir.js +13 -13
  140. package/_site/lib/embedding-service.js +85 -85
  141. package/_site/lib/engine-connection.js +93 -90
  142. package/_site/lib/engine-knowledge-client.js +72 -72
  143. package/_site/lib/folder-picker-output.js +3 -3
  144. package/_site/lib/git-path.js +14 -14
  145. package/_site/lib/github-team-store.js +3 -3
  146. package/_site/lib/index-builder.js +157 -157
  147. package/_site/lib/integration-status-scan.js +99 -99
  148. package/_site/lib/integration-status-worker.js +14 -14
  149. package/_site/lib/kb-framework.js +15 -15
  150. package/_site/lib/knowledge-db.js +363 -363
  151. package/_site/lib/knowledge-schema.js +121 -121
  152. package/_site/lib/live-gateway.js +78 -76
  153. package/_site/lib/llm-client.js +12 -12
  154. package/_site/lib/model-context-windows.js +82 -82
  155. package/_site/lib/module-bridge.js +874 -865
  156. package/_site/lib/module-port-resolver.js +57 -57
  157. package/_site/lib/post-commit-automation.js +54 -54
  158. package/_site/lib/product-data.js +35 -35
  159. package/_site/lib/project-lifecycle-service.js +14 -14
  160. package/_site/lib/project-registry-store.js +129 -129
  161. package/_site/lib/prompt-registry.js +148 -148
  162. package/_site/lib/registry-events.js +16 -0
  163. package/_site/lib/requirement-adapters.js +10 -10
  164. package/_site/lib/requirement-binder.js +112 -112
  165. package/_site/lib/runtime-control.js +89 -89
  166. package/_site/lib/runtime-endpoint.js +5 -5
  167. package/_site/lib/server-app.js +10 -1
  168. package/_site/lib/zcode-context-integration.js +60 -60
  169. package/_site/scripts/codex-notify-fanout.cjs +95 -95
  170. package/_site/scripts/install-module-candidates.js +69 -69
  171. package/_site/scripts/pack-module-candidates.js +89 -72
  172. package/_site/scripts/sync-product-content.js +28 -28
  173. package/_site/scripts/sync-release-version.js +164 -164
  174. package/_site/scripts/vendor-modules.js +49 -49
  175. package/_site/scripts/verify-module-runtime.js +50 -50
  176. package/_site/scripts/verify-product-runtime.js +36 -36
  177. package/_site/scripts/verify-retrieval-gold.js +83 -0
  178. package/_site/server.js +15 -15
  179. package/_site/start.bat +26 -26
  180. package/_site/stop.bat +11 -11
  181. package/bin/project-knowledge-integrations.js +254 -254
  182. package/bin/project-knowledge-kb.js +67 -67
  183. package/bin/project-knowledge-mcp.js +8 -8
  184. package/bin/project-knowledge.js +170 -170
  185. package/claude-prompts.json +21 -21
  186. package/docs/README.zh-CN.md +205 -205
  187. package/docs/assets/architecture.svg +181 -181
  188. package/docs/assets/logo.svg +49 -49
  189. package/docs/fresh-v5-start.md +42 -42
  190. package/docs/leader-glm-workbench-playbook.md +122 -119
  191. package/docs/project-registry-schema.md +11 -11
  192. package/docs/release-5.0.16-acceptance.md +176 -176
  193. package/docs/release-5.0.17-acceptance.md +117 -117
  194. package/docs/release-5.0.19-acceptance.md +39 -0
  195. package/docs/team-knowledge-mode-a-plan.md +73 -73
  196. package/module-runtime-manifest.json +126 -104
  197. package/package.json +110 -108
  198. package/plugins/project-knowledge/.claude-plugin/plugin.json +1 -1
  199. package/plugins/project-knowledge/.codex-plugin/plugin.json +1 -1
  200. package/plugins/project-knowledge/.mcp.json +1 -1
  201. package/plugins/project-knowledge/opencode/project-knowledge.md +3 -3
  202. package/plugins/project-knowledge/skills/project-knowledge/SKILL.md +28 -28
  203. package/templates/change.md +30 -30
  204. package/templates/project-readme.md +18 -18
  205. package/ui/index.html +1 -1
  206. package/ui/knowledge-panel.html +118 -0
  207. package/ui/product.css +8 -0
  208. package/ui/product.js +175 -8
  209. package/vendor-manifest.json +55 -53
@@ -1,69 +1,69 @@
1
- 'use strict';
2
- const fs = require('node:fs');
3
- const os = require('node:os');
4
- const path = require('node:path');
5
- const crypto = require('node:crypto');
6
- const { createRequire } = require('node:module');
7
- const { spawnSync } = require('node:child_process');
8
- const AtomicFile = require('../lib/atomic-file');
9
- const { verifyInstalled, safeRelative } = require('./pack-module-candidates');
10
- const root = path.resolve(__dirname, '../..');
11
- const npmCli = process.env.npm_execpath || path.join(path.dirname(process.execPath), 'node_modules/npm/bin/npm-cli.js');
12
- function run(args, cwd) {
13
- const result = spawnSync(process.execPath, [npmCli, ...args], { cwd, windowsHide: true, encoding: 'utf8', maxBuffer: 16 * 1024 * 1024 });
14
- if (result.status !== 0) throw new Error(`npm ${args[0]} failed (${result.status})`);
15
- return result.stdout;
16
- }
17
- function contentAddressedArtifact(packedCore) {
18
- const sha256 = crypto.createHash('sha256').update(fs.readFileSync(packedCore)).digest('hex');
19
- const file = packedCore.replace(/\.tgz$/, `-${sha256}.tgz`);
20
- if (file === packedCore) throw new Error('Candidate must be a tarball');
21
- fs.copyFileSync(packedCore, file);
22
- return { file, sha256 };
23
- }
24
- function main() {
25
- const manifestFile = path.resolve(process.argv[2]);
26
- const manifest = JSON.parse(fs.readFileSync(manifestFile));
27
- if (manifest.schema !== 'project-knowledge/module-candidates/v1') throw new Error('Invalid candidate manifest');
28
- const artifactRoot = path.dirname(manifestFile);
29
- const dependencies = {};
30
- for (const module of Object.values(manifest.modules)) {
31
- if (!safeRelative(module.tarball) || !module.tarball.endsWith('.tgz')) throw new Error('Unsafe artifact path');
32
- const file = path.join(artifactRoot, module.tarball);
33
- if (fs.lstatSync(file).isSymbolicLink() || crypto.createHash('sha256').update(fs.readFileSync(file)).digest('hex') !== module.sha256) throw new Error('Module artifact drift');
34
- dependencies[module.installName] = `file:${file}`;
35
- }
36
- const candidate = JSON.parse(run(['pack', '--ignore-scripts', '--json', '--pack-destination', artifactRoot], root))[0];
37
- const packedCore = path.join(artifactRoot, candidate.filename);
38
- // Same-version local tarballs are cached by npm. Address each candidate by
39
- // content so an update cannot silently keep an older runtime or UI.
40
- const { sha256: shellSha256, file: shellArtifact } = contentAddressedArtifact(packedCore);
41
- dependencies['@sanqianx/project-knowledge'] = `file:${shellArtifact}`;
42
- const updateAt = process.argv.indexOf('--update');
43
- const installedRoot = updateAt < 0 ? fs.mkdtempSync(path.join(os.tmpdir(), 'kb-installed-product-')) : path.resolve(process.argv[updateAt + 1]);
44
- if (path.dirname(installedRoot) !== path.resolve(os.tmpdir()) || !path.basename(installedRoot).startsWith('kb-installed-product-') || fs.lstatSync(installedRoot).isSymbolicLink()) throw new Error('Only this task’s isolated installation can be updated');
45
- if (updateAt >= 0 && JSON.parse(fs.readFileSync(path.join(installedRoot, 'installation-acceptance.json'))).installedRoot !== installedRoot) throw new Error('Installed fixture identity mismatch');
46
- AtomicFile.writeJsonAtomic(path.join(installedRoot, 'package.json'), { name: 'kb-local-acceptance', version: '0.0.0', private: true, dependencies });
47
- process.stdout.write(`Installing isolated product at ${installedRoot}\n`);
48
- run(['install', '--omit=dev', '--ignore-scripts', '--no-audit', '--no-fund'], installedRoot);
49
- verifyInstalled(manifest, installedRoot);
50
- const core = path.join(installedRoot, 'node_modules/@sanqianx/project-knowledge');
51
- const resolver = createRequire(path.join(core, 'package.json'));
52
- for (const module of Object.values(manifest.modules)) {
53
- const actual = resolver.resolve(`${module.installName}/package.json`);
54
- if (actual !== path.join(installedRoot, 'node_modules', module.installName, 'package.json')) throw new Error(`A nested stale module shadows the tested candidate: ${module.installName}`);
55
- }
56
- const verify = spawnSync(process.execPath, [path.join(core, '_site/scripts/verify-module-runtime.js'), '--working-tree'], { cwd: installedRoot, windowsHide: true, encoding: 'utf8' });
57
- if (verify.status !== 0) throw new Error('Packaged engine integrity failed');
58
- require('./verify-product-runtime').verifyProductRuntime(core);
59
- for (const relative of ['vendor-manifest.json', 'module-runtime-manifest.json']) {
60
- if (!fs.readFileSync(path.join(core, relative)).equals(fs.readFileSync(path.join(root, relative)))) throw new Error(`Installed candidate is stale: ${relative}`);
61
- }
62
- const report = { schema: 'project-knowledge/installed-candidates/v1', mode: 'local npm candidate installation, not remote release', installedRoot, core, artifactManifest: manifestFile, shellVersion: candidate.version, shellSha256, shellArtifact, modules: Object.fromEntries(Object.entries(manifest.modules).map(([key, module]) => [key, { version: module.version, sha256: module.sha256 }])), integrityPassed: true, testedAt: new Date().toISOString() };
63
- AtomicFile.writeJsonAtomic(path.join(installedRoot, 'installation-acceptance.json'), report);
64
- process.stdout.write(`INSTALLED_PRODUCT ${path.join(installedRoot, 'installation-acceptance.json')}\n`);
65
- }
66
- if (require.main === module) {
67
- try { main(); } catch (error) { process.stderr.write(`${error.message}\n`); process.exitCode = 1; }
68
- }
69
- module.exports = { contentAddressedArtifact };
1
+ 'use strict';
2
+ const fs = require('node:fs');
3
+ const os = require('node:os');
4
+ const path = require('node:path');
5
+ const crypto = require('node:crypto');
6
+ const { createRequire } = require('node:module');
7
+ const { spawnSync } = require('node:child_process');
8
+ const AtomicFile = require('../lib/atomic-file');
9
+ const { verifyInstalled, safeRelative } = require('./pack-module-candidates');
10
+ const root = path.resolve(__dirname, '../..');
11
+ const npmCli = process.env.npm_execpath || path.join(path.dirname(process.execPath), 'node_modules/npm/bin/npm-cli.js');
12
+ function run(args, cwd) {
13
+ const result = spawnSync(process.execPath, [npmCli, ...args], { cwd, windowsHide: true, encoding: 'utf8', maxBuffer: 16 * 1024 * 1024 });
14
+ if (result.status !== 0) throw new Error(`npm ${args[0]} failed (${result.status})`);
15
+ return result.stdout;
16
+ }
17
+ function contentAddressedArtifact(packedCore) {
18
+ const sha256 = crypto.createHash('sha256').update(fs.readFileSync(packedCore)).digest('hex');
19
+ const file = packedCore.replace(/\.tgz$/, `-${sha256}.tgz`);
20
+ if (file === packedCore) throw new Error('Candidate must be a tarball');
21
+ fs.copyFileSync(packedCore, file);
22
+ return { file, sha256 };
23
+ }
24
+ function main() {
25
+ const manifestFile = path.resolve(process.argv[2]);
26
+ const manifest = JSON.parse(fs.readFileSync(manifestFile));
27
+ if (manifest.schema !== 'project-knowledge/module-candidates/v1') throw new Error('Invalid candidate manifest');
28
+ const artifactRoot = path.dirname(manifestFile);
29
+ const dependencies = {};
30
+ for (const module of Object.values(manifest.modules)) {
31
+ if (!safeRelative(module.tarball) || !module.tarball.endsWith('.tgz')) throw new Error('Unsafe artifact path');
32
+ const file = path.join(artifactRoot, module.tarball);
33
+ if (fs.lstatSync(file).isSymbolicLink() || crypto.createHash('sha256').update(fs.readFileSync(file)).digest('hex') !== module.sha256) throw new Error('Module artifact drift');
34
+ dependencies[module.installName] = `file:${file}`;
35
+ }
36
+ const candidate = JSON.parse(run(['pack', '--ignore-scripts', '--json', '--pack-destination', artifactRoot], root))[0];
37
+ const packedCore = path.join(artifactRoot, candidate.filename);
38
+ // Same-version local tarballs are cached by npm. Address each candidate by
39
+ // content so an update cannot silently keep an older runtime or UI.
40
+ const { sha256: shellSha256, file: shellArtifact } = contentAddressedArtifact(packedCore);
41
+ dependencies['@sanqianx/project-knowledge'] = `file:${shellArtifact}`;
42
+ const updateAt = process.argv.indexOf('--update');
43
+ const installedRoot = updateAt < 0 ? fs.mkdtempSync(path.join(os.tmpdir(), 'kb-installed-product-')) : path.resolve(process.argv[updateAt + 1]);
44
+ if (path.dirname(installedRoot) !== path.resolve(os.tmpdir()) || !path.basename(installedRoot).startsWith('kb-installed-product-') || fs.lstatSync(installedRoot).isSymbolicLink()) throw new Error('Only this task’s isolated installation can be updated');
45
+ if (updateAt >= 0 && JSON.parse(fs.readFileSync(path.join(installedRoot, 'installation-acceptance.json'))).installedRoot !== installedRoot) throw new Error('Installed fixture identity mismatch');
46
+ AtomicFile.writeJsonAtomic(path.join(installedRoot, 'package.json'), { name: 'kb-local-acceptance', version: '0.0.0', private: true, dependencies });
47
+ process.stdout.write(`Installing isolated product at ${installedRoot}\n`);
48
+ run(['install', '--omit=dev', '--ignore-scripts', '--no-audit', '--no-fund'], installedRoot);
49
+ verifyInstalled(manifest, installedRoot);
50
+ const core = path.join(installedRoot, 'node_modules/@sanqianx/project-knowledge');
51
+ const resolver = createRequire(path.join(core, 'package.json'));
52
+ for (const module of Object.values(manifest.modules)) {
53
+ const actual = resolver.resolve(`${module.installName}/package.json`);
54
+ if (actual !== path.join(installedRoot, 'node_modules', module.installName, 'package.json')) throw new Error(`A nested stale module shadows the tested candidate: ${module.installName}`);
55
+ }
56
+ const verify = spawnSync(process.execPath, [path.join(core, '_site/scripts/verify-module-runtime.js'), '--working-tree'], { cwd: installedRoot, windowsHide: true, encoding: 'utf8' });
57
+ if (verify.status !== 0) throw new Error('Packaged engine integrity failed');
58
+ require('./verify-product-runtime').verifyProductRuntime(core);
59
+ for (const relative of ['vendor-manifest.json', 'module-runtime-manifest.json']) {
60
+ if (!fs.readFileSync(path.join(core, relative)).equals(fs.readFileSync(path.join(root, relative)))) throw new Error(`Installed candidate is stale: ${relative}`);
61
+ }
62
+ const report = { schema: 'project-knowledge/installed-candidates/v1', mode: 'local npm candidate installation, not remote release', installedRoot, core, artifactManifest: manifestFile, shellVersion: candidate.version, shellSha256, shellArtifact, modules: Object.fromEntries(Object.entries(manifest.modules).map(([key, module]) => [key, { version: module.version, sha256: module.sha256 }])), integrityPassed: true, testedAt: new Date().toISOString() };
63
+ AtomicFile.writeJsonAtomic(path.join(installedRoot, 'installation-acceptance.json'), report);
64
+ process.stdout.write(`INSTALLED_PRODUCT ${path.join(installedRoot, 'installation-acceptance.json')}\n`);
65
+ }
66
+ if (require.main === module) {
67
+ try { main(); } catch (error) { process.stderr.write(`${error.message}\n`); process.exitCode = 1; }
68
+ }
69
+ module.exports = { contentAddressedArtifact };
@@ -1,72 +1,89 @@
1
- 'use strict';
2
- const fs = require('node:fs');
3
- const path = require('node:path');
4
- const os = require('node:os');
5
- const crypto = require('node:crypto');
6
- const { spawnSync } = require('node:child_process');
7
- const AtomicFile = require('../lib/atomic-file');
8
- const hash = bytes => crypto.createHash('sha256').update(bytes).digest('hex');
9
- const root = path.resolve(__dirname, '../..');
10
- const npmCli = process.env.npm_execpath || path.join(path.dirname(process.execPath), 'node_modules/npm/bin/npm-cli.js');
11
- const definitions = {
12
- terminal: { option: '--terminal-source', name: '@sanqianx/claude-ai-workbench', installName: 'claude-ai-workbench', required: ['apps/agent-terminal/app.js', 'apps/agent-terminal/app.css', 'packages/server/lib/knowledge-context.js'] },
13
- vectorHub: { option: '--vector-source', name: '@sanqianx/vector-hub', installName: '@sanqianx/vector-hub', required: ['dist/cjs/bin.js', 'dist/cjs/server/manager.js', 'ui/index.html'] },
14
- eventBridge: { option: '--bridge-source', name: '@sanqianx/ai-coding-event-bridge-console', installName: '@sanqianx/ai-coding-event-bridge-console', required: ['src/server.js', 'src/bridge/core/consumer-registry.js', 'console/index.html'] },
15
- };
16
- function run(command, args, cwd) {
17
- const result = spawnSync(command, args, { cwd, windowsHide: true, encoding: 'utf8', maxBuffer: 16 * 1024 * 1024 });
18
- if (result.status !== 0) throw new Error(`${path.basename(command)} failed (${result.status}): ${result.error?.message || result.stderr}`);
19
- return result.stdout;
20
- }
21
- function safeRelative(file) { return typeof file === 'string' && !path.isAbsolute(file) && !/[\\\x00-\x1f]/.test(file) && !file.split('/').some(p => p === '..' || p === '.'); }
22
- function verifyInstalled(manifest, installedRoot) {
23
- for (const [key, module] of Object.entries(manifest.modules)) {
24
- const directory = path.join(installedRoot, 'node_modules', module.installName);
25
- const pkg = JSON.parse(fs.readFileSync(path.join(directory, 'package.json')));
26
- if (pkg.name !== module.name || pkg.version !== module.version) throw new Error(`Installed module identity mismatch: ${key}`);
27
- for (const [relative, expected] of Object.entries(module.files)) {
28
- if (!safeRelative(relative)) throw new Error('Unsafe module manifest path');
29
- const file = path.join(directory, relative);
30
- if (fs.lstatSync(file).isSymbolicLink() || hash(fs.readFileSync(file)) !== expected) throw new Error(`Installed module is stale or altered: ${key}/${relative}`);
31
- }
32
- }
33
- }
34
- function pack(sources) {
35
- const output = fs.mkdtempSync(path.join(os.tmpdir(), 'kb-module-candidates-'));
36
- const manifest = { schema: 'project-knowledge/module-candidates/v1', createdAt: new Date().toISOString(), modules: {} };
37
- for (const [key, definition] of Object.entries(definitions)) {
38
- const source = path.resolve(sources[key]);
39
- const pkg = JSON.parse(fs.readFileSync(path.join(source, 'package.json')));
40
- if (pkg.name !== definition.name) throw new Error(`Unexpected source identity: ${key}`);
41
- const expectedVersion = JSON.parse(fs.readFileSync(path.join(root, 'package.json'))).dependencies[definition.installName];
42
- if (expectedVersion !== pkg.version && expectedVersion !== `npm:${pkg.name}@${pkg.version}`) throw new Error(`Source version is not pinned by the shell: ${key}`);
43
- const result = JSON.parse(run(process.execPath, [npmCli, 'pack', '--ignore-scripts', '--json', '--pack-destination', output], source))[0];
44
- const files = {};
45
- for (const entry of result.files) {
46
- if (!safeRelative(entry.path)) throw new Error('Unsafe npm package path');
47
- const file = path.join(source, entry.path);
48
- if (fs.lstatSync(file).isSymbolicLink()) throw new Error(`Symlink in module package: ${key}/${entry.path}`);
49
- files[entry.path] = hash(fs.readFileSync(file));
50
- }
51
- if (definition.required.some(file => !files[file])) throw new Error(`Incomplete module runtime: ${key}`);
52
- manifest.modules[key] = { name: pkg.name, installName: definition.installName, version: pkg.version, tarball: result.filename, sha256: hash(fs.readFileSync(path.join(output, result.filename))), files };
53
- process.stdout.write(`Packed ${key} ${pkg.version}: ${result.files.length} files\n`);
54
- }
55
- const manifestPath = path.join(output, 'module-candidates.json'); AtomicFile.writeJsonAtomic(manifestPath, manifest);
56
- const productFiles = Object.fromEntries(['ui/index.html', 'ui/product.js', 'ui/live-client.js', 'ui/product.css', 'ui/favicon.svg'].map(file => [file, hash(fs.readFileSync(path.join(root, file)))]));
57
- AtomicFile.writeJsonAtomic(path.join(root, 'module-runtime-manifest.json'), { schema: 'project-knowledge/module-content/v1', productFiles, modules: Object.fromEntries(Object.entries(manifest.modules).map(([key, module]) => [key, { name: module.name, installName: module.installName, version: module.version, files: module.files }])) });
58
- process.stdout.write(`MODULE_CANDIDATES ${manifestPath}\n`);
59
- return manifestPath;
60
- }
61
- if (require.main === module) {
62
- try {
63
- const at = process.argv.indexOf('--verify-installed');
64
- if (at >= 0) { const manifest = JSON.parse(fs.readFileSync(process.argv[at + 1])); verifyInstalled(manifest, path.resolve(process.argv[at + 2])); process.stdout.write('Installed module bytes verified against candidate artifacts.\n'); }
65
- else {
66
- const sources = {};
67
- for (const [key, d] of Object.entries(definitions)) { const i = process.argv.indexOf(d.option); if (i < 0 || !process.argv[i + 1]) throw new Error(`Explicit source required: ${d.option}`); sources[key] = process.argv[i + 1]; }
68
- pack(sources);
69
- }
70
- } catch (error) { process.stderr.write(`${error.message}\n`); process.exitCode = 1; }
71
- }
72
- module.exports = { verifyInstalled, safeRelative };
1
+ 'use strict';
2
+ const fs = require('node:fs');
3
+ const path = require('node:path');
4
+ const os = require('node:os');
5
+ const crypto = require('node:crypto');
6
+ const { spawnSync } = require('node:child_process');
7
+ const AtomicFile = require('../lib/atomic-file');
8
+ const hash = bytes => crypto.createHash('sha256').update(bytes).digest('hex');
9
+ const root = path.resolve(__dirname, '../..');
10
+ const npmCli = process.env.npm_execpath || path.join(path.dirname(process.execPath), 'node_modules/npm/bin/npm-cli.js');
11
+ const definitions = {
12
+ terminal: { option: '--terminal-source', name: '@sanqianx/claude-ai-workbench', installName: 'claude-ai-workbench', required: ['apps/agent-terminal/app.js', 'apps/agent-terminal/app.css', 'packages/server/lib/knowledge-context.js'] },
13
+ vectorHub: { option: '--vector-source', name: '@sanqianx/vector-hub', installName: '@sanqianx/vector-hub', required: ['dist/cjs/bin.js', 'dist/cjs/server/manager.js', 'ui/index.html'] },
14
+ eventBridge: { option: '--bridge-source', name: '@sanqianx/ai-coding-event-bridge-console', installName: '@sanqianx/ai-coding-event-bridge-console', required: ['src/server.js', 'src/bridge/core/consumer-registry.js', 'console/index.html'] },
15
+ };
16
+ function run(command, args, cwd) {
17
+ const result = spawnSync(command, args, { cwd, windowsHide: true, encoding: 'utf8', maxBuffer: 16 * 1024 * 1024 });
18
+ if (result.status !== 0) throw new Error(`${path.basename(command)} failed (${result.status}): ${result.error?.message || result.stderr}`);
19
+ return result.stdout;
20
+ }
21
+ function safeRelative(file) { return typeof file === 'string' && !path.isAbsolute(file) && !/[\\\x00-\x1f]/.test(file) && !file.split('/').some(p => p === '..' || p === '.'); }
22
+ function installedFileBytes(pkg, relative, bytes) {
23
+ const bins = typeof pkg.bin === 'string' ? [pkg.bin] : Object.values(pkg.bin || {});
24
+ const isBin = bins.some(file => String(file).replace(/\\/g, '/').replace(/^\.\//, '') === relative);
25
+ // npm bin-links removes CRLF from the shebang only, on every platform.
26
+ // Hash the installed bytes; keep body line endings and ordinary files exact.
27
+ if (isBin && /^#![^\n]+\r\n/.test(bytes.subarray(0, 2048).toString('utf8'))) return Buffer.from(bytes.toString('utf8').replace(/^(#![^\n]+)\r\n/, '$1\n'));
28
+ return bytes;
29
+ }
30
+ function productFiles(directory) {
31
+ const previous = JSON.parse(fs.readFileSync(path.join(directory, 'module-runtime-manifest.json'), 'utf8'));
32
+ const files = new Set(['ui/index.html', 'ui/knowledge-panel.html', 'ui/product.js', 'ui/live-client.js', 'ui/product.css', 'ui/favicon.svg', ...Object.keys(previous.productFiles || {})]);
33
+ return Object.fromEntries([...files].map(file => {
34
+ if (!safeRelative(file)) throw new Error('Unsafe product asset path');
35
+ const target = path.join(directory, file);
36
+ if (fs.lstatSync(target).isSymbolicLink()) throw new Error('Symlink in product assets');
37
+ return [file, hash(fs.readFileSync(target))];
38
+ }));
39
+ }
40
+ function verifyInstalled(manifest, installedRoot) {
41
+ for (const [key, module] of Object.entries(manifest.modules)) {
42
+ const directory = path.join(installedRoot, 'node_modules', module.installName);
43
+ const pkg = JSON.parse(fs.readFileSync(path.join(directory, 'package.json')));
44
+ if (pkg.name !== module.name || pkg.version !== module.version) throw new Error(`Installed module identity mismatch: ${key}`);
45
+ for (const [relative, expected] of Object.entries(module.files)) {
46
+ if (!safeRelative(relative)) throw new Error('Unsafe module manifest path');
47
+ const file = path.join(directory, relative);
48
+ if (fs.lstatSync(file).isSymbolicLink() || hash(fs.readFileSync(file)) !== expected) throw new Error(`Installed module is stale or altered: ${key}/${relative}`);
49
+ }
50
+ }
51
+ }
52
+ function pack(sources) {
53
+ const output = fs.mkdtempSync(path.join(os.tmpdir(), 'kb-module-candidates-'));
54
+ const manifest = { schema: 'project-knowledge/module-candidates/v1', createdAt: new Date().toISOString(), modules: {} };
55
+ for (const [key, definition] of Object.entries(definitions)) {
56
+ const source = path.resolve(sources[key]);
57
+ const pkg = JSON.parse(fs.readFileSync(path.join(source, 'package.json')));
58
+ if (pkg.name !== definition.name) throw new Error(`Unexpected source identity: ${key}`);
59
+ const expectedVersion = JSON.parse(fs.readFileSync(path.join(root, 'package.json'))).dependencies[definition.installName];
60
+ if (expectedVersion !== pkg.version && expectedVersion !== `npm:${pkg.name}@${pkg.version}`) throw new Error(`Source version is not pinned by the shell: ${key}`);
61
+ const result = JSON.parse(run(process.execPath, [npmCli, 'pack', '--ignore-scripts', '--json', '--pack-destination', output], source))[0];
62
+ const files = {};
63
+ for (const entry of result.files) {
64
+ if (!safeRelative(entry.path)) throw new Error('Unsafe npm package path');
65
+ const file = path.join(source, entry.path);
66
+ if (fs.lstatSync(file).isSymbolicLink()) throw new Error(`Symlink in module package: ${key}/${entry.path}`);
67
+ files[entry.path] = hash(installedFileBytes(pkg, entry.path, fs.readFileSync(file)));
68
+ }
69
+ if (definition.required.some(file => !files[file])) throw new Error(`Incomplete module runtime: ${key}`);
70
+ manifest.modules[key] = { name: pkg.name, installName: definition.installName, version: pkg.version, tarball: result.filename, sha256: hash(fs.readFileSync(path.join(output, result.filename))), files };
71
+ process.stdout.write(`Packed ${key} ${pkg.version}: ${result.files.length} files\n`);
72
+ }
73
+ const manifestPath = path.join(output, 'module-candidates.json'); AtomicFile.writeJsonAtomic(manifestPath, manifest);
74
+ AtomicFile.writeJsonAtomic(path.join(root, 'module-runtime-manifest.json'), { schema: 'project-knowledge/module-content/v1', productFiles: productFiles(root), modules: Object.fromEntries(Object.entries(manifest.modules).map(([key, module]) => [key, { name: module.name, installName: module.installName, version: module.version, files: module.files }])) });
75
+ process.stdout.write(`MODULE_CANDIDATES ${manifestPath}\n`);
76
+ return manifestPath;
77
+ }
78
+ if (require.main === module) {
79
+ try {
80
+ const at = process.argv.indexOf('--verify-installed');
81
+ if (at >= 0) { const manifest = JSON.parse(fs.readFileSync(process.argv[at + 1])); verifyInstalled(manifest, path.resolve(process.argv[at + 2])); process.stdout.write('Installed module bytes verified against candidate artifacts.\n'); }
82
+ else {
83
+ const sources = {};
84
+ for (const [key, d] of Object.entries(definitions)) { const i = process.argv.indexOf(d.option); if (i < 0 || !process.argv[i + 1]) throw new Error(`Explicit source required: ${d.option}`); sources[key] = process.argv[i + 1]; }
85
+ pack(sources);
86
+ }
87
+ } catch (error) { process.stderr.write(`${error.message}\n`); process.exitCode = 1; }
88
+ }
89
+ module.exports = { verifyInstalled, safeRelative, productFiles, installedFileBytes };
@@ -1,28 +1,28 @@
1
- 'use strict';
2
- const fs = require('node:fs');
3
- const path = require('node:path');
4
- const crypto = require('node:crypto');
5
- const { createRequire } = require('node:module');
6
- const { execFileSync } = require('node:child_process');
7
- const { safeRelative } = require('./pack-module-candidates');
8
- const AtomicFile = require('../lib/atomic-file');
9
- const root = path.resolve(__dirname, '../..');
10
- const file = path.join(root, 'module-runtime-manifest.json');
11
- const manifest = JSON.parse(fs.readFileSync(file));
12
- const pkg = JSON.parse(fs.readFileSync(path.join(root, 'package.json')));
13
- const resolve = createRequire(path.join(root, 'package.json'));
14
- const digest = file => crypto.createHash('sha256').update(fs.readFileSync(file)).digest('hex');
15
- for (const module of Object.values(manifest.modules)) {
16
- const directory = path.dirname(resolve.resolve(`${module.installName}/package.json`));
17
- if (!directory.startsWith(path.join(root, 'node_modules') + path.sep)) throw new Error('Module must resolve inside this installed dependency tree.');
18
- const installed = JSON.parse(fs.readFileSync(path.join(directory, 'package.json')));
19
- const expected = pkg.dependencies[module.installName];
20
- if (installed.name !== module.name || ![installed.version, `npm:${installed.name}@${installed.version}`].includes(expected)) throw new Error('Install the exact registry module version before content synchronization.');
21
- module.version = installed.version;
22
- const npmCli = process.env.npm_execpath || path.join(path.dirname(process.execPath), 'node_modules/npm/bin/npm-cli.js');
23
- const packed = JSON.parse(execFileSync(process.execPath, [npmCli, 'pack', '--dry-run', '--ignore-scripts', '--json'], { cwd: directory, windowsHide: true, encoding: 'utf8', maxBuffer: 16 * 1024 * 1024 }))[0];
24
- module.files = Object.fromEntries(packed.files.map(entry => { const relative = entry.path; if (!safeRelative(relative)) throw new Error('Unsafe content path'); const target = path.join(directory, relative); if (fs.lstatSync(target).isSymbolicLink()) throw new Error('Unsafe content symlink'); return [relative, digest(target)]; }));
25
- }
26
- for (const relative of ['ui/index.html', 'ui/product.js', 'ui/live-client.js', 'ui/product.css', 'ui/favicon.svg', 'plugins/project-knowledge/hooks/claude.json', 'plugins/project-knowledge/hooks/codex.json', 'plugins/project-knowledge/scripts/context-hook.cjs', 'plugins/project-knowledge/scripts/mcp-launcher.cjs', 'plugins/project-knowledge/.claude-plugin/plugin.json', 'plugins/project-knowledge/.codex-plugin/plugin.json', 'plugins/project-knowledge/.mcp.json']) manifest.productFiles[relative] = digest(path.join(root, relative));
27
- AtomicFile.writeJsonAtomic(file, manifest);
28
- console.log('Updated content hashes from exact installed modules and reviewed product assets; this is not release acceptance.');
1
+ 'use strict';
2
+ const fs = require('node:fs');
3
+ const path = require('node:path');
4
+ const crypto = require('node:crypto');
5
+ const { createRequire } = require('node:module');
6
+ const { execFileSync } = require('node:child_process');
7
+ const { safeRelative } = require('./pack-module-candidates');
8
+ const AtomicFile = require('../lib/atomic-file');
9
+ const root = path.resolve(__dirname, '../..');
10
+ const file = path.join(root, 'module-runtime-manifest.json');
11
+ const manifest = JSON.parse(fs.readFileSync(file));
12
+ const pkg = JSON.parse(fs.readFileSync(path.join(root, 'package.json')));
13
+ const resolve = createRequire(path.join(root, 'package.json'));
14
+ const digest = file => crypto.createHash('sha256').update(fs.readFileSync(file)).digest('hex');
15
+ for (const module of Object.values(manifest.modules)) {
16
+ const directory = path.dirname(resolve.resolve(`${module.installName}/package.json`));
17
+ if (!directory.startsWith(path.join(root, 'node_modules') + path.sep)) throw new Error('Module must resolve inside this installed dependency tree.');
18
+ const installed = JSON.parse(fs.readFileSync(path.join(directory, 'package.json')));
19
+ const expected = pkg.dependencies[module.installName];
20
+ if (installed.name !== module.name || ![installed.version, `npm:${installed.name}@${installed.version}`].includes(expected)) throw new Error('Install the exact registry module version before content synchronization.');
21
+ module.version = installed.version;
22
+ const npmCli = process.env.npm_execpath || path.join(path.dirname(process.execPath), 'node_modules/npm/bin/npm-cli.js');
23
+ const packed = JSON.parse(execFileSync(process.execPath, [npmCli, 'pack', '--dry-run', '--ignore-scripts', '--json'], { cwd: directory, windowsHide: true, encoding: 'utf8', maxBuffer: 16 * 1024 * 1024 }))[0];
24
+ module.files = Object.fromEntries(packed.files.map(entry => { const relative = entry.path; if (!safeRelative(relative)) throw new Error('Unsafe content path'); const target = path.join(directory, relative); if (fs.lstatSync(target).isSymbolicLink()) throw new Error('Unsafe content symlink'); return [relative, digest(target)]; }));
25
+ }
26
+ for (const relative of ['ui/index.html', 'ui/knowledge-panel.html', 'ui/product.js', 'ui/live-client.js', 'ui/product.css', 'ui/favicon.svg', 'plugins/project-knowledge/hooks/claude.json', 'plugins/project-knowledge/hooks/codex.json', 'plugins/project-knowledge/scripts/context-hook.cjs', 'plugins/project-knowledge/scripts/mcp-launcher.cjs', 'plugins/project-knowledge/.claude-plugin/plugin.json', 'plugins/project-knowledge/.codex-plugin/plugin.json', 'plugins/project-knowledge/.mcp.json']) manifest.productFiles[relative] = digest(path.join(root, relative));
27
+ AtomicFile.writeJsonAtomic(file, manifest);
28
+ console.log('Updated content hashes from exact installed modules and reviewed product assets; this is not release acceptance.');