@sanqianx/project-knowledge 5.0.19 → 5.0.20-rc.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (200) hide show
  1. package/.agents/plugins/marketplace.json +3 -3
  2. package/.claude-plugin/marketplace.json +1 -1
  3. package/CHANGELOG.md +5 -0
  4. package/LICENSE +201 -201
  5. package/NOTICE +5 -5
  6. package/README.md +349 -349
  7. package/_modules/knowledge-engine/README.md +4 -0
  8. package/_modules/knowledge-engine/src/analysis-failures.js +39 -0
  9. package/_modules/knowledge-engine/src/analysis-worker.js +6 -2
  10. package/_modules/knowledge-engine/src/coordinator.js +7 -0
  11. package/_modules/knowledge-engine/src/headless-analyzer.js +2 -1
  12. package/_modules/knowledge-engine/src/server.js +1 -1
  13. package/_site/_test/agent-capture-mcp-ui-test.js +67 -67
  14. package/_site/_test/ai-profile-test.js +75 -75
  15. package/_site/_test/ai-vendor-presets-test.js +91 -91
  16. package/_site/_test/automatic-context-test.js +70 -70
  17. package/_site/_test/bin-cli-test.js +230 -230
  18. package/_site/_test/bridge-adapter-test.js +95 -95
  19. package/_site/_test/bridge-consumer-service-test.js +310 -310
  20. package/_site/_test/candidate-bin-content-test.js +35 -35
  21. package/_site/_test/candidate-product-assets-test.js +13 -13
  22. package/_site/_test/candidate-release-contract-test.js +14 -14
  23. package/_site/_test/capture-hook-dedup-test.js +158 -158
  24. package/_site/_test/claude-executable-discovery-test.js +28 -28
  25. package/_site/_test/claude-session-lifecycle-sweep-test.js +252 -252
  26. package/_site/_test/cli-instance-management-test.js +62 -62
  27. package/_site/_test/client-capture-restart-e2e-test.js +189 -189
  28. package/_site/_test/codex-conversation-projection-test.js +60 -60
  29. package/_site/_test/codex-marketplace-scoped-path-test.js +186 -186
  30. package/_site/_test/codex-notify-capture-e2e-test.js +101 -101
  31. package/_site/_test/codex-notify-fanout-test.js +134 -134
  32. package/_site/_test/codex-notify-upgrade-test.js +253 -253
  33. package/_site/_test/commit-boundary-freeze-test.js +147 -147
  34. package/_site/_test/commit-conversation-binding-test.js +4 -4
  35. package/_site/_test/configuration-generation-test.js +22 -22
  36. package/_site/_test/context-pack-test.js +200 -200
  37. package/_site/_test/conversation-api-test.js +10 -10
  38. package/_site/_test/conversation-origin-test.js +15 -15
  39. package/_site/_test/cross-repo-isolation-e2e-test.js +187 -187
  40. package/_site/_test/embedding-config-test.js +32 -32
  41. package/_site/_test/fixtures/fake-browser-exit.js +10 -10
  42. package/_site/_test/fixtures/fake-browser-hang.js +10 -10
  43. package/_site/_test/fixtures/integration-busy-worker.cjs +6 -6
  44. package/_site/_test/fixtures/make-git-repos.js +92 -92
  45. package/_site/_test/fixtures/retrieval-gold-queries.json +32 -32
  46. package/_site/_test/fresh-hook-reimport-test.js +53 -53
  47. package/_site/_test/fresh-product-data-test.js +56 -56
  48. package/_site/_test/git-path-compatibility-test.js +80 -80
  49. package/_site/_test/git-validation-test.js +81 -81
  50. package/_site/_test/hook-worktree-test.js +42 -42
  51. package/_site/_test/index-builder-test.js +41 -41
  52. package/_site/_test/index-error-display-contract-test.js +10 -10
  53. package/_site/_test/integration-adapters-test.js +67 -67
  54. package/_site/_test/integration-capture-setup-test.js +118 -118
  55. package/_site/_test/integration-runtime-binding-a2-test.js +566 -566
  56. package/_site/_test/integration-runtime-binding-a3-test.js +227 -227
  57. package/_site/_test/integration-runtime-binding-test.js +473 -473
  58. package/_site/_test/integration-status-scan-test.js +29 -29
  59. package/_site/_test/kb-framework-test.js +57 -57
  60. package/_site/_test/kbpath-follow-test.js +63 -63
  61. package/_site/_test/knowledge-db-test.js +144 -144
  62. package/_site/_test/knowledge-engine-client-test.js +109 -109
  63. package/_site/_test/knowledge-maintenance-test.js +40 -40
  64. package/_site/_test/knowledge-panel-ui-contract-test.js +112 -0
  65. package/_site/_test/knowledge-scopes-test.js +37 -37
  66. package/_site/_test/knowledge-storage-location-test.js +28 -28
  67. package/_site/_test/launcher-readiness-test.js +80 -80
  68. package/_site/_test/legacy-claude-session-alias-test.js +106 -106
  69. package/_site/_test/legacy-routes-removed-test.js +47 -47
  70. package/_site/_test/legacy-workbench-exclusion-test.js +92 -92
  71. package/_site/_test/live-gateway-test.js +48 -48
  72. package/_site/_test/local-marketplace-test.js +16 -16
  73. package/_site/_test/log-redaction-test.js +58 -58
  74. package/_site/_test/markdown-maintenance-test.js +11 -11
  75. package/_site/_test/model-context-windows-test.js +89 -89
  76. package/_site/_test/module-agent-start-proxy-test.js +65 -65
  77. package/_site/_test/module-artifact-integrity-test.js +27 -27
  78. package/_site/_test/module-boundary-test.js +30 -30
  79. package/_site/_test/module-bridge-eventbridge-test.js +202 -202
  80. package/_site/_test/module-dynamic-port-test.js +118 -118
  81. package/_site/_test/module-index-proxy-test.js +33 -33
  82. package/_site/_test/module-model-configuration-test.js +44 -44
  83. package/_site/_test/module-process-lifecycle-test.js +35 -35
  84. package/_site/_test/module-stream-proxy-test.js +34 -34
  85. package/_site/_test/module-terminal-restart-test.js +63 -63
  86. package/_site/_test/module-wiring-unbind-race-test.js +59 -59
  87. package/_site/_test/native-context-integrations-test.js +86 -86
  88. package/_site/_test/orphaned-claim-recovery-test.js +80 -80
  89. package/_site/_test/package-boundary-test.js +43 -42
  90. package/_site/_test/package-startup-test.js +56 -56
  91. package/_site/_test/packaged-ui-smoke-test.js +88 -88
  92. package/_site/_test/pr-consumer-contract-test.js +55 -55
  93. package/_site/_test/product-brand-contract-test.js +25 -25
  94. package/_site/_test/product-diagnostics-ui-test.js +93 -93
  95. package/_site/_test/product-import-ui-test.js +104 -104
  96. package/_site/_test/product-progress-status-test.js +12 -1
  97. package/_site/_test/project-remove-running-guard-test.js +79 -79
  98. package/_site/_test/project-store-test.js +90 -90
  99. package/_site/_test/registry-events-test.js +38 -38
  100. package/_site/_test/release-version-sync-test.js +115 -115
  101. package/_site/_test/requirement-binding-test.js +113 -113
  102. package/_site/_test/runtime-endpoint-test.js +64 -64
  103. package/_site/_test/scanner-test.js +83 -83
  104. package/_site/_test/server-security-test.js +96 -96
  105. package/_site/_test/sessions-stream-test.js +93 -93
  106. package/_site/_test/shared-scope-root-binding-test.js +67 -67
  107. package/_site/_test/simple-import-test.js +98 -98
  108. package/_site/_test/startup-analysis-disabled-test.js +6 -6
  109. package/_site/_test/team-knowledge-store-sparse-checkout-test.js +196 -196
  110. package/_site/_test/team-knowledge-store-test.js +132 -132
  111. package/_site/_test/test-server-cleanup-test.js +35 -35
  112. package/_site/_test/test-server-module-isolation-test.js +37 -37
  113. package/_site/_test/tracking-start-test.js +35 -35
  114. package/_site/_test/workbench-context-receipts-test.js +22 -22
  115. package/_site/_test/workspace-ui-contract-test.js +31 -31
  116. package/_site/_test/zcode-capture-integration-test.js +108 -108
  117. package/_site/lib/ai-vendor-presets.js +83 -83
  118. package/_site/lib/ai-workspace.js +3 -3
  119. package/_site/lib/atomic-file.js +204 -204
  120. package/_site/lib/bridge-adapter.js +260 -260
  121. package/_site/lib/capture-hook-config.js +108 -108
  122. package/_site/lib/claude-cli-runner.js +89 -89
  123. package/_site/lib/cli-instances.js +43 -43
  124. package/_site/lib/codex-context-status.js +17 -17
  125. package/_site/lib/codex-notify-config.js +193 -193
  126. package/_site/lib/commit-conversation-binder.js +12 -12
  127. package/_site/lib/commit-reconciler.js +38 -38
  128. package/_site/lib/context-pack-builder.js +299 -299
  129. package/_site/lib/conversation-exclusions.js +204 -204
  130. package/_site/lib/conversation-query-service.js +17 -17
  131. package/_site/lib/data-dir.js +13 -13
  132. package/_site/lib/embedding-service.js +85 -85
  133. package/_site/lib/engine-connection.js +93 -93
  134. package/_site/lib/engine-knowledge-client.js +72 -72
  135. package/_site/lib/folder-picker-output.js +3 -3
  136. package/_site/lib/git-path.js +14 -14
  137. package/_site/lib/github-team-store.js +3 -3
  138. package/_site/lib/index-builder.js +157 -157
  139. package/_site/lib/integration-status-scan.js +99 -99
  140. package/_site/lib/integration-status-worker.js +14 -14
  141. package/_site/lib/kb-framework.js +15 -15
  142. package/_site/lib/knowledge-db.js +363 -363
  143. package/_site/lib/knowledge-schema.js +121 -121
  144. package/_site/lib/live-gateway.js +78 -78
  145. package/_site/lib/llm-client.js +12 -12
  146. package/_site/lib/model-context-windows.js +82 -82
  147. package/_site/lib/module-bridge.js +874 -874
  148. package/_site/lib/module-port-resolver.js +57 -57
  149. package/_site/lib/post-commit-automation.js +54 -54
  150. package/_site/lib/product-data.js +35 -35
  151. package/_site/lib/project-lifecycle-service.js +14 -14
  152. package/_site/lib/project-registry-store.js +129 -129
  153. package/_site/lib/prompt-registry.js +148 -148
  154. package/_site/lib/registry-events.js +16 -16
  155. package/_site/lib/requirement-adapters.js +10 -10
  156. package/_site/lib/requirement-binder.js +112 -112
  157. package/_site/lib/runtime-control.js +89 -89
  158. package/_site/lib/runtime-endpoint.js +5 -5
  159. package/_site/lib/zcode-context-integration.js +60 -60
  160. package/_site/scripts/codex-notify-fanout.cjs +95 -95
  161. package/_site/scripts/install-module-candidates.js +69 -69
  162. package/_site/scripts/pack-module-candidates.js +89 -89
  163. package/_site/scripts/sync-product-content.js +28 -28
  164. package/_site/scripts/sync-release-version.js +164 -164
  165. package/_site/scripts/vendor-modules.js +49 -49
  166. package/_site/scripts/verify-module-runtime.js +50 -50
  167. package/_site/scripts/verify-product-runtime.js +36 -36
  168. package/_site/scripts/verify-retrieval-gold.js +83 -83
  169. package/_site/server.js +15 -15
  170. package/_site/start.bat +26 -26
  171. package/_site/stop.bat +11 -11
  172. package/bin/project-knowledge-integrations.js +254 -254
  173. package/bin/project-knowledge-kb.js +67 -67
  174. package/bin/project-knowledge-mcp.js +8 -8
  175. package/bin/project-knowledge.js +170 -170
  176. package/claude-prompts.json +21 -21
  177. package/docs/README.zh-CN.md +205 -205
  178. package/docs/assets/architecture.svg +181 -181
  179. package/docs/assets/logo.svg +49 -49
  180. package/docs/fresh-v5-start.md +42 -42
  181. package/docs/leader-glm-workbench-playbook.md +122 -122
  182. package/docs/project-registry-schema.md +11 -11
  183. package/docs/release-5.0.16-acceptance.md +176 -176
  184. package/docs/release-5.0.17-acceptance.md +117 -117
  185. package/docs/release-5.0.19-acceptance.md +39 -25
  186. package/docs/team-knowledge-mode-a-plan.md +73 -73
  187. package/module-runtime-manifest.json +53 -33
  188. package/package.json +3 -2
  189. package/plugins/project-knowledge/.claude-plugin/plugin.json +1 -1
  190. package/plugins/project-knowledge/.codex-plugin/plugin.json +1 -1
  191. package/plugins/project-knowledge/.mcp.json +1 -1
  192. package/plugins/project-knowledge/opencode/project-knowledge.md +3 -3
  193. package/plugins/project-knowledge/skills/project-knowledge/SKILL.md +28 -28
  194. package/templates/change.md +30 -30
  195. package/templates/project-readme.md +18 -18
  196. package/ui/index.html +1 -1
  197. package/ui/knowledge-panel.html +118 -0
  198. package/ui/product.css +8 -0
  199. package/ui/product.js +146 -4
  200. package/vendor-manifest.json +55 -54
