@sanqianx/project-knowledge 4.7.0 → 5.0.0

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 (196) hide show
  1. package/.agents/plugins/marketplace.json +17 -17
  2. package/.claude-plugin/marketplace.json +1 -1
  3. package/CHANGELOG.md +836 -799
  4. package/README.md +58 -41
  5. package/_modules/knowledge-engine/README.md +91 -0
  6. package/_modules/knowledge-engine/package.json +38 -0
  7. package/_modules/knowledge-engine/src/analysis-contract.js +25 -0
  8. package/_modules/knowledge-engine/src/analysis-worker.js +54 -0
  9. package/_modules/knowledge-engine/src/bin.js +52 -0
  10. package/_modules/knowledge-engine/src/bridge-client.js +24 -0
  11. package/_modules/knowledge-engine/src/coordinator.js +263 -0
  12. package/_modules/knowledge-engine/src/headless-analyzer.js +75 -0
  13. package/_modules/knowledge-engine/src/memories.js +85 -0
  14. package/_modules/knowledge-engine/src/retrieval.js +116 -0
  15. package/_modules/knowledge-engine/src/runtime/README.md +7 -0
  16. package/_modules/knowledge-engine/src/runtime/atomic-file.js +204 -0
  17. package/_modules/knowledge-engine/src/runtime/automation-config.js +140 -0
  18. package/_modules/knowledge-engine/src/runtime/commit-conversation-binder.js +249 -0
  19. package/_modules/knowledge-engine/src/runtime/commit-processing-ledger.js +26 -0
  20. package/_modules/knowledge-engine/src/runtime/commit-prompt.js +202 -0
  21. package/_modules/knowledge-engine/src/runtime/commit-reconciler.js +432 -0
  22. package/_modules/knowledge-engine/src/runtime/content-hash.js +4 -0
  23. package/_modules/knowledge-engine/src/runtime/contracts.js +279 -0
  24. package/_modules/knowledge-engine/src/runtime/conversation-exclusions.js +204 -0
  25. package/_modules/knowledge-engine/src/runtime/conversation-store.js +387 -0
  26. package/_modules/knowledge-engine/src/runtime/evidence-bundle.js +228 -0
  27. package/_modules/knowledge-engine/src/runtime/git-runner.js +62 -0
  28. package/_modules/knowledge-engine/src/runtime/knowledge-promotion.js +444 -0
  29. package/_modules/knowledge-engine/src/runtime/knowledge-retrieval-service.js +402 -0
  30. package/_modules/knowledge-engine/src/runtime/layout.js +44 -0
  31. package/_modules/knowledge-engine/src/runtime/markdown-knowledge-indexer.js +230 -0
  32. package/_modules/knowledge-engine/src/runtime/scanner.js +298 -0
  33. package/_modules/knowledge-engine/src/server.js +154 -0
  34. package/_modules/knowledge-engine/src/store.js +236 -0
  35. package/_modules/knowledge-engine/ui/app.css +172 -0
  36. package/_modules/knowledge-engine/ui/app.js +297 -0
  37. package/_modules/knowledge-engine/ui/index.html +95 -0
  38. package/_site/README.md +30 -30
  39. package/_site/_test/ai-profile-resolver-test.js +137 -137
  40. package/_site/_test/automation-queue-test.js +14 -14
  41. package/_site/_test/automation-ui-test.js +40 -40
  42. package/_site/_test/background-task-registry-test.js +43 -43
  43. package/_site/_test/baseline-schema-test.js +91 -91
  44. package/_site/_test/chat-claudecodeui-match-test.js +92 -92
  45. package/_site/_test/claude-executable-discovery-test.js +62 -62
  46. package/_site/_test/claude-workbench-test.js +170 -170
  47. package/_site/_test/codex-conversation-projection-test.js +2 -1
  48. package/_site/_test/commit-conversation-binding-test.js +95 -95
  49. package/_site/_test/commit-evidence-test.js +110 -110
  50. package/_site/_test/conversation-api-test.js +109 -109
  51. package/_site/_test/conversation-store-test.js +136 -136
  52. package/_site/_test/desktop-browser-compat-test.js +28 -28
  53. package/_site/_test/desktop-hook-runtime-regression-test.js +204 -204
  54. package/_site/_test/explicit-commit-processor-test.js +37 -37
  55. package/_site/_test/fixtures/make-git-repos.js +12 -1
  56. package/_site/_test/folder-picker-output-test.js +19 -19
  57. package/_site/_test/fresh-hook-reimport-test.js +53 -0
  58. package/_site/_test/fresh-product-data-test.js +42 -0
  59. package/_site/_test/full-integration-e2e-test.js +165 -165
  60. package/_site/_test/git-validation-test.js +2 -2
  61. package/_site/_test/hook-runtime-endpoint-test.js +88 -88
  62. package/_site/_test/hook-status-repair-api-test.js +152 -152
  63. package/_site/_test/hook-trigger-test.js +85 -85
  64. package/_site/_test/import-preflight-api-test.js +234 -233
  65. package/_site/_test/index-writer-concurrency-test.js +135 -135
  66. package/_site/_test/integration-adapters-test.js +171 -171
  67. package/_site/_test/integration-surface-coverage-test.js +164 -164
  68. package/_site/_test/knowledge-language-control-test.js +170 -170
  69. package/_site/_test/knowledge-promotion-recovery-test.js +260 -260
  70. package/_site/_test/knowledge-query-test.js +54 -54
  71. package/_site/_test/knowledge-retrieval-service-test.js +77 -77
  72. package/_site/_test/knowledge-storage-startup-test.js +52 -52
  73. package/_site/_test/knowledge-store-logs-supervision-test.js +89 -89
  74. package/_site/_test/logging-api-test.js +89 -89
  75. package/_site/_test/logging-sse-no-gap-test.js +88 -88
  76. package/_site/_test/logging-ui-test.js +106 -104
  77. package/_site/_test/markdown-delta-overlay-test.js +85 -85
  78. package/_site/_test/markdown-maintenance-api-test.js +75 -75
  79. package/_site/_test/mcp-server-test.js +149 -149
  80. package/_site/_test/module-artifact-integrity-test.js +27 -0
  81. package/_site/_test/module-boundary-test.js +30 -0
  82. package/_site/_test/module-bridge-eventbridge-test.js +7 -0
  83. package/_site/_test/module-model-configuration-test.js +44 -0
  84. package/_site/_test/module-process-lifecycle-test.js +24 -0
  85. package/_site/_test/module-stream-proxy-test.js +34 -0
  86. package/_site/_test/non-release-ci-test.js +34 -34
  87. package/_site/_test/offline-boundary-isolation-test.js +32 -32
  88. package/_site/_test/packaged-ui-smoke-test.js +11 -9
  89. package/_site/_test/path-consistency-test.js +151 -151
  90. package/_site/_test/pending-sweep-test.js +9 -9
  91. package/_site/_test/post-commit-automation-test.js +87 -87
  92. package/_site/_test/product-diagnostics-ui-test.js +75 -0
  93. package/_site/_test/product-import-ui-test.js +92 -0
  94. package/_site/_test/project-delete-recovery-test.js +64 -64
  95. package/_site/_test/project-goal-editor-test.js +144 -144
  96. package/_site/_test/project-lifecycle-transaction-test.js +106 -106
  97. package/_site/_test/prompt-settings-test.js +115 -115
  98. package/_site/_test/protected-architecture-gate-test.js +122 -122
  99. package/_site/_test/refactor-characterization-test.js +36 -36
  100. package/_site/_test/release-version-sync-test.js +1 -1
  101. package/_site/_test/requirement-recorder-test.js +173 -173
  102. package/_site/_test/run-all-tests.js +159 -156
  103. package/_site/_test/shared-contracts-test.js +58 -58
  104. package/_site/_test/startup-analysis-disabled-test.js +10 -10
  105. package/_site/_test/storage-foundation-test.js +78 -78
  106. package/_site/_test/structured-logger-test.js +101 -101
  107. package/_site/_test/tracking-start-test.js +109 -108
  108. package/_site/_test/workbench-permission-test.js +117 -117
  109. package/_site/_test/workspace-ui-contract-test.js +28 -43
  110. package/_site/lib/ai-profile-resolver.js +78 -78
  111. package/_site/lib/ai-workspace.js +101 -101
  112. package/_site/lib/automation-config.js +140 -140
  113. package/_site/lib/bridge-adapter.js +1 -0
  114. package/_site/lib/bridge-consumer-service.js +402 -402
  115. package/_site/lib/claude-cli-runner.js +1510 -1510
  116. package/_site/lib/commit-conversation-binder.js +234 -234
  117. package/_site/lib/commit-processing-ledger.js +27 -27
  118. package/_site/lib/commit-prompt.js +202 -202
  119. package/_site/lib/commit-reconciler.js +406 -406
  120. package/_site/lib/contracts.js +284 -279
  121. package/_site/lib/conversation-query-service.js +144 -144
  122. package/_site/lib/conversation-store.js +385 -385
  123. package/_site/lib/data-dir.js +6 -74
  124. package/_site/lib/engine-connection.js +67 -0
  125. package/_site/lib/evidence-bundle.js +228 -228
  126. package/_site/lib/folder-picker-output.js +21 -21
  127. package/_site/lib/git-runner.js +62 -62
  128. package/_site/lib/github-team-store.js +1172 -1172
  129. package/_site/lib/hook-manager.js +203 -202
  130. package/_site/lib/index-service.js +173 -173
  131. package/_site/lib/integration-installer.js +908 -907
  132. package/_site/lib/kb-framework.js +176 -176
  133. package/_site/lib/kb-validator.js +124 -124
  134. package/_site/lib/knowledge-promotion.js +435 -435
  135. package/_site/lib/knowledge-retrieval-service.js +401 -401
  136. package/_site/lib/knowledge-tool-runtime.js +359 -359
  137. package/_site/lib/llm-client.js +161 -161
  138. package/_site/lib/markdown-knowledge-indexer.js +230 -230
  139. package/_site/lib/module-bridge.js +191 -36
  140. package/_site/lib/post-commit-automation.js +87 -87
  141. package/_site/lib/product-data.js +24 -0
  142. package/_site/lib/project-lifecycle-service.js +535 -497
  143. package/_site/lib/project-store.js +268 -262
  144. package/_site/lib/requirement-recorder.js +278 -278
  145. package/_site/lib/runtime-endpoint.js +155 -155
  146. package/_site/lib/scanner.js +298 -298
  147. package/_site/lib/server-app.js +1579 -1520
  148. package/_site/lib/settings-store.js +133 -113
  149. package/_site/lib/storage-layout.js +162 -162
  150. package/_site/lib/structured-logger.js +574 -574
  151. package/_site/scripts/folder-picker.ps1 +156 -156
  152. package/_site/scripts/hook-trigger.js +157 -156
  153. package/_site/scripts/install-module-candidates.js +69 -0
  154. package/_site/scripts/pack-module-candidates.js +72 -0
  155. package/_site/scripts/sync-release-version.js +2 -2
  156. package/_site/scripts/vendor-modules.js +47 -0
  157. package/_site/scripts/verify-module-runtime.js +45 -0
  158. package/_site/scripts/verify-product-runtime.js +34 -0
  159. package/bin/project-knowledge-mcp.js +194 -194
  160. package/docs/README.zh-CN.md +42 -29
  161. package/docs/fresh-v5-start.md +28 -0
  162. package/docs/project-registry-schema.md +22 -22
  163. package/docs/testing-strategy.md +33 -33
  164. package/module-runtime-manifest.json +220 -0
  165. package/package.json +14 -9
  166. package/plugins/project-knowledge/.claude-plugin/plugin.json +1 -1
  167. package/plugins/project-knowledge/.codex-plugin/plugin.json +1 -1
  168. package/plugins/project-knowledge/.mcp.json +1 -1
  169. package/plugins/project-knowledge/opencode/project-knowledge.md +3 -3
  170. package/plugins/project-knowledge/skills/project-knowledge/SKILL.md +28 -28
  171. package/templates/project-readme.md +34 -34
  172. package/ui/favicon.svg +38 -38
  173. package/ui/index.html +52 -149
  174. package/ui/product.css +9 -0
  175. package/ui/product.js +412 -0
  176. package/vendor-manifest.json +45 -0
  177. package/_site/_test/data-dir-migration-test.js +0 -77
  178. package/_site/_test/import-ui-flow-test.js +0 -171
  179. package/_site/_test/knowledge-migration-test.js +0 -75
  180. package/_site/_test/legacy-forward-compat-test.js +0 -257
  181. package/_site/_test/legacy-project-upgrade-e2e-test.js +0 -363
  182. package/_site/_test/p0-data-migration-characterization-test.js +0 -37
  183. package/_site/_test/p0-e2e-gate-test.js +0 -347
  184. package/_site/_test/project-control-panel-task14-test.js +0 -63
  185. package/_site/_test/project-layout-v2-migration-test.js +0 -127
  186. package/_site/_test/server-runtime-migration-safety-test.js +0 -27
  187. package/_site/_test/task15-20-ui-flow-test.js +0 -148
  188. package/_site/_test/ui-i18n-toggle-test.js +0 -114
  189. package/_site/_test/ui-smoke-test.js +0 -73
  190. package/_site/_test/v4122-upgrade-data-contract-test.js +0 -56
  191. package/_site/lib/data-state-classifier.js +0 -40
  192. package/_site/lib/legacy-data-manifest.js +0 -26
  193. package/_site/lib/migration-service.js +0 -442
  194. package/ui/app.css +0 -58
  195. package/ui/app.js +0 -607
  196. package/ui/i18n.js +0 -146
