@sanqianx/project-knowledge 4.6.1 → 4.8.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 (198) hide show
  1. package/.agents/plugins/marketplace.json +17 -17
  2. package/.claude-plugin/marketplace.json +1 -1
  3. package/CHANGELOG.md +817 -780
  4. package/README.md +48 -36
  5. package/_modules/knowledge-engine/README.md +88 -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 +255 -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/migration.js +88 -0
  15. package/_modules/knowledge-engine/src/retrieval.js +116 -0
  16. package/_modules/knowledge-engine/src/runtime/README.md +7 -0
  17. package/_modules/knowledge-engine/src/runtime/atomic-file.js +204 -0
  18. package/_modules/knowledge-engine/src/runtime/automation-config.js +140 -0
  19. package/_modules/knowledge-engine/src/runtime/commit-conversation-binder.js +249 -0
  20. package/_modules/knowledge-engine/src/runtime/commit-processing-ledger.js +26 -0
  21. package/_modules/knowledge-engine/src/runtime/commit-prompt.js +202 -0
  22. package/_modules/knowledge-engine/src/runtime/commit-reconciler.js +432 -0
  23. package/_modules/knowledge-engine/src/runtime/content-hash.js +4 -0
  24. package/_modules/knowledge-engine/src/runtime/contracts.js +279 -0
  25. package/_modules/knowledge-engine/src/runtime/conversation-exclusions.js +204 -0
  26. package/_modules/knowledge-engine/src/runtime/conversation-store.js +387 -0
  27. package/_modules/knowledge-engine/src/runtime/evidence-bundle.js +228 -0
  28. package/_modules/knowledge-engine/src/runtime/git-runner.js +62 -0
  29. package/_modules/knowledge-engine/src/runtime/knowledge-promotion.js +444 -0
  30. package/_modules/knowledge-engine/src/runtime/knowledge-retrieval-service.js +402 -0
  31. package/_modules/knowledge-engine/src/runtime/layout.js +44 -0
  32. package/_modules/knowledge-engine/src/runtime/markdown-knowledge-indexer.js +230 -0
  33. package/_modules/knowledge-engine/src/runtime/scanner.js +298 -0
  34. package/_modules/knowledge-engine/src/server.js +155 -0
  35. package/_modules/knowledge-engine/src/store.js +239 -0
  36. package/_modules/knowledge-engine/ui/app.css +172 -0
  37. package/_modules/knowledge-engine/ui/app.js +297 -0
  38. package/_modules/knowledge-engine/ui/index.html +95 -0
  39. package/_site/README.md +30 -30
  40. package/_site/_test/ai-profile-resolver-test.js +137 -137
  41. package/_site/_test/automation-queue-test.js +14 -14
  42. package/_site/_test/automation-ui-test.js +40 -40
  43. package/_site/_test/background-task-registry-test.js +43 -43
  44. package/_site/_test/baseline-schema-test.js +91 -91
  45. package/_site/_test/bridge-adapter-test.js +1 -1
  46. package/_site/_test/bridge-consumer-service-test.js +1 -1
  47. package/_site/_test/chat-claudecodeui-match-test.js +92 -92
  48. package/_site/_test/claude-executable-discovery-test.js +62 -62
  49. package/_site/_test/claude-workbench-test.js +170 -170
  50. package/_site/_test/client-capture-restart-e2e-test.js +4 -4
  51. package/_site/_test/codex-conversation-projection-test.js +2 -1
  52. package/_site/_test/commit-boundary-freeze-test.js +1 -1
  53. package/_site/_test/commit-conversation-binding-test.js +95 -95
  54. package/_site/_test/commit-evidence-test.js +110 -110
  55. package/_site/_test/conversation-api-test.js +109 -109
  56. package/_site/_test/conversation-store-test.js +136 -136
  57. package/_site/_test/cross-repo-isolation-e2e-test.js +2 -2
  58. package/_site/_test/data-dir-migration-test.js +57 -57
  59. package/_site/_test/desktop-browser-compat-test.js +28 -28
  60. package/_site/_test/desktop-hook-runtime-regression-test.js +204 -204
  61. package/_site/_test/explicit-commit-processor-test.js +37 -37
  62. package/_site/_test/fixtures/make-git-repos.js +12 -1
  63. package/_site/_test/folder-picker-output-test.js +19 -19
  64. package/_site/_test/full-integration-e2e-test.js +165 -165
  65. package/_site/_test/git-validation-test.js +2 -2
  66. package/_site/_test/hook-runtime-endpoint-test.js +88 -88
  67. package/_site/_test/hook-status-repair-api-test.js +152 -152
  68. package/_site/_test/hook-trigger-test.js +85 -85
  69. package/_site/_test/import-preflight-api-test.js +234 -233
  70. package/_site/_test/index-writer-concurrency-test.js +135 -135
  71. package/_site/_test/integration-adapters-test.js +171 -171
  72. package/_site/_test/integration-capture-setup-test.js +1 -1
  73. package/_site/_test/integration-surface-coverage-test.js +164 -164
  74. package/_site/_test/knowledge-language-control-test.js +170 -170
  75. package/_site/_test/knowledge-promotion-recovery-test.js +260 -260
  76. package/_site/_test/knowledge-query-test.js +54 -54
  77. package/_site/_test/knowledge-retrieval-service-test.js +77 -77
  78. package/_site/_test/knowledge-storage-startup-test.js +52 -52
  79. package/_site/_test/knowledge-store-logs-supervision-test.js +89 -89
  80. package/_site/_test/legacy-forward-compat-test.js +257 -257
  81. package/_site/_test/legacy-project-upgrade-e2e-test.js +363 -363
  82. package/_site/_test/logging-api-test.js +89 -89
  83. package/_site/_test/logging-sse-no-gap-test.js +88 -88
  84. package/_site/_test/logging-ui-test.js +106 -104
  85. package/_site/_test/markdown-delta-overlay-test.js +85 -85
  86. package/_site/_test/markdown-maintenance-api-test.js +75 -75
  87. package/_site/_test/mcp-server-test.js +149 -149
  88. package/_site/_test/module-artifact-integrity-test.js +27 -0
  89. package/_site/_test/module-boundary-test.js +30 -0
  90. package/_site/_test/module-bridge-eventbridge-test.js +7 -0
  91. package/_site/_test/module-model-configuration-test.js +43 -0
  92. package/_site/_test/module-process-lifecycle-test.js +24 -0
  93. package/_site/_test/module-stream-proxy-test.js +34 -0
  94. package/_site/_test/non-release-ci-test.js +34 -34
  95. package/_site/_test/offline-boundary-isolation-test.js +32 -32
  96. package/_site/_test/p0-data-migration-characterization-test.js +37 -37
  97. package/_site/_test/p0-e2e-gate-test.js +347 -347
  98. package/_site/_test/packaged-ui-smoke-test.js +19 -10
  99. package/_site/_test/path-consistency-test.js +151 -151
  100. package/_site/_test/pending-sweep-test.js +9 -9
  101. package/_site/_test/post-commit-automation-test.js +87 -87
  102. package/_site/_test/product-diagnostics-ui-test.js +75 -0
  103. package/_site/_test/product-import-ui-test.js +92 -0
  104. package/_site/_test/project-delete-recovery-test.js +64 -64
  105. package/_site/_test/project-goal-editor-test.js +144 -144
  106. package/_site/_test/project-layout-v2-migration-test.js +127 -127
  107. package/_site/_test/project-lifecycle-transaction-test.js +106 -106
  108. package/_site/_test/prompt-settings-test.js +115 -115
  109. package/_site/_test/protected-architecture-gate-test.js +122 -122
  110. package/_site/_test/refactor-characterization-test.js +36 -36
  111. package/_site/_test/release-version-sync-test.js +1 -1
  112. package/_site/_test/requirement-recorder-test.js +173 -173
  113. package/_site/_test/run-all-tests.js +159 -156
  114. package/_site/_test/server-runtime-migration-safety-test.js +27 -27
  115. package/_site/_test/shared-contracts-test.js +58 -58
  116. package/_site/_test/startup-analysis-disabled-test.js +10 -10
  117. package/_site/_test/storage-foundation-test.js +78 -78
  118. package/_site/_test/structured-logger-test.js +101 -101
  119. package/_site/_test/tracking-start-test.js +108 -108
  120. package/_site/_test/v4122-upgrade-data-contract-test.js +56 -56
  121. package/_site/_test/workbench-permission-test.js +117 -117
  122. package/_site/_test/workspace-ui-contract-test.js +28 -43
  123. package/_site/lib/ai-profile-resolver.js +78 -78
  124. package/_site/lib/ai-workspace.js +101 -101
  125. package/_site/lib/automation-config.js +140 -140
  126. package/_site/lib/bridge-adapter.js +5 -1
  127. package/_site/lib/bridge-consumer-service.js +402 -402
  128. package/_site/lib/claude-cli-runner.js +1510 -1510
  129. package/_site/lib/commit-conversation-binder.js +234 -234
  130. package/_site/lib/commit-processing-ledger.js +27 -27
  131. package/_site/lib/commit-prompt.js +202 -202
  132. package/_site/lib/commit-reconciler.js +406 -406
  133. package/_site/lib/contracts.js +284 -279
  134. package/_site/lib/conversation-query-service.js +144 -144
  135. package/_site/lib/conversation-store.js +385 -385
  136. package/_site/lib/data-dir.js +74 -74
  137. package/_site/lib/data-state-classifier.js +40 -40
  138. package/_site/lib/engine-cutover.js +97 -0
  139. package/_site/lib/evidence-bundle.js +228 -228
  140. package/_site/lib/folder-picker-output.js +21 -21
  141. package/_site/lib/git-runner.js +62 -62
  142. package/_site/lib/github-team-store.js +1172 -1172
  143. package/_site/lib/hook-manager.js +203 -202
  144. package/_site/lib/index-service.js +173 -173
  145. package/_site/lib/integration-installer.js +907 -907
  146. package/_site/lib/kb-framework.js +176 -176
  147. package/_site/lib/kb-validator.js +124 -124
  148. package/_site/lib/knowledge-promotion.js +435 -435
  149. package/_site/lib/knowledge-retrieval-service.js +401 -401
  150. package/_site/lib/knowledge-tool-runtime.js +359 -359
  151. package/_site/lib/legacy-data-manifest.js +26 -26
  152. package/_site/lib/llm-client.js +161 -161
  153. package/_site/lib/markdown-knowledge-indexer.js +230 -230
  154. package/_site/lib/migration-service.js +442 -442
  155. package/_site/lib/module-bridge.js +198 -33
  156. package/_site/lib/post-commit-automation.js +87 -87
  157. package/_site/lib/project-lifecycle-service.js +519 -497
  158. package/_site/lib/project-store.js +268 -262
  159. package/_site/lib/requirement-recorder.js +278 -278
  160. package/_site/lib/runtime-endpoint.js +155 -155
  161. package/_site/lib/scanner.js +298 -298
  162. package/_site/lib/server-app.js +1590 -1520
  163. package/_site/lib/settings-store.js +133 -113
  164. package/_site/lib/storage-layout.js +162 -162
  165. package/_site/lib/structured-logger.js +574 -574
  166. package/_site/scripts/folder-picker.ps1 +156 -156
  167. package/_site/scripts/hook-trigger.js +157 -156
  168. package/_site/scripts/install-module-candidates.js +69 -0
  169. package/_site/scripts/pack-module-candidates.js +72 -0
  170. package/_site/scripts/sync-release-version.js +2 -2
  171. package/_site/scripts/vendor-modules.js +47 -0
  172. package/_site/scripts/verify-module-runtime.js +45 -0
  173. package/_site/scripts/verify-product-runtime.js +34 -0
  174. package/bin/project-knowledge-mcp.js +194 -194
  175. package/docs/README.zh-CN.md +34 -25
  176. package/docs/project-registry-schema.md +22 -22
  177. package/docs/testing-strategy.md +33 -33
  178. package/module-runtime-manifest.json +220 -0
  179. package/package.json +13 -10
  180. package/plugins/project-knowledge/.claude-plugin/plugin.json +1 -1
  181. package/plugins/project-knowledge/.codex-plugin/plugin.json +1 -1
  182. package/plugins/project-knowledge/.mcp.json +1 -1
  183. package/plugins/project-knowledge/opencode/project-knowledge.md +3 -3
  184. package/plugins/project-knowledge/skills/project-knowledge/SKILL.md +28 -28
  185. package/templates/project-readme.md +34 -34
  186. package/ui/favicon.svg +38 -38
  187. package/ui/index.html +51 -149
  188. package/ui/product.css +9 -0
  189. package/ui/product.js +386 -0
  190. package/vendor-manifest.json +46 -0
  191. package/_site/_test/import-ui-flow-test.js +0 -171
  192. package/_site/_test/project-control-panel-task14-test.js +0 -63
  193. package/_site/_test/task15-20-ui-flow-test.js +0 -148
  194. package/_site/_test/ui-i18n-toggle-test.js +0 -114
  195. package/_site/_test/ui-smoke-test.js +0 -73
  196. package/ui/app.css +0 -58
  197. package/ui/app.js +0 -607
  198. package/ui/i18n.js +0 -146
