@sanqianx/project-knowledge 5.0.18 → 5.0.20-rc.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.agents/plugins/marketplace.json +3 -3
- package/.claude-plugin/marketplace.json +1 -1
- package/CHANGELOG.md +14 -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-contract.js +4 -3
- 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 +8 -0
- package/_modules/knowledge-engine/src/headless-analyzer.js +2 -1
- package/_modules/knowledge-engine/src/memories.js +10 -5
- package/_modules/knowledge-engine/src/memory-eligibility.js +74 -0
- package/_modules/knowledge-engine/src/retrieval-ranking.js +19 -1
- package/_modules/knowledge-engine/src/runtime/commit-conversation-binder.js +1 -0
- package/_modules/knowledge-engine/src/runtime/commit-prompt.js +12 -9
- package/_modules/knowledge-engine/src/runtime/conversation-store.js +2 -0
- 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 -0
- package/_site/_test/candidate-product-assets-test.js +13 -0
- 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 -0
- 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 -0
- 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 -0
- 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 -0
- 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 -0
- 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 -0
- 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 +13 -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/conversation-store.js +2 -0
- package/_site/lib/data-dir.js +13 -13
- package/_site/lib/embedding-service.js +85 -85
- package/_site/lib/engine-connection.js +93 -90
- 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 -76
- package/_site/lib/llm-client.js +12 -12
- package/_site/lib/model-context-windows.js +82 -82
- package/_site/lib/module-bridge.js +874 -865
- 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 -0
- 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/server-app.js +10 -1
- 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 -72
- 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 -0
- 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 -119
- 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 -0
- package/docs/team-knowledge-mode-a-plan.md +73 -73
- package/module-runtime-manifest.json +126 -104
- package/package.json +110 -108
- 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 +175 -8
- package/vendor-manifest.json +55 -53
|
@@ -1,81 +1,81 @@
|
|
|
1
|
-
// Git status remains a read-only v2 API; manual validation/state mutation is removed.
|
|
2
|
-
const assert = require('assert');
|
|
3
|
-
const fs = require('fs');
|
|
4
|
-
const os = require('os');
|
|
5
|
-
const path = require('path');
|
|
6
|
-
const { execFileSync } = require('child_process');
|
|
7
|
-
const { spawnServer } = require('./helpers/spawn-server');
|
|
8
|
-
const { makeRepo } = require('./fixtures/make-git-repos');
|
|
9
|
-
|
|
10
|
-
const ROOT = path.resolve(__dirname, '..', '..');
|
|
11
|
-
const DATA_DIR = fs.mkdtempSync(path.join(os.tmpdir(), `kb-data-git-validation-${process.pid}-`));
|
|
12
|
-
const PORT = Number(process.env.KB_GIT_TEST_PORT || 7792);
|
|
13
|
-
const BASE_URL = `http://127.0.0.1:${PORT}`;
|
|
14
|
-
const PROJECT_ID = 'task-002-temp';
|
|
15
|
-
|
|
16
|
-
async function waitForServer() {
|
|
17
|
-
for (let attempt = 0; attempt < 100; attempt += 1) {
|
|
18
|
-
try { if ((await fetch(`${BASE_URL}/api/health`)).ok) return; } catch {}
|
|
19
|
-
await new Promise(resolve => setTimeout(resolve, 100));
|
|
20
|
-
}
|
|
21
|
-
throw new Error('server did not start');
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
async function json(method, route, body) {
|
|
25
|
-
const response = await fetch(`${BASE_URL}${route}`, {
|
|
26
|
-
method,
|
|
27
|
-
headers: body ? { 'Content-Type': 'application/json' } : undefined,
|
|
28
|
-
body: body ? JSON.stringify(body) : undefined,
|
|
29
|
-
});
|
|
30
|
-
return { response, body: await response.json() };
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
(async () => {
|
|
34
|
-
const repo = makeRepo({ kind: 'one-commit' });
|
|
35
|
-
const spawned = spawnServer({ root: ROOT, port: PORT, dataDir: DATA_DIR, tag: 'git-validation', extraEnv: { KB_AUTOMATION_FAKE_CLAUDE: '1' } });
|
|
36
|
-
let output = '';
|
|
37
|
-
spawned.child.stdout.on('data', chunk => { output += chunk; });
|
|
38
|
-
spawned.child.stderr.on('data', chunk => { output += chunk; });
|
|
39
|
-
try {
|
|
40
|
-
await waitForServer();
|
|
41
|
-
let result = await json('PATCH', '/api/settings', { knowledge: { rootPath: path.join(DATA_DIR, 'knowledge') } });
|
|
42
|
-
assert(result.response.ok, JSON.stringify(result.body));
|
|
43
|
-
result = await json('POST', '/api/projects/import', { projectId: PROJECT_ID, localPath: repo.path, displayName: 'Git status fixture' });
|
|
44
|
-
assert(result.response.status === 201, JSON.stringify(result.body));
|
|
45
|
-
|
|
46
|
-
const statePath = path.join(DATA_DIR, 'projects', PROJECT_ID, 'state.json');
|
|
47
|
-
const stateBefore = fs.readFileSync(statePath, 'utf8');
|
|
48
|
-
result = await json('GET', `/api/projects/${PROJECT_ID}/git-status`);
|
|
49
|
-
assert(result.response.ok, JSON.stringify(result.body));
|
|
50
|
-
assert.strictEqual(result.body.repoStatus, 'ok');
|
|
51
|
-
assert.strictEqual(result.body.branch, 'main');
|
|
52
|
-
assert.strictEqual(result.body.dirty, false);
|
|
53
|
-
assert.match(result.body.headCommit, /^[a-f0-9]{40}$/i);
|
|
54
|
-
assert.strictEqual(fs.readFileSync(statePath, 'utf8'), stateBefore, 'read-only status must not mutate project state');
|
|
55
|
-
|
|
56
|
-
fs.writeFileSync(path.join(repo.path, 'untracked.txt'), 'working tree change\n', 'utf8');
|
|
57
|
-
result = await json('GET', `/api/projects/${PROJECT_ID}/git-status`);
|
|
58
|
-
assert.strictEqual(result.body.dirty, true);
|
|
59
|
-
assert(result.body.changes.some(line => line.includes('untracked.txt')));
|
|
60
|
-
|
|
61
|
-
execFileSync('git', ['add', 'untracked.txt'], { cwd: repo.path });
|
|
62
|
-
execFileSync('git', ['commit', '-m', 'status fixture'], { cwd: repo.path });
|
|
63
|
-
result = await json('GET', `/api/projects/${PROJECT_ID}/git-status`);
|
|
64
|
-
assert.strictEqual(result.body.repoStatus, 'ok');
|
|
65
|
-
assert.strictEqual(result.body.dirty, false);
|
|
66
|
-
|
|
67
|
-
result = await json('POST', `/api/projects/${PROJECT_ID}/validate-git`, {});
|
|
68
|
-
assert.strictEqual(result.response.status, 404, 'manual validation route must remain deleted');
|
|
69
|
-
result = await json('GET', '/api/projects/%5Ebad/git-status');
|
|
70
|
-
assert.strictEqual(result.response.status, 400);
|
|
71
|
-
result = await json('GET', '/api/projects/project-missing/git-status');
|
|
72
|
-
assert.strictEqual(result.response.status, 404);
|
|
73
|
-
console.log('TASK-002 git validation test passed');
|
|
74
|
-
} catch (error) {
|
|
75
|
-
if (output) process.stderr.write(output);
|
|
76
|
-
throw error;
|
|
77
|
-
} finally {
|
|
78
|
-
await spawned.cleanup();
|
|
79
|
-
repo.cleanup();
|
|
80
|
-
}
|
|
81
|
-
})().catch(error => { console.error(error); process.exitCode = 1; });
|
|
1
|
+
// Git status remains a read-only v2 API; manual validation/state mutation is removed.
|
|
2
|
+
const assert = require('assert');
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const os = require('os');
|
|
5
|
+
const path = require('path');
|
|
6
|
+
const { execFileSync } = require('child_process');
|
|
7
|
+
const { spawnServer } = require('./helpers/spawn-server');
|
|
8
|
+
const { makeRepo } = require('./fixtures/make-git-repos');
|
|
9
|
+
|
|
10
|
+
const ROOT = path.resolve(__dirname, '..', '..');
|
|
11
|
+
const DATA_DIR = fs.mkdtempSync(path.join(os.tmpdir(), `kb-data-git-validation-${process.pid}-`));
|
|
12
|
+
const PORT = Number(process.env.KB_GIT_TEST_PORT || 7792);
|
|
13
|
+
const BASE_URL = `http://127.0.0.1:${PORT}`;
|
|
14
|
+
const PROJECT_ID = 'task-002-temp';
|
|
15
|
+
|
|
16
|
+
async function waitForServer() {
|
|
17
|
+
for (let attempt = 0; attempt < 100; attempt += 1) {
|
|
18
|
+
try { if ((await fetch(`${BASE_URL}/api/health`)).ok) return; } catch {}
|
|
19
|
+
await new Promise(resolve => setTimeout(resolve, 100));
|
|
20
|
+
}
|
|
21
|
+
throw new Error('server did not start');
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
async function json(method, route, body) {
|
|
25
|
+
const response = await fetch(`${BASE_URL}${route}`, {
|
|
26
|
+
method,
|
|
27
|
+
headers: body ? { 'Content-Type': 'application/json' } : undefined,
|
|
28
|
+
body: body ? JSON.stringify(body) : undefined,
|
|
29
|
+
});
|
|
30
|
+
return { response, body: await response.json() };
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
(async () => {
|
|
34
|
+
const repo = makeRepo({ kind: 'one-commit' });
|
|
35
|
+
const spawned = spawnServer({ root: ROOT, port: PORT, dataDir: DATA_DIR, tag: 'git-validation', extraEnv: { KB_AUTOMATION_FAKE_CLAUDE: '1' } });
|
|
36
|
+
let output = '';
|
|
37
|
+
spawned.child.stdout.on('data', chunk => { output += chunk; });
|
|
38
|
+
spawned.child.stderr.on('data', chunk => { output += chunk; });
|
|
39
|
+
try {
|
|
40
|
+
await waitForServer();
|
|
41
|
+
let result = await json('PATCH', '/api/settings', { knowledge: { rootPath: path.join(DATA_DIR, 'knowledge') } });
|
|
42
|
+
assert(result.response.ok, JSON.stringify(result.body));
|
|
43
|
+
result = await json('POST', '/api/projects/import', { projectId: PROJECT_ID, localPath: repo.path, displayName: 'Git status fixture' });
|
|
44
|
+
assert(result.response.status === 201, JSON.stringify(result.body));
|
|
45
|
+
|
|
46
|
+
const statePath = path.join(DATA_DIR, 'projects', PROJECT_ID, 'state.json');
|
|
47
|
+
const stateBefore = fs.readFileSync(statePath, 'utf8');
|
|
48
|
+
result = await json('GET', `/api/projects/${PROJECT_ID}/git-status`);
|
|
49
|
+
assert(result.response.ok, JSON.stringify(result.body));
|
|
50
|
+
assert.strictEqual(result.body.repoStatus, 'ok');
|
|
51
|
+
assert.strictEqual(result.body.branch, 'main');
|
|
52
|
+
assert.strictEqual(result.body.dirty, false);
|
|
53
|
+
assert.match(result.body.headCommit, /^[a-f0-9]{40}$/i);
|
|
54
|
+
assert.strictEqual(fs.readFileSync(statePath, 'utf8'), stateBefore, 'read-only status must not mutate project state');
|
|
55
|
+
|
|
56
|
+
fs.writeFileSync(path.join(repo.path, 'untracked.txt'), 'working tree change\n', 'utf8');
|
|
57
|
+
result = await json('GET', `/api/projects/${PROJECT_ID}/git-status`);
|
|
58
|
+
assert.strictEqual(result.body.dirty, true);
|
|
59
|
+
assert(result.body.changes.some(line => line.includes('untracked.txt')));
|
|
60
|
+
|
|
61
|
+
execFileSync('git', ['add', 'untracked.txt'], { cwd: repo.path });
|
|
62
|
+
execFileSync('git', ['commit', '-m', 'status fixture'], { cwd: repo.path });
|
|
63
|
+
result = await json('GET', `/api/projects/${PROJECT_ID}/git-status`);
|
|
64
|
+
assert.strictEqual(result.body.repoStatus, 'ok');
|
|
65
|
+
assert.strictEqual(result.body.dirty, false);
|
|
66
|
+
|
|
67
|
+
result = await json('POST', `/api/projects/${PROJECT_ID}/validate-git`, {});
|
|
68
|
+
assert.strictEqual(result.response.status, 404, 'manual validation route must remain deleted');
|
|
69
|
+
result = await json('GET', '/api/projects/%5Ebad/git-status');
|
|
70
|
+
assert.strictEqual(result.response.status, 400);
|
|
71
|
+
result = await json('GET', '/api/projects/project-missing/git-status');
|
|
72
|
+
assert.strictEqual(result.response.status, 404);
|
|
73
|
+
console.log('TASK-002 git validation test passed');
|
|
74
|
+
} catch (error) {
|
|
75
|
+
if (output) process.stderr.write(output);
|
|
76
|
+
throw error;
|
|
77
|
+
} finally {
|
|
78
|
+
await spawned.cleanup();
|
|
79
|
+
repo.cleanup();
|
|
80
|
+
}
|
|
81
|
+
})().catch(error => { console.error(error); process.exitCode = 1; });
|
|
@@ -1,42 +1,42 @@
|
|
|
1
|
-
const assert = require('assert');
|
|
2
|
-
const fs = require('fs');
|
|
3
|
-
const os = require('os');
|
|
4
|
-
const path = require('path');
|
|
5
|
-
const { spawnSync } = require('child_process');
|
|
6
|
-
const { installHook, readHookStatus, migrateManagedHook, LEGACY_HOOK_MARKER } = require('../lib/hook-manager');
|
|
7
|
-
|
|
8
|
-
const ROOT = path.resolve(__dirname, '..', '..');
|
|
9
|
-
const TRIGGER = path.join(ROOT, '_site', 'scripts', 'hook-trigger.js');
|
|
10
|
-
|
|
11
|
-
function git(repo, args) {
|
|
12
|
-
const result = spawnSync('git', ['-C', repo, ...args], { encoding: 'utf8', windowsHide: true, env: { ...process.env, GIT_TERMINAL_PROMPT: '0' } });
|
|
13
|
-
if (result.status !== 0) throw new Error(result.stderr || `git ${args.join(' ')} failed`);
|
|
14
|
-
return String(result.stdout || '').trim();
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
const base = fs.mkdtempSync(path.join(os.tmpdir(), 'pk-hook-worktree-'));
|
|
18
|
-
const main = path.join(base, 'main repo');
|
|
19
|
-
const linked = path.join(base, 'linked worktree');
|
|
20
|
-
fs.mkdirSync(main, { recursive: true });
|
|
21
|
-
git(main, ['init', '--initial-branch=main']);
|
|
22
|
-
git(main, ['config', 'user.email', 'hook@example.invalid']);
|
|
23
|
-
git(main, ['config', 'user.name', 'Hook Test']);
|
|
24
|
-
fs.writeFileSync(path.join(main, 'README.md'), '# main\n');
|
|
25
|
-
git(main, ['add', 'README.md']);
|
|
26
|
-
git(main, ['commit', '-m', 'initial']);
|
|
27
|
-
git(main, ['worktree', 'add', '-b', 'linked', linked]);
|
|
28
|
-
|
|
29
|
-
git(linked, ['config', 'core.hooksPath', '.custom-hooks']);
|
|
30
|
-
const installed = installHook({ repoPath: linked, projectId: 'project-worktree', triggerScriptPath: TRIGGER });
|
|
31
|
-
const authoritativeHooks = git(linked, ['rev-parse', '--path-format=absolute', '--git-path', 'hooks']);
|
|
32
|
-
assert.strictEqual(path.resolve(path.dirname(installed.hookPath)), path.resolve(authoritativeHooks));
|
|
33
|
-
assert.strictEqual(readHookStatus({ repoPath: linked, projectId: 'project-worktree' }).installed, true);
|
|
34
|
-
|
|
35
|
-
fs.writeFileSync(installed.hookPath, `#!/bin/sh\n${LEGACY_HOOK_MARKER} — old\nexit 0\n`);
|
|
36
|
-
const migrated = migrateManagedHook({ repoPath: linked, projectId: 'project-worktree', triggerScriptPath: TRIGGER });
|
|
37
|
-
assert.strictEqual(migrated.migrated, true);
|
|
38
|
-
const second = migrateManagedHook({ repoPath: linked, projectId: 'project-worktree', triggerScriptPath: TRIGGER });
|
|
39
|
-
assert.strictEqual(second.migrated, false);
|
|
40
|
-
assert.strictEqual(second.reason, 'current');
|
|
41
|
-
|
|
42
|
-
console.log('hook-worktree-test PASS');
|
|
1
|
+
const assert = require('assert');
|
|
2
|
+
const fs = require('fs');
|
|
3
|
+
const os = require('os');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
const { spawnSync } = require('child_process');
|
|
6
|
+
const { installHook, readHookStatus, migrateManagedHook, LEGACY_HOOK_MARKER } = require('../lib/hook-manager');
|
|
7
|
+
|
|
8
|
+
const ROOT = path.resolve(__dirname, '..', '..');
|
|
9
|
+
const TRIGGER = path.join(ROOT, '_site', 'scripts', 'hook-trigger.js');
|
|
10
|
+
|
|
11
|
+
function git(repo, args) {
|
|
12
|
+
const result = spawnSync('git', ['-C', repo, ...args], { encoding: 'utf8', windowsHide: true, env: { ...process.env, GIT_TERMINAL_PROMPT: '0' } });
|
|
13
|
+
if (result.status !== 0) throw new Error(result.stderr || `git ${args.join(' ')} failed`);
|
|
14
|
+
return String(result.stdout || '').trim();
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const base = fs.mkdtempSync(path.join(os.tmpdir(), 'pk-hook-worktree-'));
|
|
18
|
+
const main = path.join(base, 'main repo');
|
|
19
|
+
const linked = path.join(base, 'linked worktree');
|
|
20
|
+
fs.mkdirSync(main, { recursive: true });
|
|
21
|
+
git(main, ['init', '--initial-branch=main']);
|
|
22
|
+
git(main, ['config', 'user.email', 'hook@example.invalid']);
|
|
23
|
+
git(main, ['config', 'user.name', 'Hook Test']);
|
|
24
|
+
fs.writeFileSync(path.join(main, 'README.md'), '# main\n');
|
|
25
|
+
git(main, ['add', 'README.md']);
|
|
26
|
+
git(main, ['commit', '-m', 'initial']);
|
|
27
|
+
git(main, ['worktree', 'add', '-b', 'linked', linked]);
|
|
28
|
+
|
|
29
|
+
git(linked, ['config', 'core.hooksPath', '.custom-hooks']);
|
|
30
|
+
const installed = installHook({ repoPath: linked, projectId: 'project-worktree', triggerScriptPath: TRIGGER });
|
|
31
|
+
const authoritativeHooks = git(linked, ['rev-parse', '--path-format=absolute', '--git-path', 'hooks']);
|
|
32
|
+
assert.strictEqual(path.resolve(path.dirname(installed.hookPath)), path.resolve(authoritativeHooks));
|
|
33
|
+
assert.strictEqual(readHookStatus({ repoPath: linked, projectId: 'project-worktree' }).installed, true);
|
|
34
|
+
|
|
35
|
+
fs.writeFileSync(installed.hookPath, `#!/bin/sh\n${LEGACY_HOOK_MARKER} — old\nexit 0\n`);
|
|
36
|
+
const migrated = migrateManagedHook({ repoPath: linked, projectId: 'project-worktree', triggerScriptPath: TRIGGER });
|
|
37
|
+
assert.strictEqual(migrated.migrated, true);
|
|
38
|
+
const second = migrateManagedHook({ repoPath: linked, projectId: 'project-worktree', triggerScriptPath: TRIGGER });
|
|
39
|
+
assert.strictEqual(second.migrated, false);
|
|
40
|
+
assert.strictEqual(second.reason, 'current');
|
|
41
|
+
|
|
42
|
+
console.log('hook-worktree-test PASS');
|
|
@@ -1,41 +1,41 @@
|
|
|
1
|
-
const assert = require('assert');
|
|
2
|
-
const fs = require('fs');
|
|
3
|
-
const os = require('os');
|
|
4
|
-
const path = require('path');
|
|
5
|
-
const { regenerateIndexes } = require('../lib/index-builder');
|
|
6
|
-
|
|
7
|
-
const temp = fs.mkdtempSync(path.join(os.tmpdir(), 'index-builder-test-'));
|
|
8
|
-
|
|
9
|
-
try {
|
|
10
|
-
const modules = path.join(temp, 'modules');
|
|
11
|
-
const changes = path.join(temp, 'changes');
|
|
12
|
-
fs.mkdirSync(modules, { recursive: true });
|
|
13
|
-
fs.mkdirSync(changes, { recursive: true });
|
|
14
|
-
|
|
15
|
-
const manyTags = Array.from({ length: 40 }, (_, index) => `tag-${index}`);
|
|
16
|
-
fs.writeFileSync(path.join(modules, 'core.md'), `---\ntitle: Core | API\ntags: [${manyTags.join(', ')}]\nsourcePaths: [${manyTags.map(tag => `src/${tag}.js`).join(', ')}]\nsymbols: [${manyTags.map(tag => `symbol-${tag}`).join(', ')}]\n---\n# Core\n`, 'utf8');
|
|
17
|
-
|
|
18
|
-
for (let index = 0; index < 105; index++) {
|
|
19
|
-
const day = String((index % 28) + 1).padStart(2, '0');
|
|
20
|
-
fs.writeFileSync(path.join(changes, `change-${String(index).padStart(3, '0')}.md`), `---\ntitle: Change ${index}\ntags: [${manyTags.join(', ')}]\naffectedModules: [core, api, ui, worker, storage, release]\nupdatedAt: 2026-07-${day}\ndevelopmentIntent: ${'Long intent '.repeat(80)}\n---\n# Change ${index}\n`, 'utf8');
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
regenerateIndexes(temp);
|
|
24
|
-
const moduleIndex = fs.readFileSync(path.join(modules, '00-index.md'), 'utf8');
|
|
25
|
-
const changeIndex = fs.readFileSync(path.join(changes, '00-index.md'), 'utf8');
|
|
26
|
-
|
|
27
|
-
assert(!/^Tags:/m.test(moduleIndex), 'module index must not aggregate all tags');
|
|
28
|
-
assert(!/^Tags:/m.test(changeIndex), 'change index must not aggregate all tags');
|
|
29
|
-
assert(moduleIndex.startsWith('<!-- Generated by project-knowledge.'), 'generated indexes should identify themselves as derived files');
|
|
30
|
-
assert(moduleIndex.includes('[Core \\| API](./core.md)'), 'Markdown table links should escape title separators');
|
|
31
|
-
assert(moduleIndex.includes('tag-0, tag-1, tag-2, tag-3 (+36)'), 'tags should be capped with a remainder count');
|
|
32
|
-
assert(changeIndex.includes('Items: 105 (showing 40;'), 'large change indexes should be bounded');
|
|
33
|
-
assert.equal((changeIndex.match(/^\| \[/gm) || []).length, 40, 'only the most recent 40 entries should be listed');
|
|
34
|
-
assert(Buffer.byteLength(changeIndex, 'utf8') < 30000, 'large generated indexes should stay below 30 KiB');
|
|
35
|
-
assert(Math.max(...moduleIndex.split(/\r?\n/).map(line => line.length)) < 900, 'module index lines should stay compact');
|
|
36
|
-
assert(Math.max(...changeIndex.split(/\r?\n/).map(line => line.length)) < 900, 'change index lines should stay compact');
|
|
37
|
-
|
|
38
|
-
console.log('index-builder-test: PASS');
|
|
39
|
-
} finally {
|
|
40
|
-
fs.rmSync(temp, { recursive: true, force: true });
|
|
41
|
-
}
|
|
1
|
+
const assert = require('assert');
|
|
2
|
+
const fs = require('fs');
|
|
3
|
+
const os = require('os');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
const { regenerateIndexes } = require('../lib/index-builder');
|
|
6
|
+
|
|
7
|
+
const temp = fs.mkdtempSync(path.join(os.tmpdir(), 'index-builder-test-'));
|
|
8
|
+
|
|
9
|
+
try {
|
|
10
|
+
const modules = path.join(temp, 'modules');
|
|
11
|
+
const changes = path.join(temp, 'changes');
|
|
12
|
+
fs.mkdirSync(modules, { recursive: true });
|
|
13
|
+
fs.mkdirSync(changes, { recursive: true });
|
|
14
|
+
|
|
15
|
+
const manyTags = Array.from({ length: 40 }, (_, index) => `tag-${index}`);
|
|
16
|
+
fs.writeFileSync(path.join(modules, 'core.md'), `---\ntitle: Core | API\ntags: [${manyTags.join(', ')}]\nsourcePaths: [${manyTags.map(tag => `src/${tag}.js`).join(', ')}]\nsymbols: [${manyTags.map(tag => `symbol-${tag}`).join(', ')}]\n---\n# Core\n`, 'utf8');
|
|
17
|
+
|
|
18
|
+
for (let index = 0; index < 105; index++) {
|
|
19
|
+
const day = String((index % 28) + 1).padStart(2, '0');
|
|
20
|
+
fs.writeFileSync(path.join(changes, `change-${String(index).padStart(3, '0')}.md`), `---\ntitle: Change ${index}\ntags: [${manyTags.join(', ')}]\naffectedModules: [core, api, ui, worker, storage, release]\nupdatedAt: 2026-07-${day}\ndevelopmentIntent: ${'Long intent '.repeat(80)}\n---\n# Change ${index}\n`, 'utf8');
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
regenerateIndexes(temp);
|
|
24
|
+
const moduleIndex = fs.readFileSync(path.join(modules, '00-index.md'), 'utf8');
|
|
25
|
+
const changeIndex = fs.readFileSync(path.join(changes, '00-index.md'), 'utf8');
|
|
26
|
+
|
|
27
|
+
assert(!/^Tags:/m.test(moduleIndex), 'module index must not aggregate all tags');
|
|
28
|
+
assert(!/^Tags:/m.test(changeIndex), 'change index must not aggregate all tags');
|
|
29
|
+
assert(moduleIndex.startsWith('<!-- Generated by project-knowledge.'), 'generated indexes should identify themselves as derived files');
|
|
30
|
+
assert(moduleIndex.includes('[Core \\| API](./core.md)'), 'Markdown table links should escape title separators');
|
|
31
|
+
assert(moduleIndex.includes('tag-0, tag-1, tag-2, tag-3 (+36)'), 'tags should be capped with a remainder count');
|
|
32
|
+
assert(changeIndex.includes('Items: 105 (showing 40;'), 'large change indexes should be bounded');
|
|
33
|
+
assert.equal((changeIndex.match(/^\| \[/gm) || []).length, 40, 'only the most recent 40 entries should be listed');
|
|
34
|
+
assert(Buffer.byteLength(changeIndex, 'utf8') < 30000, 'large generated indexes should stay below 30 KiB');
|
|
35
|
+
assert(Math.max(...moduleIndex.split(/\r?\n/).map(line => line.length)) < 900, 'module index lines should stay compact');
|
|
36
|
+
assert(Math.max(...changeIndex.split(/\r?\n/).map(line => line.length)) < 900, 'change index lines should stay compact');
|
|
37
|
+
|
|
38
|
+
console.log('index-builder-test: PASS');
|
|
39
|
+
} finally {
|
|
40
|
+
fs.rmSync(temp, { recursive: true, force: true });
|
|
41
|
+
}
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
const assert = require('node:assert/strict');
|
|
3
|
-
const fs = require('node:fs');
|
|
4
|
-
const path = require('node:path');
|
|
5
|
-
const source = fs.readFileSync(path.resolve(__dirname, '../../ui/product.js'), 'utf8');
|
|
6
|
-
for (const field of ['upstreamMessage', 'failureKind', 'upstreamCode', 'requestId', 'attempt', 'httpStatus', 'durationMs']) {
|
|
7
|
-
assert.ok(source.includes(`esc(r.error.details.${field}`), `index detail must be escaped: ${field}`);
|
|
8
|
-
}
|
|
9
|
-
assert.ok(source.includes("['failed', 'index-pending'].includes(r.state)"));
|
|
10
|
-
console.log('index-error-display-contract-test PASS (real rendering/export covered by engine test:web:faults)');
|
|
1
|
+
'use strict';
|
|
2
|
+
const assert = require('node:assert/strict');
|
|
3
|
+
const fs = require('node:fs');
|
|
4
|
+
const path = require('node:path');
|
|
5
|
+
const source = fs.readFileSync(path.resolve(__dirname, '../../ui/product.js'), 'utf8');
|
|
6
|
+
for (const field of ['upstreamMessage', 'failureKind', 'upstreamCode', 'requestId', 'attempt', 'httpStatus', 'durationMs']) {
|
|
7
|
+
assert.ok(source.includes(`esc(r.error.details.${field}`), `index detail must be escaped: ${field}`);
|
|
8
|
+
}
|
|
9
|
+
assert.ok(source.includes("['failed', 'index-pending'].includes(r.state)"));
|
|
10
|
+
console.log('index-error-display-contract-test PASS (real rendering/export covered by engine test:web:faults)');
|
|
@@ -139,85 +139,85 @@ async function main() {
|
|
|
139
139
|
});
|
|
140
140
|
const codexResult = await codexManager.operate('update', ['codex']);
|
|
141
141
|
assert(codexResult[0].ok, 'Codex update should complete');
|
|
142
|
-
assert(codexCalls.some(call => call.args.join(' ') === 'plugin marketplace upgrade project-knowledge'), 'Codex should refresh its marketplace');
|
|
143
|
-
assert(codexCalls.some(call => call.args.join(' ') === 'plugin remove project-knowledge@project-knowledge'), 'Codex should remove the cached plugin during update');
|
|
144
|
-
assert(codexCalls.some(call => call.args.join(' ') === 'plugin add project-knowledge@project-knowledge'), 'Codex should install the refreshed plugin');
|
|
145
|
-
|
|
146
|
-
const chainedCodexConfig = path.join(TEMP, 'codex-config.toml');
|
|
147
|
-
const oldNotifier = Buffer.from(JSON.stringify(['node', 'C:/existing-notify.js'])).toString('base64');
|
|
148
|
-
fs.writeFileSync(chainedCodexConfig, `notify = ["node", "C:/tools/devtask-radar/connectors/codex/notify-hook.js", "--previous-notify", "${oldNotifier}"]\n`, 'utf8');
|
|
149
|
-
const chainedManager = new IntegrationManager({
|
|
150
|
-
rootDir: ROOT,
|
|
151
|
-
homeDir: path.join(TEMP, 'codex-home'),
|
|
152
|
-
bridgeHomeDir: path.join(TEMP, 'bridge-home'),
|
|
153
|
-
codexConfigFile: chainedCodexConfig,
|
|
154
|
-
});
|
|
155
|
-
const chained = await chainedManager.installCaptureCodex();
|
|
156
|
-
assert(chained.installed && !chained.conflict, 'a compatible DevTask Radar notifier should chain Bridge instead of blocking capture');
|
|
157
|
-
const chainedConfig = JSON.parse(fs.readFileSync(chainedCodexConfig, 'utf8').replace(/^\s*notify\s*=\s*/, '').trim());
|
|
158
|
-
const nextIndex = chainedConfig.indexOf('--next-base64');
|
|
142
|
+
assert(codexCalls.some(call => call.args.join(' ') === 'plugin marketplace upgrade project-knowledge'), 'Codex should refresh its marketplace');
|
|
143
|
+
assert(codexCalls.some(call => call.args.join(' ') === 'plugin remove project-knowledge@project-knowledge'), 'Codex should remove the cached plugin during update');
|
|
144
|
+
assert(codexCalls.some(call => call.args.join(' ') === 'plugin add project-knowledge@project-knowledge'), 'Codex should install the refreshed plugin');
|
|
145
|
+
|
|
146
|
+
const chainedCodexConfig = path.join(TEMP, 'codex-config.toml');
|
|
147
|
+
const oldNotifier = Buffer.from(JSON.stringify(['node', 'C:/existing-notify.js'])).toString('base64');
|
|
148
|
+
fs.writeFileSync(chainedCodexConfig, `notify = ["node", "C:/tools/devtask-radar/connectors/codex/notify-hook.js", "--previous-notify", "${oldNotifier}"]\n`, 'utf8');
|
|
149
|
+
const chainedManager = new IntegrationManager({
|
|
150
|
+
rootDir: ROOT,
|
|
151
|
+
homeDir: path.join(TEMP, 'codex-home'),
|
|
152
|
+
bridgeHomeDir: path.join(TEMP, 'bridge-home'),
|
|
153
|
+
codexConfigFile: chainedCodexConfig,
|
|
154
|
+
});
|
|
155
|
+
const chained = await chainedManager.installCaptureCodex();
|
|
156
|
+
assert(chained.installed && !chained.conflict, 'a compatible DevTask Radar notifier should chain Bridge instead of blocking capture');
|
|
157
|
+
const chainedConfig = JSON.parse(fs.readFileSync(chainedCodexConfig, 'utf8').replace(/^\s*notify\s*=\s*/, '').trim());
|
|
158
|
+
const nextIndex = chainedConfig.indexOf('--next-base64');
|
|
159
159
|
assert(nextIndex >= 0, 'managed fan-out must preserve the compatible notifier as its independent branch');
|
|
160
|
-
const nextNotify = JSON.parse(Buffer.from(chainedConfig[nextIndex + 1], 'base64').toString('utf8'));
|
|
160
|
+
const nextNotify = JSON.parse(Buffer.from(chainedConfig[nextIndex + 1], 'base64').toString('utf8'));
|
|
161
161
|
assert(nextNotify[1] === 'C:/tools/devtask-radar/connectors/codex/notify-hook.js', 'Radar remains the existing independent notifier');
|
|
162
162
|
const bridgeIndex = chainedConfig.indexOf('--bridge-base64');
|
|
163
163
|
const bridgeNotify = JSON.parse(Buffer.from(chainedConfig[bridgeIndex + 1], 'base64').toString('utf8'));
|
|
164
164
|
assert(bridgeNotify[1] === path.join(TEMP, 'bridge-home', 'bin', 'bridge-hook.cjs'), 'capture must receive the payload through the current Bridge shim');
|
|
165
|
-
const chainedStatus = await chainedManager.statusCaptureCodex();
|
|
166
|
-
assert(chainedStatus.installed && !chainedStatus.conflict, 'status should recognize the compatible chained notifier');
|
|
167
|
-
|
|
168
|
-
const nestedCodexConfig = path.join(TEMP, 'nested-codex-config.toml');
|
|
169
|
-
const nestedDevTask = ['node', 'C:/tools/devtask-radar/connectors/codex/notify-hook.js', '--next-base64', oldNotifier];
|
|
165
|
+
const chainedStatus = await chainedManager.statusCaptureCodex();
|
|
166
|
+
assert(chainedStatus.installed && !chainedStatus.conflict, 'status should recognize the compatible chained notifier');
|
|
167
|
+
|
|
168
|
+
const nestedCodexConfig = path.join(TEMP, 'nested-codex-config.toml');
|
|
169
|
+
const nestedDevTask = ['node', 'C:/tools/devtask-radar/connectors/codex/notify-hook.js', '--next-base64', oldNotifier];
|
|
170
170
|
const desktopExecutable = path.join(TEMP, 'OpenAI', 'Codex', 'runtimes', 'cua_node', 'test-runtime', 'bin', 'node_modules', '@oai', 'sky', 'bin', 'windows', 'codex-computer-use.exe');
|
|
171
171
|
fs.mkdirSync(path.dirname(desktopExecutable), { recursive: true });
|
|
172
172
|
fs.writeFileSync(desktopExecutable, '');
|
|
173
173
|
fs.writeFileSync(nestedCodexConfig, `notify = ${JSON.stringify([desktopExecutable, 'turn-ended', '--previous-notify', JSON.stringify(nestedDevTask)])}\n`, 'utf8');
|
|
174
|
-
const nestedManager = new IntegrationManager({
|
|
175
|
-
rootDir: ROOT,
|
|
176
|
-
homeDir: path.join(TEMP, 'nested-codex-home'),
|
|
177
|
-
bridgeHomeDir: path.join(TEMP, 'nested-bridge-home'),
|
|
178
|
-
codexConfigFile: nestedCodexConfig,
|
|
179
|
-
});
|
|
180
|
-
const nested = await nestedManager.installCaptureCodex();
|
|
181
|
-
assert(nested.installed && !nested.conflict, 'a nested DevTask Radar notifier should be preserved through the fan-out wrapper');
|
|
182
|
-
const nestedConfig = JSON.parse(fs.readFileSync(nestedCodexConfig, 'utf8').replace(/^\s*notify\s*=\s*/, '').trim());
|
|
174
|
+
const nestedManager = new IntegrationManager({
|
|
175
|
+
rootDir: ROOT,
|
|
176
|
+
homeDir: path.join(TEMP, 'nested-codex-home'),
|
|
177
|
+
bridgeHomeDir: path.join(TEMP, 'nested-bridge-home'),
|
|
178
|
+
codexConfigFile: nestedCodexConfig,
|
|
179
|
+
});
|
|
180
|
+
const nested = await nestedManager.installCaptureCodex();
|
|
181
|
+
assert(nested.installed && !nested.conflict, 'a nested DevTask Radar notifier should be preserved through the fan-out wrapper');
|
|
182
|
+
const nestedConfig = JSON.parse(fs.readFileSync(nestedCodexConfig, 'utf8').replace(/^\s*notify\s*=\s*/, '').trim());
|
|
183
183
|
assert(nestedConfig[0] === desktopExecutable, 'the active Codex desktop wrapper keeps its position');
|
|
184
184
|
assert(nestedConfig.join(' ').includes('codex-notify-fanout.cjs'), 'nested notifier chain should use the Project Knowledge fan-out wrapper');
|
|
185
|
-
const nestedStatus = await nestedManager.statusCaptureCodex();
|
|
186
|
-
assert(nestedStatus.installed && !nestedStatus.conflict, 'status should recognize the managed nested fan-out wrapper');
|
|
187
|
-
|
|
188
|
-
// Codex Desktop computer-use wraps the existing notifier as a JSON array in
|
|
189
|
-
// --previous-notify. The managed fan-out is still installed and must not be
|
|
190
|
-
// misreported as a third-party conflict or wrapped a second time.
|
|
191
|
-
const desktopWrappedConfig = path.join(TEMP, 'desktop-wrapped-codex-config.toml');
|
|
185
|
+
const nestedStatus = await nestedManager.statusCaptureCodex();
|
|
186
|
+
assert(nestedStatus.installed && !nestedStatus.conflict, 'status should recognize the managed nested fan-out wrapper');
|
|
187
|
+
|
|
188
|
+
// Codex Desktop computer-use wraps the existing notifier as a JSON array in
|
|
189
|
+
// --previous-notify. The managed fan-out is still installed and must not be
|
|
190
|
+
// misreported as a third-party conflict or wrapped a second time.
|
|
191
|
+
const desktopWrappedConfig = path.join(TEMP, 'desktop-wrapped-codex-config.toml');
|
|
192
192
|
const desktopWrappedArgs = [desktopExecutable, 'turn-ended', '--previous-notify', JSON.stringify(nestedConfig)];
|
|
193
|
-
fs.writeFileSync(desktopWrappedConfig, `notify = ${JSON.stringify(desktopWrappedArgs)}\n`, 'utf8');
|
|
194
|
-
const desktopWrappedManager = new IntegrationManager({
|
|
195
|
-
rootDir: ROOT,
|
|
196
|
-
homeDir: path.join(TEMP, 'desktop-wrapped-codex-home'),
|
|
197
|
-
bridgeHomeDir: path.join(TEMP, 'nested-bridge-home'),
|
|
198
|
-
codexConfigFile: desktopWrappedConfig,
|
|
199
|
-
});
|
|
200
|
-
const desktopWrappedStatus = await desktopWrappedManager.statusCaptureCodex();
|
|
201
|
-
assert(desktopWrappedStatus.installed && !desktopWrappedStatus.conflict,
|
|
202
|
-
'Codex Desktop outer wrapper should preserve managed capture health');
|
|
203
|
-
const beforeDesktopInstall = fs.readFileSync(desktopWrappedConfig, 'utf8');
|
|
204
|
-
const desktopWrappedInstall = await desktopWrappedManager.installCaptureCodex();
|
|
205
|
-
assert(desktopWrappedInstall.installed && !desktopWrappedInstall.conflict,
|
|
206
|
-
'one-click repair should recognize the nested managed fan-out');
|
|
207
|
-
assert(fs.readFileSync(desktopWrappedConfig, 'utf8') === beforeDesktopInstall,
|
|
208
|
-
'healthy Codex Desktop wrapper must remain byte-for-byte unchanged');
|
|
209
|
-
|
|
210
|
-
const bridgeOnlyConfig = path.join(TEMP, 'bridge-only-codex-config.toml');
|
|
211
|
-
const bridgeOnlyManager = new IntegrationManager({
|
|
212
|
-
rootDir: ROOT,
|
|
213
|
-
homeDir: path.join(TEMP, 'bridge-only-codex-home'),
|
|
214
|
-
bridgeHomeDir: path.join(TEMP, 'bridge-only-bridge-home'),
|
|
215
|
-
codexConfigFile: bridgeOnlyConfig,
|
|
216
|
-
});
|
|
217
|
-
const bridgeOnly = await bridgeOnlyManager.installCaptureCodex();
|
|
218
|
-
assert(bridgeOnly.installed && !bridgeOnly.conflict, 'a fresh Codex install should use the payload-preserving fan-out wrapper');
|
|
219
|
-
const bridgeOnlyArgs = JSON.parse(fs.readFileSync(bridgeOnlyConfig, 'utf8').replace(/^\s*notify\s*=\s*/, '').trim());
|
|
220
|
-
assert(bridgeOnlyArgs.join(' ').includes('codex-notify-fanout.cjs'), 'fresh Codex install should use the Project Knowledge fan-out wrapper');
|
|
193
|
+
fs.writeFileSync(desktopWrappedConfig, `notify = ${JSON.stringify(desktopWrappedArgs)}\n`, 'utf8');
|
|
194
|
+
const desktopWrappedManager = new IntegrationManager({
|
|
195
|
+
rootDir: ROOT,
|
|
196
|
+
homeDir: path.join(TEMP, 'desktop-wrapped-codex-home'),
|
|
197
|
+
bridgeHomeDir: path.join(TEMP, 'nested-bridge-home'),
|
|
198
|
+
codexConfigFile: desktopWrappedConfig,
|
|
199
|
+
});
|
|
200
|
+
const desktopWrappedStatus = await desktopWrappedManager.statusCaptureCodex();
|
|
201
|
+
assert(desktopWrappedStatus.installed && !desktopWrappedStatus.conflict,
|
|
202
|
+
'Codex Desktop outer wrapper should preserve managed capture health');
|
|
203
|
+
const beforeDesktopInstall = fs.readFileSync(desktopWrappedConfig, 'utf8');
|
|
204
|
+
const desktopWrappedInstall = await desktopWrappedManager.installCaptureCodex();
|
|
205
|
+
assert(desktopWrappedInstall.installed && !desktopWrappedInstall.conflict,
|
|
206
|
+
'one-click repair should recognize the nested managed fan-out');
|
|
207
|
+
assert(fs.readFileSync(desktopWrappedConfig, 'utf8') === beforeDesktopInstall,
|
|
208
|
+
'healthy Codex Desktop wrapper must remain byte-for-byte unchanged');
|
|
209
|
+
|
|
210
|
+
const bridgeOnlyConfig = path.join(TEMP, 'bridge-only-codex-config.toml');
|
|
211
|
+
const bridgeOnlyManager = new IntegrationManager({
|
|
212
|
+
rootDir: ROOT,
|
|
213
|
+
homeDir: path.join(TEMP, 'bridge-only-codex-home'),
|
|
214
|
+
bridgeHomeDir: path.join(TEMP, 'bridge-only-bridge-home'),
|
|
215
|
+
codexConfigFile: bridgeOnlyConfig,
|
|
216
|
+
});
|
|
217
|
+
const bridgeOnly = await bridgeOnlyManager.installCaptureCodex();
|
|
218
|
+
assert(bridgeOnly.installed && !bridgeOnly.conflict, 'a fresh Codex install should use the payload-preserving fan-out wrapper');
|
|
219
|
+
const bridgeOnlyArgs = JSON.parse(fs.readFileSync(bridgeOnlyConfig, 'utf8').replace(/^\s*notify\s*=\s*/, '').trim());
|
|
220
|
+
assert(bridgeOnlyArgs.join(' ').includes('codex-notify-fanout.cjs'), 'fresh Codex install should use the Project Knowledge fan-out wrapper');
|
|
221
221
|
|
|
222
222
|
const integrationLogs = [];
|
|
223
223
|
const failingManager = new IntegrationManager({
|