@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.
- package/.agents/plugins/marketplace.json +3 -3
- package/.claude-plugin/marketplace.json +1 -1
- package/CHANGELOG.md +5 -0
- package/LICENSE +201 -201
- package/NOTICE +5 -5
- package/README.md +349 -349
- package/_modules/knowledge-engine/README.md +4 -0
- package/_modules/knowledge-engine/src/analysis-failures.js +39 -0
- package/_modules/knowledge-engine/src/analysis-worker.js +6 -2
- package/_modules/knowledge-engine/src/coordinator.js +7 -0
- package/_modules/knowledge-engine/src/headless-analyzer.js +2 -1
- package/_modules/knowledge-engine/src/server.js +1 -1
- package/_site/_test/agent-capture-mcp-ui-test.js +67 -67
- package/_site/_test/ai-profile-test.js +75 -75
- package/_site/_test/ai-vendor-presets-test.js +91 -91
- package/_site/_test/automatic-context-test.js +70 -70
- package/_site/_test/bin-cli-test.js +230 -230
- package/_site/_test/bridge-adapter-test.js +95 -95
- package/_site/_test/bridge-consumer-service-test.js +310 -310
- package/_site/_test/candidate-bin-content-test.js +35 -35
- package/_site/_test/candidate-product-assets-test.js +13 -13
- package/_site/_test/candidate-release-contract-test.js +14 -14
- package/_site/_test/capture-hook-dedup-test.js +158 -158
- package/_site/_test/claude-executable-discovery-test.js +28 -28
- package/_site/_test/claude-session-lifecycle-sweep-test.js +252 -252
- package/_site/_test/cli-instance-management-test.js +62 -62
- package/_site/_test/client-capture-restart-e2e-test.js +189 -189
- package/_site/_test/codex-conversation-projection-test.js +60 -60
- package/_site/_test/codex-marketplace-scoped-path-test.js +186 -186
- package/_site/_test/codex-notify-capture-e2e-test.js +101 -101
- package/_site/_test/codex-notify-fanout-test.js +134 -134
- package/_site/_test/codex-notify-upgrade-test.js +253 -253
- package/_site/_test/commit-boundary-freeze-test.js +147 -147
- package/_site/_test/commit-conversation-binding-test.js +4 -4
- package/_site/_test/configuration-generation-test.js +22 -22
- package/_site/_test/context-pack-test.js +200 -200
- package/_site/_test/conversation-api-test.js +10 -10
- package/_site/_test/conversation-origin-test.js +15 -15
- package/_site/_test/cross-repo-isolation-e2e-test.js +187 -187
- package/_site/_test/embedding-config-test.js +32 -32
- package/_site/_test/fixtures/fake-browser-exit.js +10 -10
- package/_site/_test/fixtures/fake-browser-hang.js +10 -10
- package/_site/_test/fixtures/integration-busy-worker.cjs +6 -6
- package/_site/_test/fixtures/make-git-repos.js +92 -92
- package/_site/_test/fixtures/retrieval-gold-queries.json +32 -32
- package/_site/_test/fresh-hook-reimport-test.js +53 -53
- package/_site/_test/fresh-product-data-test.js +56 -56
- package/_site/_test/git-path-compatibility-test.js +80 -80
- package/_site/_test/git-validation-test.js +81 -81
- package/_site/_test/hook-worktree-test.js +42 -42
- package/_site/_test/index-builder-test.js +41 -41
- package/_site/_test/index-error-display-contract-test.js +10 -10
- package/_site/_test/integration-adapters-test.js +67 -67
- package/_site/_test/integration-capture-setup-test.js +118 -118
- package/_site/_test/integration-runtime-binding-a2-test.js +566 -566
- package/_site/_test/integration-runtime-binding-a3-test.js +227 -227
- package/_site/_test/integration-runtime-binding-test.js +473 -473
- package/_site/_test/integration-status-scan-test.js +29 -29
- package/_site/_test/kb-framework-test.js +57 -57
- package/_site/_test/kbpath-follow-test.js +63 -63
- package/_site/_test/knowledge-db-test.js +144 -144
- package/_site/_test/knowledge-engine-client-test.js +109 -109
- package/_site/_test/knowledge-maintenance-test.js +40 -40
- package/_site/_test/knowledge-panel-ui-contract-test.js +112 -0
- package/_site/_test/knowledge-scopes-test.js +37 -37
- package/_site/_test/knowledge-storage-location-test.js +28 -28
- package/_site/_test/launcher-readiness-test.js +80 -80
- package/_site/_test/legacy-claude-session-alias-test.js +106 -106
- package/_site/_test/legacy-routes-removed-test.js +47 -47
- package/_site/_test/legacy-workbench-exclusion-test.js +92 -92
- package/_site/_test/live-gateway-test.js +48 -48
- package/_site/_test/local-marketplace-test.js +16 -16
- package/_site/_test/log-redaction-test.js +58 -58
- package/_site/_test/markdown-maintenance-test.js +11 -11
- package/_site/_test/model-context-windows-test.js +89 -89
- package/_site/_test/module-agent-start-proxy-test.js +65 -65
- package/_site/_test/module-artifact-integrity-test.js +27 -27
- package/_site/_test/module-boundary-test.js +30 -30
- package/_site/_test/module-bridge-eventbridge-test.js +202 -202
- package/_site/_test/module-dynamic-port-test.js +118 -118
- package/_site/_test/module-index-proxy-test.js +33 -33
- package/_site/_test/module-model-configuration-test.js +44 -44
- package/_site/_test/module-process-lifecycle-test.js +35 -35
- package/_site/_test/module-stream-proxy-test.js +34 -34
- package/_site/_test/module-terminal-restart-test.js +63 -63
- package/_site/_test/module-wiring-unbind-race-test.js +59 -59
- package/_site/_test/native-context-integrations-test.js +86 -86
- package/_site/_test/orphaned-claim-recovery-test.js +80 -80
- package/_site/_test/package-boundary-test.js +43 -42
- package/_site/_test/package-startup-test.js +56 -56
- package/_site/_test/packaged-ui-smoke-test.js +88 -88
- package/_site/_test/pr-consumer-contract-test.js +55 -55
- package/_site/_test/product-brand-contract-test.js +25 -25
- package/_site/_test/product-diagnostics-ui-test.js +93 -93
- package/_site/_test/product-import-ui-test.js +104 -104
- package/_site/_test/product-progress-status-test.js +12 -1
- package/_site/_test/project-remove-running-guard-test.js +79 -79
- package/_site/_test/project-store-test.js +90 -90
- package/_site/_test/registry-events-test.js +38 -38
- package/_site/_test/release-version-sync-test.js +115 -115
- package/_site/_test/requirement-binding-test.js +113 -113
- package/_site/_test/runtime-endpoint-test.js +64 -64
- package/_site/_test/scanner-test.js +83 -83
- package/_site/_test/server-security-test.js +96 -96
- package/_site/_test/sessions-stream-test.js +93 -93
- package/_site/_test/shared-scope-root-binding-test.js +67 -67
- package/_site/_test/simple-import-test.js +98 -98
- package/_site/_test/startup-analysis-disabled-test.js +6 -6
- package/_site/_test/team-knowledge-store-sparse-checkout-test.js +196 -196
- package/_site/_test/team-knowledge-store-test.js +132 -132
- package/_site/_test/test-server-cleanup-test.js +35 -35
- package/_site/_test/test-server-module-isolation-test.js +37 -37
- package/_site/_test/tracking-start-test.js +35 -35
- package/_site/_test/workbench-context-receipts-test.js +22 -22
- package/_site/_test/workspace-ui-contract-test.js +31 -31
- package/_site/_test/zcode-capture-integration-test.js +108 -108
- package/_site/lib/ai-vendor-presets.js +83 -83
- package/_site/lib/ai-workspace.js +3 -3
- package/_site/lib/atomic-file.js +204 -204
- package/_site/lib/bridge-adapter.js +260 -260
- package/_site/lib/capture-hook-config.js +108 -108
- package/_site/lib/claude-cli-runner.js +89 -89
- package/_site/lib/cli-instances.js +43 -43
- package/_site/lib/codex-context-status.js +17 -17
- package/_site/lib/codex-notify-config.js +193 -193
- package/_site/lib/commit-conversation-binder.js +12 -12
- package/_site/lib/commit-reconciler.js +38 -38
- package/_site/lib/context-pack-builder.js +299 -299
- package/_site/lib/conversation-exclusions.js +204 -204
- package/_site/lib/conversation-query-service.js +17 -17
- package/_site/lib/data-dir.js +13 -13
- package/_site/lib/embedding-service.js +85 -85
- package/_site/lib/engine-connection.js +93 -93
- package/_site/lib/engine-knowledge-client.js +72 -72
- package/_site/lib/folder-picker-output.js +3 -3
- package/_site/lib/git-path.js +14 -14
- package/_site/lib/github-team-store.js +3 -3
- package/_site/lib/index-builder.js +157 -157
- package/_site/lib/integration-status-scan.js +99 -99
- package/_site/lib/integration-status-worker.js +14 -14
- package/_site/lib/kb-framework.js +15 -15
- package/_site/lib/knowledge-db.js +363 -363
- package/_site/lib/knowledge-schema.js +121 -121
- package/_site/lib/live-gateway.js +78 -78
- package/_site/lib/llm-client.js +12 -12
- package/_site/lib/model-context-windows.js +82 -82
- package/_site/lib/module-bridge.js +874 -874
- package/_site/lib/module-port-resolver.js +57 -57
- package/_site/lib/post-commit-automation.js +54 -54
- package/_site/lib/product-data.js +35 -35
- package/_site/lib/project-lifecycle-service.js +14 -14
- package/_site/lib/project-registry-store.js +129 -129
- package/_site/lib/prompt-registry.js +148 -148
- package/_site/lib/registry-events.js +16 -16
- package/_site/lib/requirement-adapters.js +10 -10
- package/_site/lib/requirement-binder.js +112 -112
- package/_site/lib/runtime-control.js +89 -89
- package/_site/lib/runtime-endpoint.js +5 -5
- package/_site/lib/zcode-context-integration.js +60 -60
- package/_site/scripts/codex-notify-fanout.cjs +95 -95
- package/_site/scripts/install-module-candidates.js +69 -69
- package/_site/scripts/pack-module-candidates.js +89 -89
- package/_site/scripts/sync-product-content.js +28 -28
- package/_site/scripts/sync-release-version.js +164 -164
- package/_site/scripts/vendor-modules.js +49 -49
- package/_site/scripts/verify-module-runtime.js +50 -50
- package/_site/scripts/verify-product-runtime.js +36 -36
- package/_site/scripts/verify-retrieval-gold.js +83 -83
- package/_site/server.js +15 -15
- package/_site/start.bat +26 -26
- package/_site/stop.bat +11 -11
- package/bin/project-knowledge-integrations.js +254 -254
- package/bin/project-knowledge-kb.js +67 -67
- package/bin/project-knowledge-mcp.js +8 -8
- package/bin/project-knowledge.js +170 -170
- package/claude-prompts.json +21 -21
- package/docs/README.zh-CN.md +205 -205
- package/docs/assets/architecture.svg +181 -181
- package/docs/assets/logo.svg +49 -49
- package/docs/fresh-v5-start.md +42 -42
- package/docs/leader-glm-workbench-playbook.md +122 -122
- package/docs/project-registry-schema.md +11 -11
- package/docs/release-5.0.16-acceptance.md +176 -176
- package/docs/release-5.0.17-acceptance.md +117 -117
- package/docs/release-5.0.19-acceptance.md +39 -25
- package/docs/team-knowledge-mode-a-plan.md +73 -73
- package/module-runtime-manifest.json +53 -33
- package/package.json +3 -2
- package/plugins/project-knowledge/.claude-plugin/plugin.json +1 -1
- package/plugins/project-knowledge/.codex-plugin/plugin.json +1 -1
- package/plugins/project-knowledge/.mcp.json +1 -1
- package/plugins/project-knowledge/opencode/project-knowledge.md +3 -3
- package/plugins/project-knowledge/skills/project-knowledge/SKILL.md +28 -28
- package/templates/change.md +30 -30
- package/templates/project-readme.md +18 -18
- package/ui/index.html +1 -1
- package/ui/knowledge-panel.html +118 -0
- package/ui/product.css +8 -0
- package/ui/product.js +146 -4
- 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.');
|