@@ -0,0 +1,239 @@
1
+ 'use strict';
2
+
3
+ const fs = require('fs');
4
+ const path = require('path');
5
+ const crypto = require('crypto');
6
+ const { validateProjectId } = require('./runtime/contracts');
7
+ const { RuntimeLayout } = require('./runtime/layout');
8
+
9
+ /**
10
+ * 单文件 JSON 状态层(<data>/engine-state.json):注册项目、分析运行、事件流。
11
+ * 任务、消费游标与受控写入元数据一起原子落盘,运行期凭据不进入本层。
12
+ */
13
+ class EngineStore {
14
+ constructor(dataDir, options = {}) {
15
+ this.dataDir = dataDir;
16
+ this.file = path.join(dataDir, 'engine-state.json');
17
+ this.state = { schemaVersion: 2, projects: [], runs: [], events: [], cursor: 0, journalCursors: {}, memories: [], sharedScopes: [] };
18
+ if (!options.deferLoad) this.load();
19
+ }
20
+
21
+ load() {
22
+ try {
23
+ const raw = JSON.parse(fs.readFileSync(this.file, 'utf8'));
24
+ if (raw && (raw.schemaVersion === undefined || raw.schemaVersion === 1)) {
25
+ const backup = path.join(this.dataDir, `legacy-debug-state-${crypto.randomUUID()}.json`);
26
+ require('./runtime/atomic-file').writeJsonAtomic(backup, raw);
27
+ this.state.legacyStateBackup = backup;
28
+ this.save();
29
+ return;
30
+ }
31
+ if (raw?.schemaVersion !== 2) throw new Error('Unsupported engine state');
32
+ if (!raw || !Array.isArray(raw.projects) || !Array.isArray(raw.runs) || !Array.isArray(raw.events) || !Number.isSafeInteger(raw.cursor) || raw.cursor < 0) throw new Error('Invalid engine state');
33
+ if (raw.memories && !Array.isArray(raw.memories) || raw.sharedScopes && !Array.isArray(raw.sharedScopes) || raw.journalCursors && (typeof raw.journalCursors !== 'object' || Array.isArray(raw.journalCursors))) throw new Error('Invalid engine projections');
34
+ const ids = new Set();
35
+ for (const project of raw.projects) {
36
+ validateProjectId(project.projectId);
37
+ if (ids.has(project.projectId) || !path.isAbsolute(project.repoPath || '') || !path.isAbsolute(project.knowledgePath || '') || !project.runtime?.analysis || !project.runtime?.index) throw new Error('Invalid project runtime');
38
+ ids.add(project.projectId);
39
+ }
40
+ const runIds = new Set();
41
+ for (const run of raw.runs) {
42
+ validateProjectId(run.projectId);
43
+ if (!/^run-[a-f0-9-]{36}$/.test(run.runId || '') || runIds.has(run.runId) || !/^[a-f0-9]{40}$/.test(run.commitSha || '') || !['queued', 'running', 'failed', 'succeeded', 'index-pending'].includes(run.state) || !Number.isSafeInteger(run.sequence) || run.sequence < 1) throw new Error('Invalid queued run');
44
+ runIds.add(run.runId);
45
+ }
46
+ for (const [journalId, sequence] of Object.entries(raw.journalCursors || {})) if (!/^(global|sha256:[a-f0-9]{64})$/.test(journalId) || !Number.isSafeInteger(sequence) || sequence < 0) throw new Error('Invalid journal cursor');
47
+ for (const scope of raw.sharedScopes || []) if (!scope || !['brain', 'domain'].includes(scope.kind) || !path.isAbsolute(scope.knowledgePath || '')) throw new Error('Invalid shared scope');
48
+ for (const memory of raw.memories || []) if (!memory || !['candidate', 'confirmed', 'ignored'].includes(memory.status) || typeof memory.text !== 'string' || !memory.evidence?.projectId || !/^[a-f0-9]{40}$/.test(memory.evidence.commitSha || '')) throw new Error('Invalid memory record');
49
+ this.state = { ...this.state, ...raw };
50
+ } catch (error) {
51
+ if (error.code !== 'ENOENT') throw Object.assign(new Error('引擎状态损坏,已保留原文件;请从备份恢复。'), { code: 'DATA_CORRUPT', cause: error });
52
+ }
53
+ }
54
+
55
+ isEmpty() {
56
+ return this.state.projects.length === 0 && this.state.runs.length === 0;
57
+ }
58
+
59
+ replace(next) {
60
+ this.state = next;
61
+ this.save();
62
+ }
63
+
64
+ save() {
65
+ fs.mkdirSync(this.dataDir, { recursive: true });
66
+ const tmp = this.file + '.tmp';
67
+ const fd = fs.openSync(tmp, 'w', 0o600);
68
+ try { fs.writeFileSync(fd, JSON.stringify(this.state, null, 2), 'utf8'); fs.fsyncSync(fd); }
69
+ finally { fs.closeSync(fd); }
70
+ fs.renameSync(tmp, this.file);
71
+ }
72
+
73
+ listProjects() {
74
+ return this.state.projects.slice();
75
+ }
76
+
77
+ getProject(projectId) {
78
+ return this.state.projects.find(p => p.projectId === projectId) || null;
79
+ }
80
+
81
+ addProject(input) {
82
+ validateProjectId(input.projectId);
83
+ const layout = new RuntimeLayout(this.dataDir);
84
+ if (!path.isAbsolute(input.repoPath || '') || !path.isAbsolute(input.knowledgePath || '')) throw Object.assign(new Error('代码目录和知识目录必须为绝对路径。'), { status: 400, code: 'INVALID_ARGUMENT' });
85
+ const existing = this.getProject(input.projectId);
86
+ if (input.knowledgeLanguage !== undefined && !['zh-CN', 'en-US'].includes(input.knowledgeLanguage)) throw Object.assign(new Error('知识语言无效。'), { status: 400, code: 'INVALID_ARGUMENT' });
87
+ if (this.state.runs.some(r => r.projectId === input.projectId && ['queued', 'running'].includes(r.state)) && existing
88
+ && (!layout.pathsEqual(existing.repoPath, input.repoPath) || !layout.pathsEqual(existing.knowledgePath, input.knowledgePath))) throw Object.assign(new Error('运行中不能改变目录绑定。'), { status: 409, code: 'PROJECT_BUSY' });
89
+ for (const project of this.state.projects) {
90
+ if (project.projectId !== input.projectId && (layout.isPathInside(project.knowledgePath, input.knowledgePath, { realpath: true }) || layout.isPathInside(input.knowledgePath, project.knowledgePath, { realpath: true }))) throw Object.assign(new Error('知识目录与已登记项目冲突。'), { status: 409, code: 'KNOWLEDGE_PATH_CONFLICT' });
91
+ }
92
+ for (const scope of this.state.sharedScopes) {
93
+ if (layout.isPathInside(scope.knowledgePath, input.knowledgePath, { realpath: true }) || layout.isPathInside(input.knowledgePath, scope.knowledgePath, { realpath: true })) throw Object.assign(new Error('项目目录不能占用共享知识目录。'), { status: 409, code: 'KNOWLEDGE_PATH_CONFLICT' });
94
+ }
95
+ // Registration is a projection, not a second settings/credential store.
96
+ const fields = { projectId: input.projectId, name: input.name || input.projectId, repoPath: path.resolve(input.repoPath), knowledgePath: path.resolve(input.knowledgePath), brainId: input.brainId || existing?.brainId || 'brain-default', knowledgeLanguage: input.knowledgeLanguage || existing?.knowledgeLanguage || 'zh-CN', enabled: input.enabled !== false };
97
+ for (const key of ['brainId', 'domainId', 'journalId', 'vectorProjectId', 'profileId', 'trackingStartCommit']) {
98
+ if (input[key] !== undefined) fields[key] = input[key] === null ? null : String(input[key]);
99
+ }
100
+ if (input.relatedProjectIds) fields.relatedProjectIds = input.relatedProjectIds.map(validateProjectId);
101
+ if (Number.isSafeInteger(input.conversationBaselineCursor)) fields.conversationBaselineCursor = input.conversationBaselineCursor;
102
+ if (input.conversationBaselineCursors) fields.conversationBaselineCursors = Object.fromEntries(Object.entries(input.conversationBaselineCursors).map(([id, cursor]) => {
103
+ if (!/^(global|sha256:[a-f0-9]{64})$/.test(id) || !Number.isSafeInteger(cursor) || cursor < 0) throw Object.assign(new Error('会话跟踪基线无效。'), { status: 400, code: 'INVALID_ARGUMENT' });
104
+ return [id, cursor];
105
+ }));
106
+ if (input.repoIdentity) fields.repoIdentity = Object.fromEntries(['schema', 'workspaceId', 'workspaceRoot', 'commonDir', 'remote'].map(key => [key, input.repoIdentity[key] == null ? null : String(input.repoIdentity[key])]));
107
+ if (existing) {
108
+ const priorBaselines = { ...(existing.runtime.conversationBaselineCursors || {}) };
109
+ if (!Object.hasOwn(priorBaselines, 'global')) priorBaselines.global = existing.runtime.conversationBaselineCursor ?? 0;
110
+ for (const [journalId, cursor] of Object.entries(fields.conversationBaselineCursors || {})) if (!Object.hasOwn(priorBaselines, journalId)) priorBaselines[journalId] = cursor;
111
+ existing.runtime.conversationBaselineCursors = priorBaselines;
112
+ delete fields.conversationBaselineCursor;
113
+ delete fields.conversationBaselineCursors;
114
+ Object.assign(existing, fields, { updatedAt: new Date().toISOString() });
115
+ this.save();
116
+ return { project: existing, reused: true };
117
+ }
118
+ const project = {
119
+ ...fields,
120
+ runtime: initialRuntime(input),
121
+ lastCommitSha: null,
122
+ lastRunAt: null,
123
+ createdAt: new Date().toISOString(),
124
+ updatedAt: new Date().toISOString(),
125
+ };
126
+ this.state.projects.push(project);
127
+ this.save();
128
+ return { project, reused: false };
129
+ }
130
+
131
+ removeProject(projectId) {
132
+ if (this.state.runs.some(r => r.projectId === projectId && ['queued', 'running'].includes(r.state))) throw Object.assign(new Error('请等待当前维护运行结束后再解绑。'), { status: 409, code: 'PROJECT_BUSY' });
133
+ const before = this.state.projects.length;
134
+ this.state.projects = this.state.projects.filter(p => p.projectId !== projectId);
135
+ this.save();
136
+ return this.state.projects.length < before;
137
+ }
138
+
139
+ listRuns(projectId) {
140
+ const runs = projectId
141
+ ? this.state.runs.filter(r => r.projectId === projectId)
142
+ : this.state.runs;
143
+ return runs.slice().sort((a, b) => String(b.startedAt).localeCompare(String(a.startedAt)));
144
+ }
145
+
146
+ getRun(runId) {
147
+ return this.state.runs.find(r => r.runId === runId) || null;
148
+ }
149
+
150
+ addRun(input) {
151
+ if (!/^[a-f0-9]{40}$/i.test(input.commitSha || '')) throw Object.assign(new Error('必须提供完整的真实提交 SHA。'), { status: 400, code: 'INVALID_ARGUMENT' });
152
+ const prior = this.state.runs.find(r => r.projectId === input.projectId && r.commitSha === input.commitSha.toLowerCase());
153
+ if (prior) return prior;
154
+ const run = {
155
+ runId: 'run-' + crypto.randomUUID(),
156
+ projectId: input.projectId,
157
+ commitSha: input.commitSha.toLowerCase(),
158
+ trigger: input.trigger || 'manual',
159
+ note: input.note || '',
160
+ state: 'queued',
161
+ startedAt: new Date().toISOString(),
162
+ finishedAt: null,
163
+ durationMs: null,
164
+ error: null,
165
+ staging: null,
166
+ phase: 'queued',
167
+ sequence: this.state.runs.length + 1,
168
+ indexState: 'pending',
169
+ journalId: input.journalId || null,
170
+ journalSequence: input.journalSequence || null,
171
+ };
172
+ this.state.runs.push(run);
173
+ const project = this.getProject(input.projectId);
174
+ if (project) {
175
+ project.lastCommitSha = run.commitSha;
176
+ project.lastRunAt = run.startedAt;
177
+ }
178
+ this.save();
179
+ return run;
180
+ }
181
+
182
+ updateRun(runId, patch) {
183
+ const run = this.getRun(runId);
184
+ if (!run) return null;
185
+ Object.assign(run, patch);
186
+ if (run.finishedAt && run.state !== 'queued') {
187
+ run.durationMs = Math.max(0, new Date(run.finishedAt) - new Date(run.startedAt));
188
+ }
189
+ this.save();
190
+ return run;
191
+ }
192
+
193
+ addEvent(input) {
194
+ const event = {
195
+ seq: ++this.state.cursor,
196
+ type: input.type,
197
+ projectId: input.projectId || null,
198
+ summary: input.summary || '',
199
+ at: new Date().toISOString(),
200
+ runId: input.runId || null,
201
+ commitSha: input.commitSha || null,
202
+ phase: input.phase || null,
203
+ };
204
+ this.state.events.push(event);
205
+ if (this.state.events.length > 500) this.state.events = this.state.events.slice(-500);
206
+ this.save();
207
+ return event;
208
+ }
209
+
210
+ listEvents(limit) {
211
+ const cap = Math.max(1, Math.min(Number(limit) || 50, 500));
212
+ return this.state.events.slice(-cap).reverse();
213
+ }
214
+
215
+ readRuntime(projectId) {
216
+ const project = this.getProject(projectId);
217
+ if (!project) throw Object.assign(new Error('项目未登记。'), { code: 'PROJECT_NOT_FOUND', status: 404 });
218
+ return structuredClone(project.runtime || initialRuntime(project));
219
+ }
220
+
221
+ async updateRuntime(projectId, fn) {
222
+ const project = this.getProject(projectId);
223
+ const draft = this.readRuntime(projectId);
224
+ const result = fn(draft);
225
+ if (result && typeof result.then === 'function') throw new Error('Runtime mutations must be synchronous');
226
+ project.runtime = draft;
227
+ this.save();
228
+ return structuredClone(draft);
229
+ }
230
+
231
+ setJournalCursor(journalId, sequence) {
232
+ this.state.journalCursors[journalId] = Math.max(this.state.journalCursors[journalId] || 0, sequence);
233
+ this.save();
234
+ }
235
+ }
236
+
237
+ function initialRuntime(project = {}) { return { lastAnalyzedCommit: project.trackingStartCommit || null, conversationBaselineCursor: project.conversationBaselineCursor ?? null, conversationBaselineCursors: project.conversationBaselineCursors || {}, consumedRequirements: [], analysis: { status: 'idle', activeClaim: null, lastError: null }, index: { generation: 0, dirty: false, status: 'pending' } }; }
238
+
239
+ module.exports = { EngineStore, initialRuntime };
@@ -0,0 +1,172 @@
1
+ /* 与 event-bridge / vector-hub 一致:灰阶四层、单一蓝与克制的语义色。 */
2
+ :root {
3
+ --bg-side: #191a1d; --bg-main: #202125; --bg-card: #292a2f; --bg-card-raised: #2d2f35; --bg-input: #34363c;
4
+ --bg-hover: #303237; --bg-active: #393b42; --line: #3b3d44; --line-soft: #2d2f35; --text: #ebebed;
5
+ --text-2: #a9abb1; --text-3: #73757c; --label: #91939a; --blue: #5b93f7; --blue-soft: rgba(91, 147, 247, .13);
6
+ --green: #42cf9d; --green-soft: rgba(66, 207, 157, .11); --red: #ec776c; --red-soft: rgba(236, 119, 108, .12);
7
+ --amber: #e8ae5c; --amber-soft: rgba(232, 174, 92, .12); --mono: Consolas, "Microsoft YaHei", monospace;
8
+ }
9
+ * { box-sizing: border-box; margin: 0; padding: 0; }
10
+ .debug-only { display: none !important; }
11
+ .engine-debug .debug-only { display: flex !important; }
12
+ body.kb-embedded:not([data-kb-theme="dark"]) {
13
+ --bg-side: #f8f9fb; --bg-main: #ffffff; --bg-card: #ffffff; --bg-card-raised: #f8f9fb; --bg-input: #edf1f4;
14
+ --bg-hover: #edf1f4; --bg-active: #e6edf3; --line: #dce2e8; --line-soft: #dce2e8;
15
+ --text: #202733; --text-2: #687587; --text-3: #8e99a7; --label: #687587;
16
+ --blue: #516f89; --blue-soft: #e6edf3; --green: #397761; --green-soft: #edf6f1; --red: #a05252; --red-soft: #fbeeee;
17
+ color-scheme: light;
18
+ }
19
+ body.kb-embedded[data-kb-theme="dark"] { --bg-side:#1b2028; --bg-main:#20252d; --bg-card:#252b34; --bg-card-raised:#2c333e; --bg-input:#2c333e; --bg-hover:#2c333e; --bg-active:#293748; --line:#39424f; --line-soft:#39424f; --text:#e6ebf0; --text-2:#a0aaba; --text-3:#788393; --label:#a0aaba; --blue:#8aa8cf; --blue-soft:#293748; --green:#81b29a; --red:#d88b8b; }
20
+ .kb-embedded aside { width: 208px; min-width: 208px; }
21
+ .kb-embedded .brand { display: none; }
22
+ .kb-embedded #btnShowRegister, .kb-embedded #registerForm, .kb-embedded [data-del] { display: none; }
23
+ .kb-embedded main { padding: 24px 32px; max-width: none; }
24
+ .kb-embedded .metric strong { font-size: 24px; }
25
+ html, body { min-height: 100%; }
26
+ body { background: var(--bg-main); color: var(--text); font: 13px/1.55 -apple-system, BlinkMacSystemFont, "Segoe UI", "Microsoft YaHei", sans-serif; color-scheme: dark; }
27
+ button, input, select { font: inherit; }
28
+ button { color: inherit; background: none; border: 0; cursor: pointer; }
29
+ button:focus-visible, input:focus-visible, select:focus-visible { outline: 2px solid var(--blue); outline-offset: 2px; }
30
+ ::-webkit-scrollbar { width: 10px; height: 10px; }
31
+ ::-webkit-scrollbar-thumb { background: #4a4c53; border: 3px solid transparent; border-radius: 99px; background-clip: content-box; }
32
+ .hidden { display: none !important; }
33
+ .mono { color: var(--text-2); font-family: var(--mono); font-size: 11.5px; }
34
+
35
+ #app { display: flex; min-height: 100vh; }
36
+ aside { width: 224px; min-width: 224px; background: var(--bg-side); border-right: 1px solid var(--line-soft); display: flex; flex-direction: column; position: sticky; top: 0; height: 100vh; }
37
+ .brand { display: flex; align-items: center; gap: 9px; padding: 17px 15px 16px; border-bottom: 1px solid var(--line-soft); }
38
+ .mark { height: 25px; width: 25px; display: grid; place-items: center; border-radius: 7px; background: var(--text); color: #17181b; font: 700 10px/1 var(--mono); }
39
+ .brand-copy { display: grid; line-height: 1.2; }
40
+ .brand-copy strong { font-size: 12px; font-weight: 600; }
41
+ .brand-copy span { margin-top: 3px; color: var(--text-3); font-size: 10.5px; }
42
+ .nav { flex: 1; padding: 13px 8px; overflow-y: auto; }
43
+ .nav-label { padding: 0 10px 7px; color: var(--text-3); font-size: 10px; font-weight: 600; letter-spacing: .09em; text-transform: uppercase; }
44
+ .nav-label-lower { margin-top: 17px; }
45
+ .nav-item { width: 100%; display: flex; align-items: center; gap: 9px; padding: 8px 10px; border-radius: 6px; color: var(--text-2); font-size: 12.5px; text-align: left; }
46
+ .nav-symbol { width: 14px; color: var(--text-3); font-family: var(--mono); text-align: center; }
47
+ .nav-item:hover { background: var(--bg-hover); color: var(--text); }
48
+ .nav-item.on { background: var(--bg-active); color: var(--text); }
49
+ .nav-item.on .nav-symbol { color: var(--blue); }
50
+ .side-foot { display: flex; align-items: center; gap: 8px; padding: 12px 15px; border-top: 1px solid var(--line-soft); color: var(--text-3); font-size: 11px; }
51
+ .dot, .readiness-dot { width: 7px; height: 7px; flex: 0 0 auto; border-radius: 50%; background: var(--red); }
52
+ .dot.up, .is-ok { background: var(--green); box-shadow: 0 0 0 3px var(--green-soft); }
53
+
54
+ main { flex: 1; min-width: 0; padding: 28px clamp(22px, 4vw, 54px) 48px; }
55
+ .topbar, .view { max-width: 1220px; margin: 0 auto; }
56
+ .topbar { display: flex; align-items: end; justify-content: space-between; gap: 20px; padding-bottom: 22px; }
57
+ .eyebrow, .section-kicker { color: var(--text-3); font-size: 10.5px; font-weight: 600; letter-spacing: .08em; text-transform: uppercase; }
58
+ h1 { margin-top: 3px; font-size: 24px; font-weight: 600; line-height: 1.2; }
59
+ h2 { font-size: 14px; font-weight: 600; line-height: 1.35; }
60
+ .scope-control { display: grid; gap: 4px; min-width: 180px; color: var(--text-3); font-size: 10.5px; }
61
+ select, input { min-width: 0; width: 100%; padding: 7px 9px; background: var(--bg-input); border: 1px solid var(--line); border-radius: 6px; color: var(--text); font-size: 12.5px; }
62
+ select:focus, input:focus { border-color: var(--blue); outline: none; }
63
+ .view { display: none; }
64
+ .view.on { display: block; }
65
+ .view-intro { display: flex; align-items: center; justify-content: space-between; gap: 18px; margin-bottom: 16px; color: var(--text-2); }
66
+ .view-intro p { max-width: 720px; }
67
+ .view-intro.compact { margin-bottom: 14px; }
68
+ .btn { padding: 6px 11px; background: var(--bg-card-raised); border: 1px solid var(--line); border-radius: 6px; color: var(--text-2); font-size: 12px; white-space: nowrap; }
69
+ .btn:hover { background: var(--bg-hover); color: var(--text); }
70
+ .btn.primary { border-color: var(--blue); background: var(--blue); color: white; }
71
+ .btn.primary:hover { filter: brightness(1.08); }
72
+ .btn.danger:hover { border-color: var(--red); background: var(--red-soft); color: var(--red); }
73
+ .quiet-link { color: var(--blue); font-size: 11.5px; }
74
+ .quiet-link:hover { text-decoration: underline; }
75
+
76
+ .panel, .flow-panel { background: var(--bg-card); border: 1px solid var(--line-soft); border-radius: 9px; padding: 15px 16px; }
77
+ .panel-heading { display: flex; align-items: flex-start; justify-content: space-between; gap: 14px; margin-bottom: 13px; }
78
+ .panel-heading h2 { margin-top: 2px; }
79
+ .phase-pill, .badge { display: inline-flex; align-items: center; min-height: 20px; padding: 1px 8px; border-radius: 99px; font: 10.5px/1.5 var(--mono); white-space: nowrap; }
80
+ .phase-pill { border: 1px solid var(--line); color: var(--text-3); }
81
+ .flow-panel { margin-bottom: 12px; background: linear-gradient(110deg, var(--bg-card), #282b32); }
82
+ .flow-track { display: grid; grid-template-columns: repeat(5, minmax(0, 1fr)); list-style: none; overflow: hidden; border: 1px solid var(--line); border-radius: 7px; }
83
+ .flow-step { position: relative; display: flex; gap: 10px; min-height: 78px; padding: 13px 11px; background: rgba(0,0,0,.06); border-right: 1px solid var(--line); }
84
+ .flow-step:last-child { border-right: 0; }
85
+ .flow-step::after { content: ""; position: absolute; right: -1px; top: 13px; height: 18px; border-right: 2px solid var(--blue); opacity: .78; }
86
+ .flow-step:last-child::after { display: none; }
87
+ .flow-step.is-active { background: var(--blue-soft); }
88
+ .flow-step.is-done .flow-number { border-color: rgba(66,207,157,.55); color: var(--green); }
89
+ .flow-step.is-active .flow-number { border-color: rgba(91,147,247,.7); color: var(--blue); }
90
+ .flow-step.is-pending { opacity: .65; }
91
+ .flow-number { width: 21px; height: 21px; flex: 0 0 21px; display: grid; place-items: center; border: 1px solid var(--line); border-radius: 50%; color: var(--text-3); font: 10px/1 var(--mono); }
92
+ .flow-step strong { display: block; font-size: 12px; font-weight: 500; }
93
+ .flow-step small { display: block; margin-top: 4px; color: var(--text-3); font-size: 10.5px; line-height: 1.35; }
94
+
95
+ .metric-grid { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 10px; margin-bottom: 12px; }
96
+ .metric { padding: 13px 15px; background: var(--bg-card); border: 1px solid var(--line-soft); border-radius: 8px; }
97
+ .metric span, .metric small { display: block; }
98
+ .metric span { color: var(--label); font-size: 11px; }
99
+ .metric strong { display: block; margin: 2px 0; font: 600 23px/1.2 var(--mono); }
100
+ .metric small { color: var(--text-3); font-size: 10.5px; }
101
+ .metric-warn strong { color: var(--red); }
102
+ .overview-grid { display: grid; grid-template-columns: minmax(0, 1.45fr) minmax(300px, .85fr); gap: 12px; align-items: start; }
103
+ .table-wrap { overflow-x: auto; }
104
+ table { width: 100%; border-collapse: collapse; font-size: 12px; }
105
+ th { padding: 5px 8px 7px; border-bottom: 1px solid var(--line-soft); color: var(--label); font-size: 10.5px; font-weight: 500; text-align: left; white-space: nowrap; }
106
+ td { padding: 8px; border-bottom: 1px solid var(--line-soft); vertical-align: top; }
107
+ tbody tr:last-child td { border-bottom: 0; }
108
+ tbody tr.click { cursor: pointer; }
109
+ tbody tr.click:hover, tbody tr.sel { background: var(--bg-hover); }
110
+ td.wrap { min-width: 170px; word-break: break-word; }
111
+ .empty { padding: 16px; color: var(--text-3); text-align: center; }
112
+ .badge.queued, .badge.running { background: var(--blue-soft); color: var(--blue); }
113
+ .badge.running { animation: pulse 1.3s ease infinite; }
114
+ .badge.succeeded { background: var(--green-soft); color: var(--green); }
115
+ .badge.failed { background: var(--red-soft); color: var(--red); }
116
+ .badge.type { border: 1px solid var(--line); color: var(--text-2); }
117
+ @keyframes pulse { 50% { opacity: .55; } }
118
+ .attention-body { min-height: 48px; margin: -2px 0 12px; color: var(--text-2); }
119
+ .service-list { border-top: 1px solid var(--line-soft); font-size: 11.5px; }
120
+ .service-list div { display: grid; grid-template-columns: 105px 1fr; gap: 8px; padding: 7px 0; border-bottom: 1px solid var(--line-soft); }
121
+ .service-list div:last-child { border: 0; }
122
+ .service-list dt { color: var(--text-3); }
123
+ .service-list dd { min-width: 0; color: var(--text-2); overflow-wrap: anywhere; }
124
+
125
+ .toolbar { display: flex; gap: 8px; margin-bottom: 12px; }
126
+ .filters select { width: auto; min-width: 145px; }
127
+ .form { margin-bottom: 12px; }
128
+ .form-row { display: flex; gap: 10px; margin-bottom: 10px; }
129
+ .form label { display: flex; flex: 1; flex-direction: column; gap: 4px; color: var(--label); font-size: 11px; }
130
+ .form .checkbox { flex: 0 0 auto; flex-direction: row; align-items: center; gap: 7px; padding-top: 20px; color: var(--text-2); font-size: 12px; }
131
+ .form .checkbox input { width: auto; }
132
+ .form-actions { display: flex; gap: 8px; }
133
+ .detail-grid { display: grid; grid-template-columns: minmax(225px, .7fr) minmax(0, 1.3fr); gap: 20px; }
134
+ .kv div { display: grid; grid-template-columns: 92px minmax(0, 1fr); gap: 10px; padding: 5px 0; }
135
+ .kv dt { color: var(--text-3); }
136
+ .kv dd { min-width: 0; overflow-wrap: anywhere; }
137
+ .panel-sub { margin: 0 0 6px; color: var(--text-3); font-size: 10.5px; letter-spacing: .03em; text-transform: uppercase; }
138
+ .code { max-height: 270px; padding: 10px; overflow: auto; background: var(--bg-side); border: 1px solid var(--line-soft); border-radius: 6px; color: var(--text-2); font: 11px/1.55 var(--mono); white-space: pre-wrap; word-break: break-word; }
139
+ .run-timeline { display: grid; grid-template-columns: repeat(5, 1fr); gap: 6px; list-style: none; margin-bottom: 16px; }
140
+ .run-timeline li { position: relative; padding: 7px 8px; border-left: 2px solid var(--line); color: var(--text-3); font-size: 11px; }
141
+ .run-timeline li.done { border-color: var(--green); color: var(--green); }
142
+ .run-timeline li.current { border-color: var(--blue); color: var(--blue); }
143
+ .run-timeline li.failed { border-color: var(--red); color: var(--red); }
144
+
145
+ .knowledge-hero { display: flex; align-items: end; justify-content: space-between; gap: 22px; margin-bottom: 12px; }
146
+ .knowledge-path { max-width: 650px; margin-top: 5px; overflow-wrap: anywhere; }
147
+ .knowledge-summary { min-width: 145px; text-align: right; }
148
+ .knowledge-summary span, .knowledge-summary small { display: block; color: var(--text-3); font-size: 10.5px; }
149
+ .knowledge-summary strong { display: block; margin: 2px 0; font: 600 16px/1.3 var(--mono); }
150
+ .knowledge-list { display: grid; gap: 0; }
151
+ .knowledge-operation { display: grid; grid-template-columns: 74px minmax(0, 1fr) auto; gap: 11px; align-items: start; padding: 10px 0; border-bottom: 1px solid var(--line-soft); }
152
+ .knowledge-operation:last-child { border: 0; }
153
+ .operation-kind { color: var(--blue); font: 10.5px/1.7 var(--mono); }
154
+ .knowledge-operation strong { display: block; font: 12px/1.45 var(--mono); overflow-wrap: anywhere; }
155
+ .knowledge-operation small { display: block; margin-top: 2px; color: var(--text-3); font-size: 11px; }
156
+ .knowledge-operation em { color: var(--text-3); font-size: 10.5px; font-style: normal; white-space: nowrap; }
157
+ .readiness-list > div { display: grid; grid-template-columns: 12px 1fr auto; gap: 9px; align-items: start; padding: 10px 0; border-bottom: 1px solid var(--line-soft); }
158
+ .readiness-list > div:last-child { border: 0; }
159
+ .readiness-dot { margin-top: 5px; }
160
+ .readiness-dot.is-pending { background: var(--amber); box-shadow: 0 0 0 3px var(--amber-soft); }
161
+ .readiness-list strong { display: block; font-size: 12px; font-weight: 500; }
162
+ .readiness-list small { display: block; margin-top: 2px; color: var(--text-3); font-size: 11px; }
163
+ .readiness-list b { color: var(--text-3); font: 10.5px/1.7 var(--mono); white-space: nowrap; }
164
+ .answer { color: var(--text); line-height: 1.75; white-space: pre-wrap; }
165
+ .citations { padding-left: 19px; color: var(--text-2); font-size: 11.5px; }
166
+ .citations li { margin: 3px 0; }
167
+ .score { color: var(--blue); font-family: var(--mono); }
168
+ #toast { position: fixed; bottom: 18px; left: 50%; z-index: 5; max-width: min(92vw, 560px); padding: 8px 14px; transform: translateX(-50%) translateY(10px); background: var(--bg-card-raised); border: 1px solid var(--line); border-radius: 7px; color: var(--text); opacity: 0; pointer-events: none; transition: opacity .18s ease, transform .18s ease; }
169
+ #toast.show { transform: translateX(-50%); opacity: 1; }
170
+
171
+ @media (max-width: 980px) { .flow-track { grid-template-columns: 1fr; } .flow-step { min-height: auto; border-right: 0; border-bottom: 1px solid var(--line); } .flow-step:last-child { border-bottom: 0; } .flow-step::after { top: auto; bottom: -1px; right: auto; left: 16px; width: 18px; height: 0; border-right: 0; border-bottom: 2px solid var(--blue); } .metric-grid { grid-template-columns: repeat(2, 1fr); } .overview-grid { grid-template-columns: 1fr; } }
172
+ @media (max-width: 720px) { #app { display: block; } aside { position: static; width: auto; min-width: 0; height: auto; } .brand { padding-bottom: 11px; } .nav { display: flex; gap: 3px; padding: 7px 8px; overflow-x: auto; } .nav-label, .nav-symbol, .side-foot { display: none; } .nav-item { width: auto; white-space: nowrap; } main { padding: 21px 15px 36px; } .topbar { align-items: start; } .scope-control { min-width: 130px; } h1 { font-size: 21px; } .view-intro, .knowledge-hero { align-items: flex-start; flex-direction: column; } .form-row, .detail-grid { display: grid; grid-template-columns: 1fr; } .form .checkbox { padding-top: 0; } .run-timeline { grid-template-columns: 1fr; } .knowledge-summary { text-align: left; } }