@@ -1,106 +1,106 @@
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 { StorageLayout } = require('../lib/storage-layout');
7
- const { SettingsStore } = require('../lib/settings-store');
8
- const { ProjectRegistryStore } = require('../lib/project-registry-store');
9
- const { ProjectStore } = require('../lib/project-store');
10
- const { ProjectLifecycleService } = require('../lib/project-lifecycle-service');
11
- const realHookManager = require('../lib/hook-manager');
12
-
13
- const ROOT = path.resolve(__dirname, '..', '..');
14
- const TRIGGER = path.join(ROOT, '_site', 'scripts', 'hook-trigger.js');
15
-
16
- function git(repo, args) {
17
- const result = spawnSync('git', ['-C', repo, ...args], { encoding: 'utf8', windowsHide: true, env: { ...process.env, GIT_TERMINAL_PROMPT: '0' } });
18
- if (result.status !== 0) throw new Error(result.stderr || `git ${args.join(' ')} failed`);
19
- return String(result.stdout || '').trim();
20
- }
21
-
22
- function createRepo(root, name, withCommit = true) {
23
- const repo = path.join(root, name);
24
- fs.mkdirSync(repo, { recursive: true });
25
- git(repo, ['init', '--initial-branch=main']);
26
- git(repo, ['config', 'user.email', 'life@example.invalid']);
27
- git(repo, ['config', 'user.name', 'Lifecycle Test']);
28
- if (withCommit) {
29
- fs.writeFileSync(path.join(repo, 'README.md'), `# ${name}\n`);
30
- git(repo, ['add', 'README.md']);
31
- git(repo, ['commit', '-m', 'initial']);
32
- }
33
- return repo;
34
- }
35
-
36
- (async () => {
37
- const root = fs.mkdtempSync(path.join(os.tmpdir(), 'pk-lifecycle-'));
38
- const dataDir = path.join(root, 'data');
39
- const knowledgeRoot = path.join(root, 'knowledge');
40
- const layout = new StorageLayout({ dataDir });
41
- const settings = new SettingsStore({ layout });
42
- const registry = new ProjectRegistryStore({ layout });
43
- const projects = new ProjectStore({ layout });
44
- await settings.initialize({ knowledge: { rootPath: knowledgeRoot } });
45
- await registry.initialize();
46
- const lifecycle = new ProjectLifecycleService({
47
- layout,
48
- settingsStore: settings,
49
- registryStore: registry,
50
- projectStore: projects,
51
- triggerScriptPath: TRIGGER,
52
- bridgeAdapter: { async getHighWatermark() { return { status: 'captured', cursor: 73 }; } },
53
- });
54
-
55
- const repo = createRepo(root, 'repo-a');
56
- const claude = path.join(repo, 'CLAUDE.md');
57
- fs.writeFileSync(claude, '# user only\n');
58
- const imported = await lifecycle.importProject({ localPath: repo, projectId: 'project-life-a', storageName: 'repo-a-stable' });
59
- assert.strictEqual(imported.ok, true);
60
- assert.strictEqual(imported.config.aiProfileId, null, 'import must not require an AI profile');
61
- assert.strictEqual(imported.state.trackingStartCommit, git(repo, ['rev-parse', 'HEAD']));
62
- assert.strictEqual(imported.state.conversationBaselineCursor, 73, 'import must freeze the current Bridge cursor as its conversation baseline');
63
- assert.deepStrictEqual(fs.readdirSync(imported.config.knowledgePath), [], 'import must not create TODO knowledge files');
64
- assert.strictEqual(fs.readFileSync(claude, 'utf8'), '# user only\n');
65
- assert.strictEqual(realHookManager.readHookStatus({ repoPath: repo, projectId: imported.projectId }).installed, true);
66
- await assert.rejects(lifecycle.importProject({ localPath: repo }), /already imported/);
67
-
68
- const conflictRepo = createRepo(root, 'repo-conflict');
69
- const conflictPath = path.join(knowledgeRoot, 'conflict-stable');
70
- fs.mkdirSync(conflictPath, { recursive: true });
71
- fs.writeFileSync(path.join(conflictPath, 'user.md'), 'do not overwrite');
72
- await assert.rejects(lifecycle.importProject({ localPath: conflictRepo, projectId: 'project-conflict', storageName: 'conflict-stable' }), error => error.code === 'MIGRATION_TARGET_CONFLICT');
73
- assert(!registry.listIds().includes('project-conflict'));
74
- assert.strictEqual(fs.readFileSync(path.join(conflictPath, 'user.md'), 'utf8'), 'do not overwrite');
75
-
76
- const failingRepo = createRepo(root, 'repo-failing');
77
- const failingHooks = { ...realHookManager, installHook() { throw new Error('injected Hook failure'); } };
78
- const failingLifecycle = new ProjectLifecycleService({ layout, settingsStore: settings, registryStore: registry, projectStore: projects, triggerScriptPath: TRIGGER, hookManager: failingHooks });
79
- await assert.rejects(failingLifecycle.importProject({ localPath: failingRepo, projectId: 'project-failing', storageName: 'failing-stable' }));
80
- assert(!registry.listIds().includes('project-failing'));
81
- assert(!fs.existsSync(layout.getProjectMetadataDir('project-failing')));
82
- assert(!fs.existsSync(path.join(knowledgeRoot, 'failing-stable')));
83
-
84
- const emptyRepo = createRepo(root, 'repo-empty', false);
85
- const empty = await lifecycle.importProject({ localPath: emptyRepo, projectId: 'project-empty', storageName: 'empty-stable' });
86
- assert.strictEqual(empty.state.trackingMode, 'empty-repo');
87
- assert.strictEqual(empty.state.trackingStartCommit, null);
88
-
89
- const failingDelete = new ProjectLifecycleService({
90
- layout, settingsStore: settings, registryStore: registry, projectStore: projects, triggerScriptPath: TRIGGER,
91
- hookManager: { ...realHookManager, uninstallHook() { throw new Error('injected uninstall failure'); } },
92
- });
93
- await assert.rejects(failingDelete.deleteProject(imported.projectId));
94
- assert(registry.listIds().includes(imported.projectId), 'Hook uninstall failure must preserve registration');
95
-
96
- const knowledgePath = imported.config.knowledgePath;
97
- const deleted = await lifecycle.deleteProject(imported.projectId);
98
- assert.strictEqual(deleted.removedKnowledge, false);
99
- assert(fs.existsSync(knowledgePath), 'default delete must preserve external knowledge directory');
100
- assert(!registry.listIds().includes(imported.projectId));
101
-
102
- console.log('project-lifecycle-transaction-test PASS');
103
- })().catch(error => {
104
- console.error(error.stack || error.message);
105
- process.exitCode = 1;
106
- });
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 { StorageLayout } = require('../lib/storage-layout');
7
+ const { SettingsStore } = require('../lib/settings-store');
8
+ const { ProjectRegistryStore } = require('../lib/project-registry-store');
9
+ const { ProjectStore } = require('../lib/project-store');
10
+ const { ProjectLifecycleService } = require('../lib/project-lifecycle-service');
11
+ const realHookManager = require('../lib/hook-manager');
12
+
13
+ const ROOT = path.resolve(__dirname, '..', '..');
14
+ const TRIGGER = path.join(ROOT, '_site', 'scripts', 'hook-trigger.js');
15
+
16
+ function git(repo, args) {
17
+ const result = spawnSync('git', ['-C', repo, ...args], { encoding: 'utf8', windowsHide: true, env: { ...process.env, GIT_TERMINAL_PROMPT: '0' } });
18
+ if (result.status !== 0) throw new Error(result.stderr || `git ${args.join(' ')} failed`);
19
+ return String(result.stdout || '').trim();
20
+ }
21
+
22
+ function createRepo(root, name, withCommit = true) {
23
+ const repo = path.join(root, name);
24
+ fs.mkdirSync(repo, { recursive: true });
25
+ git(repo, ['init', '--initial-branch=main']);
26
+ git(repo, ['config', 'user.email', 'life@example.invalid']);
27
+ git(repo, ['config', 'user.name', 'Lifecycle Test']);
28
+ if (withCommit) {
29
+ fs.writeFileSync(path.join(repo, 'README.md'), `# ${name}\n`);
30
+ git(repo, ['add', 'README.md']);
31
+ git(repo, ['commit', '-m', 'initial']);
32
+ }
33
+ return repo;
34
+ }
35
+
36
+ (async () => {
37
+ const root = fs.mkdtempSync(path.join(os.tmpdir(), 'pk-lifecycle-'));
38
+ const dataDir = path.join(root, 'data');
39
+ const knowledgeRoot = path.join(root, 'knowledge');
40
+ const layout = new StorageLayout({ dataDir });
41
+ const settings = new SettingsStore({ layout });
42
+ const registry = new ProjectRegistryStore({ layout });
43
+ const projects = new ProjectStore({ layout });
44
+ await settings.initialize({ knowledge: { rootPath: knowledgeRoot } });
45
+ await registry.initialize();
46
+ const lifecycle = new ProjectLifecycleService({
47
+ layout,
48
+ settingsStore: settings,
49
+ registryStore: registry,
50
+ projectStore: projects,
51
+ triggerScriptPath: TRIGGER,
52
+ bridgeAdapter: { async getHighWatermark() { return { status: 'captured', cursor: 73 }; } },
53
+ });
54
+
55
+ const repo = createRepo(root, 'repo-a');
56
+ const claude = path.join(repo, 'CLAUDE.md');
57
+ fs.writeFileSync(claude, '# user only\n');
58
+ const imported = await lifecycle.importProject({ localPath: repo, projectId: 'project-life-a', storageName: 'repo-a-stable' });
59
+ assert.strictEqual(imported.ok, true);
60
+ assert.strictEqual(imported.config.aiProfileId, null, 'import must not require an AI profile');
61
+ assert.strictEqual(imported.state.trackingStartCommit, git(repo, ['rev-parse', 'HEAD']));
62
+ assert.strictEqual(imported.state.conversationBaselineCursor, 73, 'import must freeze the current Bridge cursor as its conversation baseline');
63
+ assert.deepStrictEqual(fs.readdirSync(imported.config.knowledgePath), [], 'import must not create TODO knowledge files');
64
+ assert.strictEqual(fs.readFileSync(claude, 'utf8'), '# user only\n');
65
+ assert.strictEqual(realHookManager.readHookStatus({ repoPath: repo, projectId: imported.projectId }).installed, true);
66
+ await assert.rejects(lifecycle.importProject({ localPath: repo }), /already imported/);
67
+
68
+ const conflictRepo = createRepo(root, 'repo-conflict');
69
+ const conflictPath = path.join(knowledgeRoot, 'conflict-stable');
70
+ fs.mkdirSync(conflictPath, { recursive: true });
71
+ fs.writeFileSync(path.join(conflictPath, 'user.md'), 'do not overwrite');
72
+ await assert.rejects(lifecycle.importProject({ localPath: conflictRepo, projectId: 'project-conflict', storageName: 'conflict-stable' }), error => error.code === 'MIGRATION_TARGET_CONFLICT');
73
+ assert(!registry.listIds().includes('project-conflict'));
74
+ assert.strictEqual(fs.readFileSync(path.join(conflictPath, 'user.md'), 'utf8'), 'do not overwrite');
75
+
76
+ const failingRepo = createRepo(root, 'repo-failing');
77
+ const failingHooks = { ...realHookManager, installHook() { throw new Error('injected Hook failure'); } };
78
+ const failingLifecycle = new ProjectLifecycleService({ layout, settingsStore: settings, registryStore: registry, projectStore: projects, triggerScriptPath: TRIGGER, hookManager: failingHooks });
79
+ await assert.rejects(failingLifecycle.importProject({ localPath: failingRepo, projectId: 'project-failing', storageName: 'failing-stable' }));
80
+ assert(!registry.listIds().includes('project-failing'));
81
+ assert(!fs.existsSync(layout.getProjectMetadataDir('project-failing')));
82
+ assert(!fs.existsSync(path.join(knowledgeRoot, 'failing-stable')));
83
+
84
+ const emptyRepo = createRepo(root, 'repo-empty', false);
85
+ const empty = await lifecycle.importProject({ localPath: emptyRepo, projectId: 'project-empty', storageName: 'empty-stable' });
86
+ assert.strictEqual(empty.state.trackingMode, 'empty-repo');
87
+ assert.strictEqual(empty.state.trackingStartCommit, null);
88
+
89
+ const failingDelete = new ProjectLifecycleService({
90
+ layout, settingsStore: settings, registryStore: registry, projectStore: projects, triggerScriptPath: TRIGGER,
91
+ hookManager: { ...realHookManager, uninstallHook() { throw new Error('injected uninstall failure'); } },
92
+ });
93
+ await assert.rejects(failingDelete.deleteProject(imported.projectId));
94
+ assert(registry.listIds().includes(imported.projectId), 'Hook uninstall failure must preserve registration');
95
+
96
+ const knowledgePath = imported.config.knowledgePath;
97
+ const deleted = await lifecycle.deleteProject(imported.projectId);
98
+ assert.strictEqual(deleted.removedKnowledge, false);
99
+ assert(fs.existsSync(knowledgePath), 'default delete must preserve external knowledge directory');
100
+ assert(!registry.listIds().includes(imported.projectId));
101
+
102
+ console.log('project-lifecycle-transaction-test PASS');
103
+ })().catch(error => {
104
+ console.error(error.stack || error.message);
105
+ process.exitCode = 1;
106
+ });
@@ -1,115 +1,115 @@
1
- // _site/_test/prompt-settings-test.js
2
- //
3
- // T12: Prompt Settings on the canonical analysis path. Verifies:
4
- // - promptOverrides from settings flow into the canonical commit
5
- // prompt rendering (via commit-prompt.js / automation-config.js);
6
- // - reset to default restores the original prompt hash;
7
- // - malformed templates are rejected;
8
- // - the canonical CommitReconciler.prepareClaim path picks up the
9
- // configured override without bypassing evidence manifests or
10
- // prompt hashes.
11
-
12
- const assert = require('assert');
13
- const fs = require('fs');
14
- const os = require('os');
15
- const path = require('path');
16
- const { StorageLayout } = require('../lib/storage-layout');
17
- const { SettingsStore, defaultSettings } = require('../lib/settings-store');
18
- const { renderCommitPrompt } = require('../lib/commit-prompt');
19
- const { normalizeAutomationConfig, DEFAULT_COMMIT_PROMPT_TEMPLATE } = require('../lib/automation-config');
20
-
21
- (async () => {
22
- const dataDir = fs.mkdtempSync(path.join(os.tmpdir(), 'pk-prompt-settings-'));
23
- const layout = new StorageLayout({ dataDir });
24
- const settings = new SettingsStore({ layout });
25
- await settings.initialize({ knowledge: { rootPath: '' } });
26
-
27
- const fakeEvidence = {
28
- schema: 'commit-evidence/v1',
29
- commitSha: 'abc1234567890abcdef1234567890abcdef123456',
30
- parents: [],
31
- author: 'tester',
32
- date: '2026-08-20T00:00:00Z',
33
- subject: 'test commit',
34
- branch: 'main',
35
- patchBase: 'parent',
36
- patchMode: 'parent',
37
- files: [],
38
- patchHash: 'sha256:' + '0'.repeat(64),
39
- patchBytes: 0,
40
- patchInline: true,
41
- patchChunked: false,
42
- patchOmitted: false,
43
- patchLimitBytes: 0,
44
- omittedReason: null,
45
- evidenceBundle: null,
46
- evidenceHash: 'sha256:' + '0'.repeat(64),
47
- patch: '',
48
- };
49
- const baseInput = {
50
- config: { projectId: 'p', displayName: 'P', knowledgePath: '', repoPath: '', automation: {} },
51
- evidence: fakeEvidence,
52
- requirements: [],
53
- conversationSnapshot: null,
54
- existingKnowledge: { entries: [], totalBytes: 0 },
55
- };
56
-
57
- // Case 1: default prompt renders the canonical template.
58
- {
59
- const config = normalizeAutomationConfig({});
60
- assert.strictEqual(config.commitPromptTemplate, DEFAULT_COMMIT_PROMPT_TEMPLATE);
61
- const rendered = renderCommitPrompt(baseInput);
62
- assert(rendered.prompt.length > 0);
63
- assert(rendered.promptHash && rendered.promptHash.startsWith('sha256:'), 'promptHash must be a sha256 digest');
64
- assert(rendered.prompt.includes('你正在根据一个 Git Commit'), 'default template must render the canonical Chinese prompt');
65
- }
66
-
67
- // Case 2: promptOverrides.commitPromptTemplate changes the rendered
68
- // prompt deterministically.
69
- {
70
- const override = 'CUSTOM-PROMPT-TEMPLATE {{commitHash}} {{commitSubject}} {{commitAuthor}}';
71
- const renderedDefault = renderCommitPrompt(baseInput);
72
- const renderedCustom = renderCommitPrompt({
73
- ...baseInput,
74
- config: { ...baseInput.config, automation: { commitPromptTemplate: override } },
75
- });
76
- assert.notStrictEqual(renderedCustom.promptHash, renderedDefault.promptHash, 'override must change prompt hash');
77
- assert(renderedCustom.prompt.includes('CUSTOM-PROMPT-TEMPLATE'), 'override template must appear in rendered prompt');
78
- }
79
-
80
- // Case 3: reset to default restores the original template + hash.
81
- {
82
- const originalConfig = normalizeAutomationConfig({});
83
- const overrideConfig = normalizeAutomationConfig({ commitPromptTemplate: 'OVERRIDE {{commitHash}}' });
84
- const resetConfig = normalizeAutomationConfig({ ...overrideConfig, commitPromptTemplate: '' });
85
- assert.strictEqual(resetConfig.commitPromptTemplate, originalConfig.commitPromptTemplate, 'empty override restores default');
86
- }
87
-
88
- // Case 4: malformed template (no required tokens) — the renderer must
89
- // still produce a deterministic hash; downstream code (prepareClaim)
90
- // is what enforces minimum content via ensureRequiredSections.
91
- {
92
- const rendered = renderCommitPrompt({
93
- ...baseInput,
94
- config: { ...baseInput.config, automation: { commitPromptTemplate: 'NO-TOKENS-HERE' } },
95
- });
96
- assert(rendered.prompt.includes('NO-TOKENS-HERE'));
97
- assert(rendered.promptHash.startsWith('sha256:'));
98
- }
99
-
100
- // Case 5: settings.promptOverrides is wired through settings updatePatch.
101
- // Persisting a prompt override via the canonical settings API must
102
- // appear in subsequent reads (the next analyzer invocation will pick
103
- // up the new template via this path).
104
- {
105
- await settings.updatePatch({ promptOverrides: { commitPromptTemplate: 'NEW {{commitHash}} {{commitSubject}}' } });
106
- const current = settings.read();
107
- assert.strictEqual(current.promptOverrides.commitPromptTemplate, 'NEW {{commitHash}} {{commitSubject}}');
108
- }
109
-
110
- fs.rmSync(dataDir, { recursive: true, force: true });
111
- console.log('prompt-settings-test PASS');
112
- })().catch(error => {
113
- console.error(error.stack || error.message);
114
- process.exit(1);
115
- });
1
+ // _site/_test/prompt-settings-test.js
2
+ //
3
+ // T12: Prompt Settings on the canonical analysis path. Verifies:
4
+ // - promptOverrides from settings flow into the canonical commit
5
+ // prompt rendering (via commit-prompt.js / automation-config.js);
6
+ // - reset to default restores the original prompt hash;
7
+ // - malformed templates are rejected;
8
+ // - the canonical CommitReconciler.prepareClaim path picks up the
9
+ // configured override without bypassing evidence manifests or
10
+ // prompt hashes.
11
+
12
+ const assert = require('assert');
13
+ const fs = require('fs');
14
+ const os = require('os');
15
+ const path = require('path');
16
+ const { StorageLayout } = require('../lib/storage-layout');
17
+ const { SettingsStore, defaultSettings } = require('../lib/settings-store');
18
+ const { renderCommitPrompt } = require('../lib/commit-prompt');
19
+ const { normalizeAutomationConfig, DEFAULT_COMMIT_PROMPT_TEMPLATE } = require('../lib/automation-config');
20
+
21
+ (async () => {
22
+ const dataDir = fs.mkdtempSync(path.join(os.tmpdir(), 'pk-prompt-settings-'));
23
+ const layout = new StorageLayout({ dataDir });
24
+ const settings = new SettingsStore({ layout });
25
+ await settings.initialize({ knowledge: { rootPath: '' } });
26
+
27
+ const fakeEvidence = {
28
+ schema: 'commit-evidence/v1',
29
+ commitSha: 'abc1234567890abcdef1234567890abcdef123456',
30
+ parents: [],
31
+ author: 'tester',
32
+ date: '2026-08-20T00:00:00Z',
33
+ subject: 'test commit',
34
+ branch: 'main',
35
+ patchBase: 'parent',
36
+ patchMode: 'parent',
37
+ files: [],
38
+ patchHash: 'sha256:' + '0'.repeat(64),
39
+ patchBytes: 0,
40
+ patchInline: true,
41
+ patchChunked: false,
42
+ patchOmitted: false,
43
+ patchLimitBytes: 0,
44
+ omittedReason: null,
45
+ evidenceBundle: null,
46
+ evidenceHash: 'sha256:' + '0'.repeat(64),
47
+ patch: '',
48
+ };
49
+ const baseInput = {
50
+ config: { projectId: 'p', displayName: 'P', knowledgePath: '', repoPath: '', automation: {} },
51
+ evidence: fakeEvidence,
52
+ requirements: [],
53
+ conversationSnapshot: null,
54
+ existingKnowledge: { entries: [], totalBytes: 0 },
55
+ };
56
+
57
+ // Case 1: default prompt renders the canonical template.
58
+ {
59
+ const config = normalizeAutomationConfig({});
60
+ assert.strictEqual(config.commitPromptTemplate, DEFAULT_COMMIT_PROMPT_TEMPLATE);
61
+ const rendered = renderCommitPrompt(baseInput);
62
+ assert(rendered.prompt.length > 0);
63
+ assert(rendered.promptHash && rendered.promptHash.startsWith('sha256:'), 'promptHash must be a sha256 digest');
64
+ assert(rendered.prompt.includes('你正在根据一个 Git Commit'), 'default template must render the canonical Chinese prompt');
65
+ }
66
+
67
+ // Case 2: promptOverrides.commitPromptTemplate changes the rendered
68
+ // prompt deterministically.
69
+ {
70
+ const override = 'CUSTOM-PROMPT-TEMPLATE {{commitHash}} {{commitSubject}} {{commitAuthor}}';
71
+ const renderedDefault = renderCommitPrompt(baseInput);
72
+ const renderedCustom = renderCommitPrompt({
73
+ ...baseInput,
74
+ config: { ...baseInput.config, automation: { commitPromptTemplate: override } },
75
+ });
76
+ assert.notStrictEqual(renderedCustom.promptHash, renderedDefault.promptHash, 'override must change prompt hash');
77
+ assert(renderedCustom.prompt.includes('CUSTOM-PROMPT-TEMPLATE'), 'override template must appear in rendered prompt');
78
+ }
79
+
80
+ // Case 3: reset to default restores the original template + hash.
81
+ {
82
+ const originalConfig = normalizeAutomationConfig({});
83
+ const overrideConfig = normalizeAutomationConfig({ commitPromptTemplate: 'OVERRIDE {{commitHash}}' });
84
+ const resetConfig = normalizeAutomationConfig({ ...overrideConfig, commitPromptTemplate: '' });
85
+ assert.strictEqual(resetConfig.commitPromptTemplate, originalConfig.commitPromptTemplate, 'empty override restores default');
86
+ }
87
+
88
+ // Case 4: malformed template (no required tokens) — the renderer must
89
+ // still produce a deterministic hash; downstream code (prepareClaim)
90
+ // is what enforces minimum content via ensureRequiredSections.
91
+ {
92
+ const rendered = renderCommitPrompt({
93
+ ...baseInput,
94
+ config: { ...baseInput.config, automation: { commitPromptTemplate: 'NO-TOKENS-HERE' } },
95
+ });
96
+ assert(rendered.prompt.includes('NO-TOKENS-HERE'));
97
+ assert(rendered.promptHash.startsWith('sha256:'));
98
+ }
99
+
100
+ // Case 5: settings.promptOverrides is wired through settings updatePatch.
101
+ // Persisting a prompt override via the canonical settings API must
102
+ // appear in subsequent reads (the next analyzer invocation will pick
103
+ // up the new template via this path).
104
+ {
105
+ await settings.updatePatch({ promptOverrides: { commitPromptTemplate: 'NEW {{commitHash}} {{commitSubject}}' } });
106
+ const current = settings.read();
107
+ assert.strictEqual(current.promptOverrides.commitPromptTemplate, 'NEW {{commitHash}} {{commitSubject}}');
108
+ }
109
+
110
+ fs.rmSync(dataDir, { recursive: true, force: true });
111
+ console.log('prompt-settings-test PASS');
112
+ })().catch(error => {
113
+ console.error(error.stack || error.message);
114
+ process.exit(1);
115
+ });