@sanqianx/project-knowledge 5.0.19 → 5.0.20
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,80 +1,80 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
const assert = require('node:assert/strict');
|
|
3
|
-
const fs = require('node:fs');
|
|
4
|
-
const path = require('node:path');
|
|
5
|
-
const os = require('node:os');
|
|
6
|
-
const net = require('node:net');
|
|
7
|
-
const { spawn } = require('node:child_process');
|
|
8
|
-
const endpoint = require('../lib/runtime-endpoint');
|
|
9
|
-
|
|
10
|
-
const packageRoot = process.env.KB_LAUNCHER_TEST_PACKAGE || path.resolve(__dirname, '../..');
|
|
11
|
-
const bin = path.join(packageRoot, 'bin/project-knowledge.js');
|
|
12
|
-
const root = fs.mkdtempSync(path.join(os.tmpdir(), 'kb-launcher-readiness-'));
|
|
13
|
-
const data = path.join(root, '.project-knowledge-v5');
|
|
14
|
-
const record = path.join(data, 'runtime/claude-sessions/project-a/session.json');
|
|
15
|
-
const recordBytes = JSON.stringify({ schema: 'claude-workbench-session/v1', sessionId: 'old-test', projectSlug: 'project-a', state: 'running', metadata: { automation: true } });
|
|
16
|
-
fs.mkdirSync(path.dirname(record), { recursive: true });
|
|
17
|
-
fs.writeFileSync(record, recordBytes);
|
|
18
|
-
const env = { ...process.env, KB_DATA_DIR: data, KB_MODULES_AUTOSTART: '0', AI_CODING_EVENT_BRIDGE_HOME: path.join(root, 'bridge') };
|
|
19
|
-
let serverPid;
|
|
20
|
-
|
|
21
|
-
function run(args, overrides = {}) {
|
|
22
|
-
return new Promise((resolve, reject) => {
|
|
23
|
-
const child = spawn(process.execPath, [bin, ...args], { cwd: root, env: { ...env, ...overrides }, stdio: ['ignore', 'pipe', 'pipe'], windowsHide: true });
|
|
24
|
-
let stdout = '', stderr = '';
|
|
25
|
-
const timer = setTimeout(() => { child.kill(); reject(new Error(`CLI timed out: ${stdout} ${stderr}`)); }, 25000);
|
|
26
|
-
child.stdout.on('data', chunk => { stdout += chunk; });
|
|
27
|
-
child.stderr.on('data', chunk => { stderr += chunk; });
|
|
28
|
-
child.once('error', reject);
|
|
29
|
-
child.once('exit', code => { clearTimeout(timer); resolve({ code, stdout, stderr }); });
|
|
30
|
-
});
|
|
31
|
-
}
|
|
32
|
-
async function freePort() {
|
|
33
|
-
const server = net.createServer();
|
|
34
|
-
await new Promise(resolve => server.listen(0, '127.0.0.1', resolve));
|
|
35
|
-
const port = server.address().port;
|
|
36
|
-
await new Promise(resolve => server.close(resolve));
|
|
37
|
-
return port;
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
(async () => {
|
|
41
|
-
const port = await freePort();
|
|
42
|
-
const launched = await run(['--no-open'], { KB_SITE_PORT: String(port) });
|
|
43
|
-
serverPid = endpoint.readEndpoint(data)?.pid;
|
|
44
|
-
assert.equal(launched.code, 0, launched.stderr);
|
|
45
|
-
assert.match(launched.stdout, new RegExp(`is ready at http://127.0.0.1:${port}`));
|
|
46
|
-
assert(serverPid && endpoint.isProcessAlive(serverPid));
|
|
47
|
-
const response = await fetch(`http://127.0.0.1:${port}/api/state`);
|
|
48
|
-
assert.equal(response.status, 200, 'ready must mean HTTP is usable, not only a PID exists');
|
|
49
|
-
const state = await response.json();
|
|
50
|
-
assert.deepEqual(state.projects, [], 'abandoned sessions must not import projects or enqueue analysis');
|
|
51
|
-
assert.equal(fs.readFileSync(record, 'utf8'), recordBytes);
|
|
52
|
-
const again = await run(['--no-open']);
|
|
53
|
-
assert.equal(again.code, 0);
|
|
54
|
-
assert.match(again.stdout, /Already running/);
|
|
55
|
-
const stopped = await run(['stop']);
|
|
56
|
-
assert.equal(stopped.code, 0);
|
|
57
|
-
for (let attempt = 0; attempt < 50 && endpoint.isProcessAlive(serverPid); attempt++) await new Promise(resolve => setTimeout(resolve, 100));
|
|
58
|
-
assert.equal(endpoint.isProcessAlive(serverPid), false);
|
|
59
|
-
serverPid = null;
|
|
60
|
-
|
|
61
|
-
const oldData = path.join(root, 'actual-old-data');
|
|
62
|
-
fs.mkdirSync(oldData);
|
|
63
|
-
const original = JSON.stringify({ projectOrder: ['old-project'] });
|
|
64
|
-
fs.writeFileSync(path.join(oldData, 'projects.json'), original);
|
|
65
|
-
const failed = await run(['--no-open', '--port', String(port)], { KB_DATA_DIR: oldData });
|
|
66
|
-
assert.equal(failed.code, 1, 'background failures must return nonzero to the invoking terminal');
|
|
67
|
-
assert.match(failed.stderr, /启动失败/);
|
|
68
|
-
assert.match(failed.stderr, /projects\.json/);
|
|
69
|
-
assert(!/is ready|Listening at/.test(failed.stdout));
|
|
70
|
-
assert.equal(fs.readFileSync(path.join(oldData, 'projects.json'), 'utf8'), original);
|
|
71
|
-
assert.equal(fs.existsSync(path.join(oldData, 'product-data.json')), false);
|
|
72
|
-
const log = fs.readFileSync(path.join(oldData, 'launcher.log'), 'utf8');
|
|
73
|
-
assert.match(log, /startup failed/);
|
|
74
|
-
assert(!/Listening at/.test(log), 'never advertise a listener before initialization succeeds');
|
|
75
|
-
console.log('launcher readiness PASS: unrelated cwd, abandoned records, HTTP readiness, stop, terminal failure, legacy preservation');
|
|
76
|
-
})().catch(error => { console.error(error); process.exitCode = 1; }).finally(() => {
|
|
77
|
-
if (serverPid && endpoint.readEndpoint(data)?.pid === serverPid) {
|
|
78
|
-
try { process.kill(serverPid); } catch {}
|
|
79
|
-
}
|
|
80
|
-
});
|
|
1
|
+
'use strict';
|
|
2
|
+
const assert = require('node:assert/strict');
|
|
3
|
+
const fs = require('node:fs');
|
|
4
|
+
const path = require('node:path');
|
|
5
|
+
const os = require('node:os');
|
|
6
|
+
const net = require('node:net');
|
|
7
|
+
const { spawn } = require('node:child_process');
|
|
8
|
+
const endpoint = require('../lib/runtime-endpoint');
|
|
9
|
+
|
|
10
|
+
const packageRoot = process.env.KB_LAUNCHER_TEST_PACKAGE || path.resolve(__dirname, '../..');
|
|
11
|
+
const bin = path.join(packageRoot, 'bin/project-knowledge.js');
|
|
12
|
+
const root = fs.mkdtempSync(path.join(os.tmpdir(), 'kb-launcher-readiness-'));
|
|
13
|
+
const data = path.join(root, '.project-knowledge-v5');
|
|
14
|
+
const record = path.join(data, 'runtime/claude-sessions/project-a/session.json');
|
|
15
|
+
const recordBytes = JSON.stringify({ schema: 'claude-workbench-session/v1', sessionId: 'old-test', projectSlug: 'project-a', state: 'running', metadata: { automation: true } });
|
|
16
|
+
fs.mkdirSync(path.dirname(record), { recursive: true });
|
|
17
|
+
fs.writeFileSync(record, recordBytes);
|
|
18
|
+
const env = { ...process.env, KB_DATA_DIR: data, KB_MODULES_AUTOSTART: '0', AI_CODING_EVENT_BRIDGE_HOME: path.join(root, 'bridge') };
|
|
19
|
+
let serverPid;
|
|
20
|
+
|
|
21
|
+
function run(args, overrides = {}) {
|
|
22
|
+
return new Promise((resolve, reject) => {
|
|
23
|
+
const child = spawn(process.execPath, [bin, ...args], { cwd: root, env: { ...env, ...overrides }, stdio: ['ignore', 'pipe', 'pipe'], windowsHide: true });
|
|
24
|
+
let stdout = '', stderr = '';
|
|
25
|
+
const timer = setTimeout(() => { child.kill(); reject(new Error(`CLI timed out: ${stdout} ${stderr}`)); }, 25000);
|
|
26
|
+
child.stdout.on('data', chunk => { stdout += chunk; });
|
|
27
|
+
child.stderr.on('data', chunk => { stderr += chunk; });
|
|
28
|
+
child.once('error', reject);
|
|
29
|
+
child.once('exit', code => { clearTimeout(timer); resolve({ code, stdout, stderr }); });
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
async function freePort() {
|
|
33
|
+
const server = net.createServer();
|
|
34
|
+
await new Promise(resolve => server.listen(0, '127.0.0.1', resolve));
|
|
35
|
+
const port = server.address().port;
|
|
36
|
+
await new Promise(resolve => server.close(resolve));
|
|
37
|
+
return port;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
(async () => {
|
|
41
|
+
const port = await freePort();
|
|
42
|
+
const launched = await run(['--no-open'], { KB_SITE_PORT: String(port) });
|
|
43
|
+
serverPid = endpoint.readEndpoint(data)?.pid;
|
|
44
|
+
assert.equal(launched.code, 0, launched.stderr);
|
|
45
|
+
assert.match(launched.stdout, new RegExp(`is ready at http://127.0.0.1:${port}`));
|
|
46
|
+
assert(serverPid && endpoint.isProcessAlive(serverPid));
|
|
47
|
+
const response = await fetch(`http://127.0.0.1:${port}/api/state`);
|
|
48
|
+
assert.equal(response.status, 200, 'ready must mean HTTP is usable, not only a PID exists');
|
|
49
|
+
const state = await response.json();
|
|
50
|
+
assert.deepEqual(state.projects, [], 'abandoned sessions must not import projects or enqueue analysis');
|
|
51
|
+
assert.equal(fs.readFileSync(record, 'utf8'), recordBytes);
|
|
52
|
+
const again = await run(['--no-open']);
|
|
53
|
+
assert.equal(again.code, 0);
|
|
54
|
+
assert.match(again.stdout, /Already running/);
|
|
55
|
+
const stopped = await run(['stop']);
|
|
56
|
+
assert.equal(stopped.code, 0);
|
|
57
|
+
for (let attempt = 0; attempt < 50 && endpoint.isProcessAlive(serverPid); attempt++) await new Promise(resolve => setTimeout(resolve, 100));
|
|
58
|
+
assert.equal(endpoint.isProcessAlive(serverPid), false);
|
|
59
|
+
serverPid = null;
|
|
60
|
+
|
|
61
|
+
const oldData = path.join(root, 'actual-old-data');
|
|
62
|
+
fs.mkdirSync(oldData);
|
|
63
|
+
const original = JSON.stringify({ projectOrder: ['old-project'] });
|
|
64
|
+
fs.writeFileSync(path.join(oldData, 'projects.json'), original);
|
|
65
|
+
const failed = await run(['--no-open', '--port', String(port)], { KB_DATA_DIR: oldData });
|
|
66
|
+
assert.equal(failed.code, 1, 'background failures must return nonzero to the invoking terminal');
|
|
67
|
+
assert.match(failed.stderr, /启动失败/);
|
|
68
|
+
assert.match(failed.stderr, /projects\.json/);
|
|
69
|
+
assert(!/is ready|Listening at/.test(failed.stdout));
|
|
70
|
+
assert.equal(fs.readFileSync(path.join(oldData, 'projects.json'), 'utf8'), original);
|
|
71
|
+
assert.equal(fs.existsSync(path.join(oldData, 'product-data.json')), false);
|
|
72
|
+
const log = fs.readFileSync(path.join(oldData, 'launcher.log'), 'utf8');
|
|
73
|
+
assert.match(log, /startup failed/);
|
|
74
|
+
assert(!/Listening at/.test(log), 'never advertise a listener before initialization succeeds');
|
|
75
|
+
console.log('launcher readiness PASS: unrelated cwd, abandoned records, HTTP readiness, stop, terminal failure, legacy preservation');
|
|
76
|
+
})().catch(error => { console.error(error); process.exitCode = 1; }).finally(() => {
|
|
77
|
+
if (serverPid && endpoint.readEndpoint(data)?.pid === serverPid) {
|
|
78
|
+
try { process.kill(serverPid); } catch {}
|
|
79
|
+
}
|
|
80
|
+
});
|
|
@@ -1,106 +1,106 @@
|
|
|
1
|
-
// Run: node _site/_test/legacy-claude-session-alias-test.js
|
|
2
|
-
|
|
3
|
-
const assert = require('assert');
|
|
4
|
-
const fs = require('fs');
|
|
5
|
-
const os = require('os');
|
|
6
|
-
const path = require('path');
|
|
7
|
-
|
|
8
|
-
const dataDir = fs.mkdtempSync(path.join(os.tmpdir(), `kb-legacy-session-${process.pid}-`));
|
|
9
|
-
const projectId = 'project-stable-id';
|
|
10
|
-
const legacySlug = 'legacy-project-slug';
|
|
11
|
-
const knowledgePath = path.join(dataDir, 'knowledge', legacySlug);
|
|
12
|
-
const metadataPath = path.join(dataDir, 'projects', projectId);
|
|
13
|
-
const now = new Date().toISOString();
|
|
14
|
-
process.env.KB_DATA_DIR = dataDir;
|
|
15
|
-
require('../lib/data-dir')._resetCache();
|
|
16
|
-
|
|
17
|
-
const { defaultProjectConfig, defaultProjectState } = require('../lib/project-store');
|
|
18
|
-
|
|
19
|
-
function writeRecord(root, record) {
|
|
20
|
-
fs.mkdirSync(root, { recursive: true });
|
|
21
|
-
fs.writeFileSync(path.join(root, `${record.sessionId}.json`), `${JSON.stringify({
|
|
22
|
-
schema: 'claude-workbench-session/v1',
|
|
23
|
-
projectPath: dataDir,
|
|
24
|
-
kbPath: knowledgePath,
|
|
25
|
-
promptKey: 'post-commit-automation',
|
|
26
|
-
runner: 'sdk',
|
|
27
|
-
state: 'idle',
|
|
28
|
-
model: 'test-model',
|
|
29
|
-
startedAt: now,
|
|
30
|
-
endedAt: now,
|
|
31
|
-
exitCode: 0,
|
|
32
|
-
turns: 1,
|
|
33
|
-
pendingPermission: null,
|
|
34
|
-
updatedAt: now,
|
|
35
|
-
...record,
|
|
36
|
-
}, null, 2)}\n`, 'utf8');
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
try {
|
|
40
|
-
fs.mkdirSync(metadataPath, { recursive: true });
|
|
41
|
-
fs.mkdirSync(knowledgePath, { recursive: true });
|
|
42
|
-
fs.writeFileSync(path.join(metadataPath, 'config.json'), `${JSON.stringify(defaultProjectConfig(projectId, {
|
|
43
|
-
displayName: 'Legacy project',
|
|
44
|
-
storageName: 'legacy-project',
|
|
45
|
-
repoPath: dataDir,
|
|
46
|
-
knowledgePath,
|
|
47
|
-
legacyExtensions: { slug: legacySlug, sourceSchema: 'legacy-v1' },
|
|
48
|
-
}), null, 2)}\n`, 'utf8');
|
|
49
|
-
fs.writeFileSync(path.join(metadataPath, 'state.json'), `${JSON.stringify(defaultProjectState(), null, 2)}\n`, 'utf8');
|
|
50
|
-
fs.writeFileSync(path.join(dataDir, 'projects.json'), `${JSON.stringify({
|
|
51
|
-
schema: 'project-registry/v2',
|
|
52
|
-
schemaVersion: 2,
|
|
53
|
-
projectOrder: [projectId],
|
|
54
|
-
projects: { [projectId]: { createdAt: now, displayNameSnapshot: 'Legacy project' } },
|
|
55
|
-
revision: 1,
|
|
56
|
-
updatedAt: now,
|
|
57
|
-
}, null, 2)}\n`, 'utf8');
|
|
58
|
-
|
|
59
|
-
const legacyRoot = path.join(dataDir, '_ai', legacySlug, 'claude-workbench');
|
|
60
|
-
const currentRoot = path.join(dataDir, 'runtime', 'claude-sessions', projectId);
|
|
61
|
-
writeRecord(legacyRoot, {
|
|
62
|
-
sessionId: 'sess-legacy-only',
|
|
63
|
-
projectSlug: legacySlug,
|
|
64
|
-
source: 'git-hook',
|
|
65
|
-
automation: true,
|
|
66
|
-
events: [
|
|
67
|
-
{ type: 'claude/user-prompt', text: 'historical hook prompt' },
|
|
68
|
-
{ type: 'claude/result', result: 'historical analysis result' },
|
|
69
|
-
],
|
|
70
|
-
});
|
|
71
|
-
writeRecord(legacyRoot, {
|
|
72
|
-
sessionId: 'sess-duplicate',
|
|
73
|
-
projectSlug: legacySlug,
|
|
74
|
-
source: 'legacy-copy',
|
|
75
|
-
events: [{ type: 'claude/result', result: 'legacy duplicate' }],
|
|
76
|
-
});
|
|
77
|
-
writeRecord(currentRoot, {
|
|
78
|
-
sessionId: 'sess-duplicate',
|
|
79
|
-
projectSlug: projectId,
|
|
80
|
-
source: 'current-runtime',
|
|
81
|
-
events: [{ type: 'claude/result', result: 'current duplicate' }],
|
|
82
|
-
});
|
|
83
|
-
|
|
84
|
-
const runner = require('../lib/claude-cli-runner');
|
|
85
|
-
const listed = runner.listSessions({ projectSlug: projectId });
|
|
86
|
-
assert.strictEqual(listed.length, 2);
|
|
87
|
-
const legacy = listed.find(session => session.sessionId === 'sess-legacy-only');
|
|
88
|
-
assert(legacy, 'legacy alias session must be discoverable under the stable projectId');
|
|
89
|
-
assert.strictEqual(legacy.projectSlug, projectId);
|
|
90
|
-
assert.strictEqual(legacy.legacyProjectSlug, legacySlug);
|
|
91
|
-
assert.strictEqual(legacy.legacySource, true);
|
|
92
|
-
assert.strictEqual(listed.find(session => session.sessionId === 'sess-duplicate').source, 'current-runtime', 'canonical runtime record must win duplicate IDs');
|
|
93
|
-
|
|
94
|
-
const events = [];
|
|
95
|
-
const unsubscribe = runner.subscribe('sess-legacy-only', event => events.push(event));
|
|
96
|
-
unsubscribe();
|
|
97
|
-
assert(events.some(event => event.type === 'claude/user-prompt' && event.text === 'historical hook prompt'));
|
|
98
|
-
assert(events.some(event => event.type === 'claude/result' && event.result === 'historical analysis result'));
|
|
99
|
-
assert.strictEqual(runner.getState('sess-legacy-only').projectSlug, projectId);
|
|
100
|
-
console.log('legacy-claude-session-alias-test PASS');
|
|
101
|
-
} catch (error) {
|
|
102
|
-
console.error(error.stack || error.message);
|
|
103
|
-
process.exitCode = 1;
|
|
104
|
-
} finally {
|
|
105
|
-
fs.rmSync(dataDir, { recursive: true, force: true });
|
|
106
|
-
}
|
|
1
|
+
// Run: node _site/_test/legacy-claude-session-alias-test.js
|
|
2
|
+
|
|
3
|
+
const assert = require('assert');
|
|
4
|
+
const fs = require('fs');
|
|
5
|
+
const os = require('os');
|
|
6
|
+
const path = require('path');
|
|
7
|
+
|
|
8
|
+
const dataDir = fs.mkdtempSync(path.join(os.tmpdir(), `kb-legacy-session-${process.pid}-`));
|
|
9
|
+
const projectId = 'project-stable-id';
|
|
10
|
+
const legacySlug = 'legacy-project-slug';
|
|
11
|
+
const knowledgePath = path.join(dataDir, 'knowledge', legacySlug);
|
|
12
|
+
const metadataPath = path.join(dataDir, 'projects', projectId);
|
|
13
|
+
const now = new Date().toISOString();
|
|
14
|
+
process.env.KB_DATA_DIR = dataDir;
|
|
15
|
+
require('../lib/data-dir')._resetCache();
|
|
16
|
+
|
|
17
|
+
const { defaultProjectConfig, defaultProjectState } = require('../lib/project-store');
|
|
18
|
+
|
|
19
|
+
function writeRecord(root, record) {
|
|
20
|
+
fs.mkdirSync(root, { recursive: true });
|
|
21
|
+
fs.writeFileSync(path.join(root, `${record.sessionId}.json`), `${JSON.stringify({
|
|
22
|
+
schema: 'claude-workbench-session/v1',
|
|
23
|
+
projectPath: dataDir,
|
|
24
|
+
kbPath: knowledgePath,
|
|
25
|
+
promptKey: 'post-commit-automation',
|
|
26
|
+
runner: 'sdk',
|
|
27
|
+
state: 'idle',
|
|
28
|
+
model: 'test-model',
|
|
29
|
+
startedAt: now,
|
|
30
|
+
endedAt: now,
|
|
31
|
+
exitCode: 0,
|
|
32
|
+
turns: 1,
|
|
33
|
+
pendingPermission: null,
|
|
34
|
+
updatedAt: now,
|
|
35
|
+
...record,
|
|
36
|
+
}, null, 2)}\n`, 'utf8');
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
try {
|
|
40
|
+
fs.mkdirSync(metadataPath, { recursive: true });
|
|
41
|
+
fs.mkdirSync(knowledgePath, { recursive: true });
|
|
42
|
+
fs.writeFileSync(path.join(metadataPath, 'config.json'), `${JSON.stringify(defaultProjectConfig(projectId, {
|
|
43
|
+
displayName: 'Legacy project',
|
|
44
|
+
storageName: 'legacy-project',
|
|
45
|
+
repoPath: dataDir,
|
|
46
|
+
knowledgePath,
|
|
47
|
+
legacyExtensions: { slug: legacySlug, sourceSchema: 'legacy-v1' },
|
|
48
|
+
}), null, 2)}\n`, 'utf8');
|
|
49
|
+
fs.writeFileSync(path.join(metadataPath, 'state.json'), `${JSON.stringify(defaultProjectState(), null, 2)}\n`, 'utf8');
|
|
50
|
+
fs.writeFileSync(path.join(dataDir, 'projects.json'), `${JSON.stringify({
|
|
51
|
+
schema: 'project-registry/v2',
|
|
52
|
+
schemaVersion: 2,
|
|
53
|
+
projectOrder: [projectId],
|
|
54
|
+
projects: { [projectId]: { createdAt: now, displayNameSnapshot: 'Legacy project' } },
|
|
55
|
+
revision: 1,
|
|
56
|
+
updatedAt: now,
|
|
57
|
+
}, null, 2)}\n`, 'utf8');
|
|
58
|
+
|
|
59
|
+
const legacyRoot = path.join(dataDir, '_ai', legacySlug, 'claude-workbench');
|
|
60
|
+
const currentRoot = path.join(dataDir, 'runtime', 'claude-sessions', projectId);
|
|
61
|
+
writeRecord(legacyRoot, {
|
|
62
|
+
sessionId: 'sess-legacy-only',
|
|
63
|
+
projectSlug: legacySlug,
|
|
64
|
+
source: 'git-hook',
|
|
65
|
+
automation: true,
|
|
66
|
+
events: [
|
|
67
|
+
{ type: 'claude/user-prompt', text: 'historical hook prompt' },
|
|
68
|
+
{ type: 'claude/result', result: 'historical analysis result' },
|
|
69
|
+
],
|
|
70
|
+
});
|
|
71
|
+
writeRecord(legacyRoot, {
|
|
72
|
+
sessionId: 'sess-duplicate',
|
|
73
|
+
projectSlug: legacySlug,
|
|
74
|
+
source: 'legacy-copy',
|
|
75
|
+
events: [{ type: 'claude/result', result: 'legacy duplicate' }],
|
|
76
|
+
});
|
|
77
|
+
writeRecord(currentRoot, {
|
|
78
|
+
sessionId: 'sess-duplicate',
|
|
79
|
+
projectSlug: projectId,
|
|
80
|
+
source: 'current-runtime',
|
|
81
|
+
events: [{ type: 'claude/result', result: 'current duplicate' }],
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
const runner = require('../lib/claude-cli-runner');
|
|
85
|
+
const listed = runner.listSessions({ projectSlug: projectId });
|
|
86
|
+
assert.strictEqual(listed.length, 2);
|
|
87
|
+
const legacy = listed.find(session => session.sessionId === 'sess-legacy-only');
|
|
88
|
+
assert(legacy, 'legacy alias session must be discoverable under the stable projectId');
|
|
89
|
+
assert.strictEqual(legacy.projectSlug, projectId);
|
|
90
|
+
assert.strictEqual(legacy.legacyProjectSlug, legacySlug);
|
|
91
|
+
assert.strictEqual(legacy.legacySource, true);
|
|
92
|
+
assert.strictEqual(listed.find(session => session.sessionId === 'sess-duplicate').source, 'current-runtime', 'canonical runtime record must win duplicate IDs');
|
|
93
|
+
|
|
94
|
+
const events = [];
|
|
95
|
+
const unsubscribe = runner.subscribe('sess-legacy-only', event => events.push(event));
|
|
96
|
+
unsubscribe();
|
|
97
|
+
assert(events.some(event => event.type === 'claude/user-prompt' && event.text === 'historical hook prompt'));
|
|
98
|
+
assert(events.some(event => event.type === 'claude/result' && event.result === 'historical analysis result'));
|
|
99
|
+
assert.strictEqual(runner.getState('sess-legacy-only').projectSlug, projectId);
|
|
100
|
+
console.log('legacy-claude-session-alias-test PASS');
|
|
101
|
+
} catch (error) {
|
|
102
|
+
console.error(error.stack || error.message);
|
|
103
|
+
process.exitCode = 1;
|
|
104
|
+
} finally {
|
|
105
|
+
fs.rmSync(dataDir, { recursive: true, force: true });
|
|
106
|
+
}
|
|
@@ -1,47 +1,47 @@
|
|
|
1
|
-
const assert = require('assert');
|
|
2
|
-
const fs = require('fs');
|
|
3
|
-
const os = require('os');
|
|
4
|
-
const path = require('path');
|
|
5
|
-
const { spawnServer } = require('./helpers/spawn-server');
|
|
6
|
-
|
|
7
|
-
const ROOT = path.resolve(__dirname, '..', '..');
|
|
8
|
-
const PORT = 7833;
|
|
9
|
-
const BASE = `http://127.0.0.1:${PORT}`;
|
|
10
|
-
const dataDir = fs.mkdtempSync(path.join(os.tmpdir(), 'pkb-routes-v2-'));
|
|
11
|
-
|
|
12
|
-
async function waitForServer() {
|
|
13
|
-
for (let attempt = 0; attempt < 100; attempt += 1) {
|
|
14
|
-
try { if ((await fetch(`${BASE}/api/health`)).ok) return; } catch {}
|
|
15
|
-
await new Promise(resolve => setTimeout(resolve, 100));
|
|
16
|
-
}
|
|
17
|
-
throw new Error('server did not start');
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
(async () => {
|
|
21
|
-
const source = `${fs.readFileSync(path.join(ROOT, '_site', 'server.js'), 'utf8')}\n${fs.readFileSync(path.join(ROOT, '_site', 'lib', 'server-app.js'), 'utf8')}`;
|
|
22
|
-
for (const removedSymbol of ['automation/simulate', 'automation/init', 'hook-install', 'hook-uninstall', 'dispatchProjectInit', 'DEFAULT_INIT_PROMPT_TEMPLATE', 'renderProjectInitPrompt', '/api/raw']) {
|
|
23
|
-
assert(!source.includes(removedSymbol), `removed production symbol remains: ${removedSymbol}`);
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
const spawned = spawnServer({ root: ROOT, port: PORT, dataDir, tag: 'routes-v2', extraEnv: { KB_SKIP_MIGRATION: '1' } });
|
|
27
|
-
try {
|
|
28
|
-
await waitForServer();
|
|
29
|
-
const routes = [
|
|
30
|
-
['PUT', '/api/projects'],
|
|
31
|
-
['POST', '/api/projects/project-example/init'],
|
|
32
|
-
['POST', '/api/projects/project-example/automation/simulate'],
|
|
33
|
-
['POST', '/api/projects/project-example/automation/init'],
|
|
34
|
-
['GET', '/api/projects/project-example/hook-status'],
|
|
35
|
-
['POST', '/api/projects/project-example/hook-install'],
|
|
36
|
-
['POST', '/api/projects/project-example/hook-uninstall'],
|
|
37
|
-
['GET', '/api/raw?path=README.md'],
|
|
38
|
-
];
|
|
39
|
-
for (const [method, pathname] of routes) {
|
|
40
|
-
const response = await fetch(`${BASE}${pathname}`, { method });
|
|
41
|
-
assert.strictEqual(response.status, 404, `${method} ${pathname} should be removed`);
|
|
42
|
-
}
|
|
43
|
-
console.log('legacy-routes-removed-test PASS');
|
|
44
|
-
} finally {
|
|
45
|
-
await spawned.cleanup();
|
|
46
|
-
}
|
|
47
|
-
})().catch(error => { console.error(error); process.exitCode = 1; });
|
|
1
|
+
const assert = require('assert');
|
|
2
|
+
const fs = require('fs');
|
|
3
|
+
const os = require('os');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
const { spawnServer } = require('./helpers/spawn-server');
|
|
6
|
+
|
|
7
|
+
const ROOT = path.resolve(__dirname, '..', '..');
|
|
8
|
+
const PORT = 7833;
|
|
9
|
+
const BASE = `http://127.0.0.1:${PORT}`;
|
|
10
|
+
const dataDir = fs.mkdtempSync(path.join(os.tmpdir(), 'pkb-routes-v2-'));
|
|
11
|
+
|
|
12
|
+
async function waitForServer() {
|
|
13
|
+
for (let attempt = 0; attempt < 100; attempt += 1) {
|
|
14
|
+
try { if ((await fetch(`${BASE}/api/health`)).ok) return; } catch {}
|
|
15
|
+
await new Promise(resolve => setTimeout(resolve, 100));
|
|
16
|
+
}
|
|
17
|
+
throw new Error('server did not start');
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
(async () => {
|
|
21
|
+
const source = `${fs.readFileSync(path.join(ROOT, '_site', 'server.js'), 'utf8')}\n${fs.readFileSync(path.join(ROOT, '_site', 'lib', 'server-app.js'), 'utf8')}`;
|
|
22
|
+
for (const removedSymbol of ['automation/simulate', 'automation/init', 'hook-install', 'hook-uninstall', 'dispatchProjectInit', 'DEFAULT_INIT_PROMPT_TEMPLATE', 'renderProjectInitPrompt', '/api/raw']) {
|
|
23
|
+
assert(!source.includes(removedSymbol), `removed production symbol remains: ${removedSymbol}`);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const spawned = spawnServer({ root: ROOT, port: PORT, dataDir, tag: 'routes-v2', extraEnv: { KB_SKIP_MIGRATION: '1' } });
|
|
27
|
+
try {
|
|
28
|
+
await waitForServer();
|
|
29
|
+
const routes = [
|
|
30
|
+
['PUT', '/api/projects'],
|
|
31
|
+
['POST', '/api/projects/project-example/init'],
|
|
32
|
+
['POST', '/api/projects/project-example/automation/simulate'],
|
|
33
|
+
['POST', '/api/projects/project-example/automation/init'],
|
|
34
|
+
['GET', '/api/projects/project-example/hook-status'],
|
|
35
|
+
['POST', '/api/projects/project-example/hook-install'],
|
|
36
|
+
['POST', '/api/projects/project-example/hook-uninstall'],
|
|
37
|
+
['GET', '/api/raw?path=README.md'],
|
|
38
|
+
];
|
|
39
|
+
for (const [method, pathname] of routes) {
|
|
40
|
+
const response = await fetch(`${BASE}${pathname}`, { method });
|
|
41
|
+
assert.strictEqual(response.status, 404, `${method} ${pathname} should be removed`);
|
|
42
|
+
}
|
|
43
|
+
console.log('legacy-routes-removed-test PASS');
|
|
44
|
+
} finally {
|
|
45
|
+
await spawned.cleanup();
|
|
46
|
+
}
|
|
47
|
+
})().catch(error => { console.error(error); process.exitCode = 1; });
|