@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,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,89 +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 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/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
+ '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.');