@@ -1,80 +1,80 @@
1
- const assert = require('assert/strict');
2
- const fs = require('fs');
3
- const os = require('os');
4
- const path = require('path');
5
- const { spawnSync } = require('child_process');
6
- const { resolveGitPath } = require('../lib/git-path');
7
- const { StorageLayout } = require('../lib/storage-layout');
8
- const { SettingsStore } = require('../lib/settings-store');
9
- const { ProjectRegistryStore } = require('../lib/project-registry-store');
10
- const { ProjectStore } = require('../lib/project-store');
11
- const { ProjectLifecycleService } = require('../lib/project-lifecycle-service');
12
- const { BridgeAdapter } = require('../lib/bridge-adapter');
13
- const { Logger, LogRepository } = require('../lib/structured-logger');
14
- const hooks = require('../lib/hook-manager');
15
-
16
- (async () => {
17
- const root = fs.mkdtempSync(path.join(os.tmpdir(), 'kb-git-path-'));
18
- const repo = path.join(root, '中文 空格项目'); fs.mkdirSync(repo);
19
- const git = (args, cwd = repo) => {
20
- const result = spawnSync('git', args, { cwd, encoding: 'utf8', windowsHide: true });
21
- assert.equal(result.status, 0, result.stderr); return result.stdout.trim();
22
- };
23
- for (const invalid of ['', '--path-format=absolute\n.git/hooks', '.git/hooks\nother', '--unsupported', '\0']) {
24
- assert.throws(() => resolveGitPath(repo, invalid), { code: 'INVALID_ARGUMENT' });
25
- }
26
- assert.equal(resolveGitPath(repo, '.git/hooks\r\n'), path.join(repo, '.git', 'hooks'));
27
- git(['init', '-q']); git(['config', 'user.name', 'Git Compatibility']); git(['config', 'user.email', 'git-compat@example.invalid']);
28
- fs.writeFileSync(path.join(repo, 'README.md'), '# baseline\n'); git(['add', '.']); git(['commit', '-qm', 'baseline']);
29
- const layout = new StorageLayout({ dataDir: path.join(root, 'data') });
30
- const settingsStore = new SettingsStore({ layout });
31
- const registryStore = new ProjectRegistryStore({ layout });
32
- const projectStore = new ProjectStore({ layout });
33
- await settingsStore.initialize({ knowledge: { rootPath: path.join(root, 'knowledge') } });
34
- await registryStore.initialize();
35
- const bridgeAdapter = new BridgeAdapter({ dataDir: layout.getDataDir(), bridgeHomeDir: path.join(root, 'bridge') });
36
- assert.ok(bridgeAdapter.isAvailable());
37
- const logger = new Logger({ layout });
38
- const triggerScriptPath = path.resolve(__dirname, '../scripts/hook-trigger.js');
39
- const options = { layout, settingsStore, registryStore, projectStore, bridgeAdapter, logger, triggerScriptPath };
40
- const lifecycle = new ProjectLifecycleService(options);
41
- const input = { localPath: repo, knowledgePath: path.join(root, 'knowledge', 'fresh'), projectId: 'project-git-compat' };
42
- const preflight = await lifecycle.preflightImport(input);
43
- assert.equal(preflight.ready, true);
44
- assert.equal(preflight.checks.git.commonDir, path.join(repo, '.git'));
45
- const imported = await lifecycle.importProject(input);
46
- assert.equal(imported.hook.hookPath, path.join(repo, '.git', 'hooks', 'post-commit'));
47
- assert.equal(imported.config.repoIdentity.commonDir, path.join(repo, '.git'));
48
- const nested = path.join(repo, 'src'); fs.mkdirSync(nested);
49
- assert.equal((await bridgeAdapter.resolveRepoIdentity(nested)).repoIdentity.commonDir, imported.config.repoIdentity.commonDir);
50
- fs.appendFileSync(path.join(repo, 'README.md'), 'commit after import\n'); git(['add', '.']); git(['commit', '-qm', 'captured update']);
51
- const sha = git(['rev-parse', 'HEAD']);
52
- const journal = await bridgeAdapter.readEvents({ after: 0, limit: 100 });
53
- const boundary = journal.events.find(event => event.commitSha === sha && event.schema === 'git-commit-boundary/v1');
54
- assert.ok(boundary, 'real Git post-commit Hook must append a durable boundary');
55
- assert.equal(boundary.repoIdentity.commonDir, path.join(repo, '.git'));
56
- assert.equal(boundary.projectId, input.projectId);
57
- await lifecycle.deleteProject(input.projectId);
58
- const brokenRegistry = Object.create(registryStore);
59
- brokenRegistry.add = async () => { throw new Error('diagnostic registry failure'); };
60
- await assert.rejects(new ProjectLifecycleService({ ...options, registryStore: brokenRegistry }).importProject(input), /Project import failed/);
61
- const failed = new LogRepository({ layout }).query({ event: 'project.import.failed' }).entries[0];
62
- assert.equal(failed.error.message, 'diagnostic registry failure');
63
- assert.equal(failed.phase, 'hook-verified');
64
- const transaction = JSON.parse(fs.readFileSync(lifecycle.transactionPath(failed.operationId), 'utf8'));
65
- assert.equal(transaction.phase, 'failed'); assert.equal(transaction.failurePhase, 'hook-verified');
66
- assert.equal(hooks.readHookStatus({ repoPath: repo }).installed, false);
67
- const worktree = path.join(root, '关联 工作树'); git(['worktree', 'add', '-qb', 'linked', worktree]);
68
- const sharedHook = hooks.installHook({ repoPath: worktree, projectId: 'project-worktree', triggerScriptPath });
69
- assert.equal(path.dirname(sharedHook.hookPath), path.join(repo, '.git', 'hooks'));
70
- hooks.uninstallHook({ repoPath: worktree, projectId: 'project-worktree' });
71
- for (const custom of ['.custom hooks', path.join(root, '绝对 hooks')]) {
72
- git(['config', 'core.hooksPath', custom], worktree);
73
- const installed = hooks.installHook({ repoPath: worktree, projectId: 'project-worktree', triggerScriptPath });
74
- assert.equal(path.dirname(installed.hookPath), path.resolve(worktree, custom));
75
- assert.equal(hooks.readHookStatus({ repoPath: worktree }).installed, true);
76
- hooks.uninstallHook({ repoPath: worktree, projectId: 'project-worktree' });
77
- }
78
- await logger.close();
79
- console.log(`git-path-compatibility-test PASS (${git(['--version'])}; import → real Hook → journal; worktrees/custom hooks; diagnostic phase)`);
80
- })().catch(error => { console.error(error.stack || error.message); process.exitCode = 1; });
1
+ const assert = require('assert/strict');
2
+ const fs = require('fs');
3
+ const os = require('os');
4
+ const path = require('path');
5
+ const { spawnSync } = require('child_process');
6
+ const { resolveGitPath } = require('../lib/git-path');
7
+ const { StorageLayout } = require('../lib/storage-layout');
8
+ const { SettingsStore } = require('../lib/settings-store');
9
+ const { ProjectRegistryStore } = require('../lib/project-registry-store');
10
+ const { ProjectStore } = require('../lib/project-store');
11
+ const { ProjectLifecycleService } = require('../lib/project-lifecycle-service');
12
+ const { BridgeAdapter } = require('../lib/bridge-adapter');
13
+ const { Logger, LogRepository } = require('../lib/structured-logger');
14
+ const hooks = require('../lib/hook-manager');
15
+
16
+ (async () => {
17
+ const root = fs.mkdtempSync(path.join(os.tmpdir(), 'kb-git-path-'));
18
+ const repo = path.join(root, '中文 空格项目'); fs.mkdirSync(repo);
19
+ const git = (args, cwd = repo) => {
20
+ const result = spawnSync('git', args, { cwd, encoding: 'utf8', windowsHide: true });
21
+ assert.equal(result.status, 0, result.stderr); return result.stdout.trim();
22
+ };
23
+ for (const invalid of ['', '--path-format=absolute\n.git/hooks', '.git/hooks\nother', '--unsupported', '\0']) {
24
+ assert.throws(() => resolveGitPath(repo, invalid), { code: 'INVALID_ARGUMENT' });
25
+ }
26
+ assert.equal(resolveGitPath(repo, '.git/hooks\r\n'), path.join(repo, '.git', 'hooks'));
27
+ git(['init', '-q']); git(['config', 'user.name', 'Git Compatibility']); git(['config', 'user.email', 'git-compat@example.invalid']);
28
+ fs.writeFileSync(path.join(repo, 'README.md'), '# baseline\n'); git(['add', '.']); git(['commit', '-qm', 'baseline']);
29
+ const layout = new StorageLayout({ dataDir: path.join(root, 'data') });
30
+ const settingsStore = new SettingsStore({ layout });
31
+ const registryStore = new ProjectRegistryStore({ layout });
32
+ const projectStore = new ProjectStore({ layout });
33
+ await settingsStore.initialize({ knowledge: { rootPath: path.join(root, 'knowledge') } });
34
+ await registryStore.initialize();
35
+ const bridgeAdapter = new BridgeAdapter({ dataDir: layout.getDataDir(), bridgeHomeDir: path.join(root, 'bridge') });
36
+ assert.ok(bridgeAdapter.isAvailable());
37
+ const logger = new Logger({ layout });
38
+ const triggerScriptPath = path.resolve(__dirname, '../scripts/hook-trigger.js');
39
+ const options = { layout, settingsStore, registryStore, projectStore, bridgeAdapter, logger, triggerScriptPath };
40
+ const lifecycle = new ProjectLifecycleService(options);
41
+ const input = { localPath: repo, knowledgePath: path.join(root, 'knowledge', 'fresh'), projectId: 'project-git-compat' };
42
+ const preflight = await lifecycle.preflightImport(input);
43
+ assert.equal(preflight.ready, true);
44
+ assert.equal(preflight.checks.git.commonDir, path.join(repo, '.git'));
45
+ const imported = await lifecycle.importProject(input);
46
+ assert.equal(imported.hook.hookPath, path.join(repo, '.git', 'hooks', 'post-commit'));
47
+ assert.equal(imported.config.repoIdentity.commonDir, path.join(repo, '.git'));
48
+ const nested = path.join(repo, 'src'); fs.mkdirSync(nested);
49
+ assert.equal((await bridgeAdapter.resolveRepoIdentity(nested)).repoIdentity.commonDir, imported.config.repoIdentity.commonDir);
50
+ fs.appendFileSync(path.join(repo, 'README.md'), 'commit after import\n'); git(['add', '.']); git(['commit', '-qm', 'captured update']);
51
+ const sha = git(['rev-parse', 'HEAD']);
52
+ const journal = await bridgeAdapter.readEvents({ after: 0, limit: 100 });
53
+ const boundary = journal.events.find(event => event.commitSha === sha && event.schema === 'git-commit-boundary/v1');
54
+ assert.ok(boundary, 'real Git post-commit Hook must append a durable boundary');
55
+ assert.equal(boundary.repoIdentity.commonDir, path.join(repo, '.git'));
56
+ assert.equal(boundary.projectId, input.projectId);
57
+ await lifecycle.deleteProject(input.projectId);
58
+ const brokenRegistry = Object.create(registryStore);
59
+ brokenRegistry.add = async () => { throw new Error('diagnostic registry failure'); };
60
+ await assert.rejects(new ProjectLifecycleService({ ...options, registryStore: brokenRegistry }).importProject(input), /Project import failed/);
61
+ const failed = new LogRepository({ layout }).query({ event: 'project.import.failed' }).entries[0];
62
+ assert.equal(failed.error.message, 'diagnostic registry failure');
63
+ assert.equal(failed.phase, 'hook-verified');
64
+ const transaction = JSON.parse(fs.readFileSync(lifecycle.transactionPath(failed.operationId), 'utf8'));
65
+ assert.equal(transaction.phase, 'failed'); assert.equal(transaction.failurePhase, 'hook-verified');
66
+ assert.equal(hooks.readHookStatus({ repoPath: repo }).installed, false);
67
+ const worktree = path.join(root, '关联 工作树'); git(['worktree', 'add', '-qb', 'linked', worktree]);
68
+ const sharedHook = hooks.installHook({ repoPath: worktree, projectId: 'project-worktree', triggerScriptPath });
69
+ assert.equal(path.dirname(sharedHook.hookPath), path.join(repo, '.git', 'hooks'));
70
+ hooks.uninstallHook({ repoPath: worktree, projectId: 'project-worktree' });
71
+ for (const custom of ['.custom hooks', path.join(root, '绝对 hooks')]) {
72
+ git(['config', 'core.hooksPath', custom], worktree);
73
+ const installed = hooks.installHook({ repoPath: worktree, projectId: 'project-worktree', triggerScriptPath });
74
+ assert.equal(path.dirname(installed.hookPath), path.resolve(worktree, custom));
75
+ assert.equal(hooks.readHookStatus({ repoPath: worktree }).installed, true);
76
+ hooks.uninstallHook({ repoPath: worktree, projectId: 'project-worktree' });
77
+ }
78
+ await logger.close();
79
+ console.log(`git-path-compatibility-test PASS (${git(['--version'])}; import → real Hook → journal; worktrees/custom hooks; diagnostic phase)`);
80
+ })().catch(error => { console.error(error.stack || error.message); process.exitCode = 1; });
@@ -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)');