@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,297 @@
1
+ 'use strict';
2
+
3
+ const $ = selector => document.querySelector(selector);
4
+ const $$ = selector => [...document.querySelectorAll(selector)];
5
+ const viewMeta = {
6
+ overview: ['knowledge control plane', '知识脉搏'],
7
+ runs: ['generation pipeline', '生成流水线'],
8
+ knowledge: ['canonical knowledge', '知识状态'],
9
+ retrieval: ['retrieval verification', '检索验证'],
10
+ projects: ['integration diagnostics', '项目接线'],
11
+ events: ['immutable evidence stream', '事件流'],
12
+ };
13
+ let currentView = 'overview';
14
+ let scopedProjectId = '';
15
+ let toastTimer;
16
+ let runPollTimer;
17
+ let selectedRunId = null;
18
+ let viewEpoch = 0;
19
+ const embedParams = new URLSearchParams(location.search);
20
+ const embedded = embedParams.get('embed') === '1';
21
+ let embedVisible = !embedded;
22
+ const apiBase = embedParams.get('apiBase') || '/api';
23
+ const parentOrigin = document.referrer ? new URL(document.referrer).origin : location.origin;
24
+ document.body.classList.toggle('kb-embedded', embedded);
25
+
26
+ async function api(path, options = {}) {
27
+ const init = { headers: { 'Content-Type': 'application/json' }, ...options };
28
+ if (init.body && typeof init.body !== 'string') init.body = JSON.stringify(init.body);
29
+ const response = await fetch(path.replace(/^\/api(?=\/|$)/, apiBase), init);
30
+ const payload = await response.json().catch(() => ({ ok: false, error: 'BAD_JSON' }));
31
+ if (!response.ok || payload.ok === false) {
32
+ toast(payload.message || payload.error || `HTTP ${response.status}`);
33
+ throw new Error(payload.error || 'API_ERROR');
34
+ }
35
+ return payload;
36
+ }
37
+
38
+ function toast(message) {
39
+ $('#toast').textContent = message;
40
+ $('#toast').classList.add('show');
41
+ clearTimeout(toastTimer);
42
+ toastTimer = setTimeout(() => $('#toast').classList.remove('show'), 2600);
43
+ }
44
+
45
+ function escapeHtml(value) { return String(value ?? '').replace(/[&<>"']/g, char => ({ '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#39;' }[char])); }
46
+ function fmtTime(iso) {
47
+ if (!iso) return '–';
48
+ const date = new Date(iso);
49
+ return `${String(date.getHours()).padStart(2, '0')}:${String(date.getMinutes()).padStart(2, '0')}:${String(date.getSeconds()).padStart(2, '0')}`;
50
+ }
51
+ function fmtDuration(ms) { return ms == null ? '–' : ms < 1000 ? `${ms}ms` : `${(ms / 1000).toFixed(1)}s`; }
52
+ function shortSha(sha) { return sha ? sha.slice(0, 7) : '–'; }
53
+ function stateBadge(state) { return `<span class="badge ${escapeHtml(state)}">${escapeHtml(({ queued: '排队', running: '运行中', succeeded: '可检索', failed: '失败', 'index-pending': '索引待修复' })[state] || state)}</span>`; }
54
+ function typeBadge(type) { return `<span class="badge type">${escapeHtml(type)}</span>`; }
55
+ function scopedPath(path) { return scopedProjectId ? `${path}${path.includes('?') ? '&' : '?'}projectId=${encodeURIComponent(scopedProjectId)}` : path; }
56
+ function successfulRun(runs) { return runs.find(run => run.promotedAt) || null; }
57
+
58
+ function setView(view) {
59
+ currentView = view;
60
+ viewEpoch++;
61
+ const [kicker, title] = viewMeta[view];
62
+ $('#viewKicker').textContent = kicker;
63
+ $('#viewTitle').textContent = title;
64
+ $$('.nav-item').forEach(button => button.classList.toggle('on', button.dataset.view === view));
65
+ $$('.view').forEach(section => section.classList.toggle('on', section.id === `view-${view}`));
66
+ refreshView(view);
67
+ }
68
+
69
+ $$('.nav-item').forEach(button => button.addEventListener('click', () => setView(button.dataset.view)));
70
+ $$('[data-goto]').forEach(button => button.addEventListener('click', () => setView(button.dataset.goto)));
71
+ $('#scopeProject').addEventListener('change', () => { scopedProjectId = $('#scopeProject').value; selectedRunId = null; viewEpoch++; refreshView(currentView); });
72
+
73
+ async function pollHealth() {
74
+ try {
75
+ const health = await api('/api/health');
76
+ document.body.classList.toggle('engine-debug', health.debug === true);
77
+ $('#healthDot').classList.add('up');
78
+ $('#healthText').textContent = `v${health.version} · 游标 ${health.journal.cursor}`;
79
+ return health;
80
+ } catch {
81
+ $('#healthDot').classList.remove('up');
82
+ $('#healthText').textContent = '服务离线';
83
+ return null;
84
+ }
85
+ }
86
+
87
+ async function loadProjectScope() {
88
+ const { projects } = await api('/api/projects');
89
+ const select = $('#scopeProject');
90
+ const previous = scopedProjectId || select.value;
91
+ select.innerHTML = '<option value="">全部项目</option>' + projects.map(project => `<option value="${escapeHtml(project.projectId)}">${escapeHtml(project.name)}</option>`).join('');
92
+ if ([...select.options].some(option => option.value === previous)) select.value = previous;
93
+ else { select.value = ''; scopedProjectId = ''; }
94
+ return projects;
95
+ }
96
+
97
+ async function refreshView(view) {
98
+ if (view === 'overview') loadOverview();
99
+ if (view === 'runs') { await loadProjectSelects(); loadRuns(); }
100
+ if (view === 'knowledge') loadKnowledge();
101
+ if (view === 'retrieval') loadProjectSelects();
102
+ if (view === 'projects') loadProjects();
103
+ if (view === 'events') loadEvents();
104
+ }
105
+
106
+ function setFlowState(id, className) {
107
+ const element = $(id);
108
+ element.classList.remove('is-active', 'is-done');
109
+ if (className) element.classList.add(className);
110
+ }
111
+
112
+ async function loadOverview() {
113
+ const epoch = viewEpoch;
114
+ const [health, projects, runPayload, eventPayload] = await Promise.all([
115
+ pollHealth(), loadProjectScope().catch(() => []), api(scopedPath('/api/runs')).catch(() => null), api('/api/events?limit=20').catch(() => null),
116
+ ]);
117
+ if (!health || !runPayload || epoch !== viewEpoch) return;
118
+ const runs = runPayload.runs;
119
+ const events = (eventPayload?.events || []).filter(event => !scopedProjectId || event.projectId === scopedProjectId);
120
+ const succeeded = runs.filter(run => run.promotedAt);
121
+ const failed = runs.filter(run => run.state === 'failed' || run.state === 'index-pending');
122
+ const running = runs.filter(run => run.state === 'queued' || run.state === 'running');
123
+ const latestSuccess = successfulRun(runs);
124
+ const latestEvent = events[0];
125
+ $('#statProjects').textContent = scopedProjectId ? '1' : projects.length;
126
+ $('#statProjectsDetail').textContent = scopedProjectId ? '当前选定项目' : '已登记的引擎接线';
127
+ $('#statRunning').textContent = running.length;
128
+ $('#statRunningDetail').textContent = running.length ? '正在推进生成链路' : '没有待处理运行';
129
+ $('#statSucceeded').textContent = succeeded.length;
130
+ $('#statSucceededDetail').textContent = latestSuccess ? `最近 ${shortSha(latestSuccess.commitSha)}` : '尚未晋升知识';
131
+ $('#statFailed').textContent = failed.length;
132
+ $('#statFailedDetail').textContent = failed.length ? '打开运行记录处理' : '没有失败运行';
133
+ setFlowState('#flowJournal', health.journal.enabled && !health.journal.error ? 'is-done' : '');
134
+ $('#flowJournalText').textContent = health.journal.error || (health.journal.enabled ? `${Object.keys(health.journal.cursors).length} 份日志正在消费` : '尚未移交自动维护所有权');
135
+ setFlowState('#flowReconcile', latestEvent ? 'is-done' : '');
136
+ $('#flowReconcileText').textContent = latestEvent ? `${latestEvent.type} · ${fmtTime(latestEvent.at)}` : '等待 commit boundary';
137
+ setFlowState('#flowAnalyze', running.length ? 'is-active' : latestSuccess ? 'is-done' : '');
138
+ $('#flowAnalyzeText').textContent = running.length ? `${running.length} 个运行正在分析` : latestSuccess ? `最近完成 ${shortSha(latestSuccess.commitSha)}` : '没有运行中的分析';
139
+ setFlowState('#flowPromote', latestSuccess ? 'is-done' : '');
140
+ $('#flowPromoteText').textContent = latestSuccess ? `${latestSuccess.staging?.operations?.length || 0} 项知识变更已晋升` : '尚未生成暂存产物';
141
+ setFlowState('#flowIndex', latestSuccess?.indexState === 'succeeded' ? 'is-done' : latestSuccess?.indexState === 'running' ? 'is-active' : '');
142
+ $('#flowIndexText').textContent = latestSuccess?.indexState === 'succeeded' ? '知识与派生索引一致' : latestSuccess ? '知识已更新,索引尚未完成' : '等待首次知识晋升';
143
+ $('#flowPhase').textContent = health.journal.enabled ? '日志消费已启用' : '自动维护待切换';
144
+ $('#overviewRuns').innerHTML = runs.slice(0, 6).map(run => `<tr class="click" data-open-run="${escapeHtml(run.runId)}"><td class="mono">${shortSha(run.commitSha)}</td><td>${escapeHtml(run.projectId)}</td><td>${stateBadge(run.state)}</td><td class="mono">${run.staging?.operations?.length || 0} 项</td><td class="mono">${fmtDuration(run.durationMs)}</td></tr>`).join('') || '<tr><td colspan="5" class="empty">暂无知识运行。</td></tr>';
145
+ $$('[data-open-run]').forEach(row => row.addEventListener('click', () => { selectedRunId = row.dataset.openRun; setView('runs'); }));
146
+ if (failed.length) {
147
+ $('#attentionTitle').textContent = `${failed.length} 个运行需要处理`;
148
+ $('#attentionBody').textContent = `最近异常 ${shortSha(failed[0].commitSha)}:${failed[0].error?.message || '未提供错误详情'}。`;
149
+ } else if (running.length) {
150
+ $('#attentionTitle').textContent = '知识正在生成';
151
+ $('#attentionBody').textContent = `${running.length} 个 commit 已被认领。打开流水线可查看当前阶段和暂存产物。`;
152
+ } else if (latestSuccess) {
153
+ $('#attentionTitle').textContent = '链路目前稳定';
154
+ $('#attentionBody').textContent = `最近已为 ${shortSha(latestSuccess.commitSha)} 晋升 ${latestSuccess.staging?.operations?.length || 0} 项知识变更。`;
155
+ } else {
156
+ $('#attentionTitle').textContent = '等待第一份知识证据';
157
+ $('#attentionBody').textContent = '引擎已就绪;提交 commit boundary 后,对账器会创建首个分析运行。';
158
+ }
159
+ $('#serviceInfo').innerHTML = `<div><dt>服务版本</dt><dd class="mono">${health.version}</dd></div><div><dt>服务 uptime</dt><dd class="mono">${Math.floor(health.uptimeSec / 60)}m ${health.uptimeSec % 60}s</dd></div><div><dt>事件消费者</dt><dd>${escapeHtml(health.journal.consumer)}</dd></div><div><dt>状态目录</dt><dd class="mono">${escapeHtml(health.dataDir)}</dd></div>`;
160
+ }
161
+
162
+ async function loadProjectSelects() {
163
+ const { projects } = await api('/api/projects');
164
+ const options = projects.map(project => `<option value="${escapeHtml(project.projectId)}">${escapeHtml(project.name)}</option>`).join('');
165
+ for (const select of [$('#triggerProject'), $('#retrievalProject'), $('#runProjectFilter')]) {
166
+ const previous = select.value;
167
+ const isFilter = select === $('#runProjectFilter');
168
+ select.innerHTML = (isFilter ? '<option value="">全部项目</option>' : '') + options;
169
+ const preferred = previous || (!isFilter ? scopedProjectId : '');
170
+ if ([...select.options].some(option => option.value === preferred)) select.value = preferred;
171
+ }
172
+ }
173
+
174
+ async function loadRuns() {
175
+ const epoch = viewEpoch;
176
+ const selectedProject = $('#runProjectFilter').value;
177
+ const selectedState = $('#runStateFilter').value;
178
+ const { runs } = await api(selectedProject ? `/api/runs?projectId=${encodeURIComponent(selectedProject)}` : scopedPath('/api/runs'));
179
+ if (epoch !== viewEpoch) return;
180
+ const visibleRuns = selectedState ? runs.filter(run => run.state === selectedState) : runs;
181
+ $('#runsBody').innerHTML = visibleRuns.map(run => `<tr class="click ${run.runId === selectedRunId ? 'sel' : ''}" data-run="${escapeHtml(run.runId)}"><td class="mono">${shortSha(run.commitSha)}</td><td>${escapeHtml(run.projectId)}</td><td class="mono">${escapeHtml(run.trigger)}</td><td>${stateBadge(run.state)}</td><td class="mono">${fmtTime(run.startedAt)}</td><td class="mono">${fmtDuration(run.durationMs)}</td></tr>`).join('') || '<tr><td colspan="6" class="empty">没有符合条件的运行。</td></tr>';
182
+ $$('[data-run]').forEach(row => row.addEventListener('click', () => { selectedRunId = row.dataset.run; showRunDetail(selectedRunId); loadRuns(); }));
183
+ if (selectedRunId) showRunDetail(selectedRunId).catch(() => {});
184
+ clearTimeout(runPollTimer);
185
+ if (embedVisible && runs.some(run => run.state === 'queued' || run.state === 'running') && currentView === 'runs') runPollTimer = setTimeout(loadRuns, 800);
186
+ }
187
+
188
+ function runTimeline(run) {
189
+ const stages = ['排队', '冻结证据', '分析会话', '校验晋升', '更新索引', '可检索'];
190
+ const activeIndex = ({ queued: 0, recovering: 1, preparing: 1, analyzing: 2, promoting: 3, promoted: 4, indexing: 4, 'index-pending': 4, ready: 6 })[run.phase] ?? 2;
191
+ return stages.map((stage, index) => `<li class="${['failed', 'index-pending'].includes(run.state) && index === activeIndex ? 'failed' : index < activeIndex ? 'done' : index === activeIndex ? 'current' : ''}">${stage}</li>`).join('');
192
+ }
193
+
194
+ async function showRunDetail(runId) {
195
+ const { run } = await api(`/api/runs/${encodeURIComponent(runId)}`);
196
+ if (runId !== selectedRunId || (scopedProjectId && run.projectId !== scopedProjectId)) return;
197
+ $('#runDetail').classList.remove('hidden');
198
+ $('#runDetailId').textContent = run.runId;
199
+ $('#runTimeline').innerHTML = runTimeline(run);
200
+ $('#runMeta').innerHTML = `<div><dt>状态</dt><dd>${stateBadge(run.state)}</dd></div><div><dt>项目 / commit</dt><dd>${escapeHtml(run.projectId)} · <span class="mono">${shortSha(run.commitSha)}</span></dd></div><div><dt>触发入口</dt><dd class="mono">${escapeHtml(run.trigger)}</dd></div><div><dt>错误</dt><dd>${run.error ? escapeHtml(run.error.message) : '–'}</dd></div>${run.error?.retryable ? '<div><dt>恢复</dt><dd><button class="btn" id="retryRun">重试失败阶段</button></dd></div>' : ''}`;
201
+ $('#retryRun')?.addEventListener('click', async () => { await api(`/api/runs/${encodeURIComponent(runId)}/retry`, { method: 'POST' }); loadRuns(); });
202
+ $('#runStaging').textContent = run.staging ? JSON.stringify(run.staging, null, 2) : '该运行没有 staging 产物。';
203
+ }
204
+
205
+ $('#runProjectFilter').addEventListener('change', loadRuns);
206
+ $('#runStateFilter').addEventListener('change', loadRuns);
207
+ $('#btnShowTrigger').addEventListener('click', async () => { $('#triggerForm').classList.toggle('hidden'); if (!$('#triggerForm').classList.contains('hidden')) await loadProjectSelects(); });
208
+ $('#btnCancelTrigger').addEventListener('click', () => $('#triggerForm').classList.add('hidden'));
209
+ $('#triggerForm').addEventListener('submit', async event => {
210
+ event.preventDefault();
211
+ const data = Object.fromEntries(new FormData(event.target));
212
+ data.simulateFailure = event.target.simulateFailure.checked;
213
+ await api('/api/runs', { method: 'POST', body: data });
214
+ event.target.reset(); $('#triggerForm').classList.add('hidden'); toast('已加入真实维护队列。'); loadRuns();
215
+ });
216
+
217
+ async function loadKnowledge() {
218
+ const epoch = viewEpoch;
219
+ const [projects, payload] = await Promise.all([api('/api/projects'), api(scopedPath('/api/runs'))]);
220
+ if (epoch !== viewEpoch) return;
221
+ const project = scopedProjectId ? projects.projects.find(item => item.projectId === scopedProjectId) : projects.projects[0];
222
+ const runs = scopedProjectId ? payload.runs : payload.runs.filter(run => !project || run.projectId === project.projectId);
223
+ const latest = successfulRun(runs);
224
+ $('#knowledgeProjectName').textContent = project ? project.name : '尚未登记项目';
225
+ $('#knowledgePath').textContent = project?.knowledgePath || '等待 project-knowledge-base 外壳投递 knowledgePath';
226
+ $('#knowledgeLastCommit').textContent = latest ? shortSha(latest.commitSha) : '–';
227
+ $('#knowledgeLastAt').textContent = latest ? `${fmtTime(latest.promotedAt)} · ${latest.staging?.operations?.length || 0} 项已晋升` : '尚无晋升记录';
228
+ $('#knowledgeIndexState').textContent = latest?.indexState === 'succeeded' ? '可检索' : latest ? '待同步' : '等待运行';
229
+ const operations = latest?.staging?.operations || [];
230
+ $('#knowledgeOperationCount').textContent = `${operations.length} 项`;
231
+ $('#knowledgeOperations').innerHTML = operations.map(operation => `<article class="knowledge-operation"><span class="operation-kind">${escapeHtml(operation.operation)}</span><div><strong>${escapeHtml(operation.path)}</strong><small>${escapeHtml(operation.reason)}</small></div><em>${escapeHtml(operation.evidenceReferences?.[0] || '无证据')}</em></article>`).join('') || '<p class="empty">成功运行后,暂存清单会在这里展示知识树将受到的影响。</p>';
232
+ }
233
+
234
+ async function loadProjects() {
235
+ const { projects } = await api('/api/projects');
236
+ $('#projectsBody').innerHTML = projects.map(project => `<tr><td><strong>${escapeHtml(project.name)}</strong><br><span class="mono">${escapeHtml(project.projectId)}</span></td><td class="mono wrap">${escapeHtml(project.repoPath || '–')}</td><td class="mono wrap">${escapeHtml(project.knowledgePath || '–')}</td><td class="mono">${shortSha(project.lastCommitSha)}</td><td><button class="btn danger" data-del="${escapeHtml(project.projectId)}">摘除接线</button></td></tr>`).join('') || '<tr><td colspan="5" class="empty">尚无来自外壳的项目接线。</td></tr>';
237
+ $$('[data-del]').forEach(button => button.addEventListener('click', async () => {
238
+ if (!confirm(`摘除项目 ${button.dataset.del} 的引擎接线?知识树与运行历史会保留。`)) return;
239
+ await api(`/api/projects/${encodeURIComponent(button.dataset.del)}`, { method: 'DELETE' });
240
+ if (scopedProjectId === button.dataset.del) scopedProjectId = '';
241
+ loadProjects();
242
+ }));
243
+ }
244
+
245
+ $('#btnShowRegister').addEventListener('click', () => $('#registerForm').classList.toggle('hidden'));
246
+ $('#btnCancelRegister').addEventListener('click', () => $('#registerForm').classList.add('hidden'));
247
+ $('#registerForm').addEventListener('submit', async event => {
248
+ event.preventDefault();
249
+ await api('/api/projects', { method: 'POST', body: Object.fromEntries(new FormData(event.target)) });
250
+ event.target.reset(); $('#registerForm').classList.add('hidden'); loadProjects();
251
+ });
252
+
253
+ $('#btnSearch').addEventListener('click', async () => {
254
+ const projectId = $('#retrievalProject').value;
255
+ const query = $('#retrievalQuery').value.trim();
256
+ if (!projectId || !query) return toast('先选择项目并输入查询。');
257
+ const { results } = await api('/api/search', { method: 'POST', body: { projectId, query, limit: Number($('#retrievalLimit').value) || 5 } });
258
+ $('#searchBody').innerHTML = results.map(hit => `<tr><td class="mono">${escapeHtml(hit.path)}</td><td>${escapeHtml(hit.title)}</td><td class="score">${hit.score}</td><td class="wrap">${escapeHtml(hit.snippet)}</td></tr>`).join('') || '<tr><td colspan="4" class="empty">没有命中的知识文档。</td></tr>';
259
+ });
260
+ $('#btnAsk').addEventListener('click', async () => {
261
+ const projectId = $('#retrievalProject').value;
262
+ const query = $('#retrievalQuery').value.trim();
263
+ if (!projectId || !query) return toast('先选择项目并输入查询。');
264
+ const result = await api('/api/ask', { method: 'POST', body: { projectId, query } });
265
+ $('#askPanel').classList.remove('hidden');
266
+ $('#askAnswer').textContent = result.answer;
267
+ $('#askCitations').innerHTML = result.citations.map(citation => `<li><span class="mono">${escapeHtml(citation.path)}</span> · ${escapeHtml(citation.title)} · <span class="score">${citation.score}</span></li>`).join('') || '<li>无引用</li>';
268
+ });
269
+
270
+ async function loadEvents() {
271
+ const { events } = await api('/api/events?limit=100');
272
+ const visible = events.filter(event => !scopedProjectId || event.projectId === scopedProjectId);
273
+ $('#eventCursor').textContent = `游标 ${events[0]?.seq || 0}`;
274
+ $('#eventsBody').innerHTML = visible.map(event => `<tr><td class="mono">${event.seq}</td><td>${typeBadge(event.type)}</td><td>${escapeHtml(event.projectId || '–')}</td><td class="wrap">${escapeHtml(event.summary)}</td><td class="mono">${fmtTime(event.at)}</td></tr>`).join('') || '<tr><td colspan="5" class="empty">暂无事件。</td></tr>';
275
+ }
276
+ $('#btnInjectEvent').addEventListener('click', async () => { await api('/api/events', { method: 'POST', body: { type: 'commit-boundary', projectId: scopedProjectId || null, summary: '手动注入的测试事件' } }); loadEvents(); });
277
+
278
+ window.addEventListener('message', async event => {
279
+ if (!embedded || event.source !== parent || event.origin !== parentOrigin || !event.data) return;
280
+ const message = event.data;
281
+ if (message.type === 'kb:theme') document.body.dataset.kbTheme = message.theme === 'dark' ? 'dark' : 'light';
282
+ if (message.type === 'kb:visibility') {
283
+ embedVisible = message.visible === true;
284
+ clearTimeout(runPollTimer);
285
+ if (embedVisible) refreshView(currentView);
286
+ }
287
+ if (message.type === 'kb:select-project') {
288
+ scopedProjectId = message.projectId || ''; selectedRunId = null; viewEpoch++;
289
+ await loadProjectScope(); await refreshView(currentView);
290
+ parent.postMessage({ type: 'kb:project-selected', projectId: scopedProjectId, requestId: message.requestId }, parentOrigin);
291
+ }
292
+ if (message.type === 'kb:refresh') refreshView(currentView);
293
+ });
294
+ if (embedded) { document.body.dataset.kbTheme = 'light'; parent.postMessage({ type: 'kb:ready', module: 'engine' }, parentOrigin); }
295
+ pollHealth();
296
+ loadOverview();
297
+ setInterval(() => { if (embedVisible && currentView === 'overview') loadOverview(); }, 5000);
@@ -0,0 +1,95 @@
1
+ <!DOCTYPE html>
2
+ <html lang="zh-CN">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>knowledge-engine · 知识生命周期控制台</title>
7
+ <link rel="stylesheet" href="./app.css">
8
+ </head>
9
+ <body>
10
+ <div id="app">
11
+ <aside>
12
+ <div class="brand">
13
+ <div class="mark">KE</div>
14
+ <div class="brand-copy"><strong>knowledge-engine</strong><span>知识生命周期控制台</span></div>
15
+ </div>
16
+ <nav class="nav" aria-label="知识引擎导航">
17
+ <p class="nav-label">运行</p>
18
+ <button class="nav-item on" data-view="overview"><span class="nav-symbol">◎</span>知识脉搏</button>
19
+ <button class="nav-item" data-view="runs"><span class="nav-symbol">↳</span>生成流水线</button>
20
+ <button class="nav-item" data-view="knowledge"><span class="nav-symbol">□</span>知识状态</button>
21
+ <button class="nav-item" data-view="retrieval"><span class="nav-symbol">⌕</span>检索验证</button>
22
+ <p class="nav-label nav-label-lower">调试</p>
23
+ <button class="nav-item" data-view="projects"><span class="nav-symbol">↔</span>项目接线</button>
24
+ <button class="nav-item" data-view="events"><span class="nav-symbol">≋</span>事件流</button>
25
+ </nav>
26
+ <div class="side-foot"><span class="dot" id="healthDot"></span><span id="healthText">连接中…</span></div>
27
+ </aside>
28
+
29
+ <main>
30
+ <header class="topbar">
31
+ <div><p class="eyebrow" id="viewKicker">knowledge control plane</p><h1 id="viewTitle">知识脉搏</h1></div>
32
+ <label class="scope-control">范围<select id="scopeProject"><option value="">全部项目</option></select></label>
33
+ </header>
34
+
35
+ <section id="view-overview" class="view on">
36
+ <div class="view-intro"><p>从 commit 证据到可引用知识:这里看见每一段链路是否连续,而不是只看一串运行日志。</p><button class="btn primary" data-goto="runs">查看生成流水线</button></div>
37
+ <section class="flow-panel" aria-labelledby="flowTitle">
38
+ <div class="panel-heading"><div><p class="section-kicker">当前知识流</p><h2 id="flowTitle">证据正在如何成为知识</h2></div><span class="phase-pill" id="flowPhase">正在读取运行状态</span></div>
39
+ <ol class="flow-track">
40
+ <li class="flow-step" id="flowJournal"><span class="flow-number">01</span><div><strong>事件入站</strong><small id="flowJournalText">journal 等待连接</small></div></li>
41
+ <li class="flow-step" id="flowReconcile"><span class="flow-number">02</span><div><strong>提交对账</strong><small id="flowReconcileText">等待 commit boundary</small></div></li>
42
+ <li class="flow-step" id="flowAnalyze"><span class="flow-number">03</span><div><strong>证据分析</strong><small id="flowAnalyzeText">没有运行中的分析</small></div></li>
43
+ <li class="flow-step" id="flowPromote"><span class="flow-number">04</span><div><strong>校验与晋升</strong><small id="flowPromoteText">尚未生成暂存产物</small></div></li>
44
+ <li class="flow-step is-pending" id="flowIndex"><span class="flow-number">05</span><div><strong>检索就绪</strong><small id="flowIndexText">等待接入 vector-hub</small></div></li>
45
+ </ol>
46
+ </section>
47
+ <div class="metric-grid">
48
+ <article class="metric"><span>本范围项目</span><strong id="statProjects">–</strong><small id="statProjectsDetail">等待服务响应</small></article>
49
+ <article class="metric"><span>进行中</span><strong id="statRunning">–</strong><small id="statRunningDetail">队列与分析会话</small></article>
50
+ <article class="metric"><span>已晋升</span><strong id="statSucceeded">–</strong><small id="statSucceededDetail">成功完成的知识运行</small></article>
51
+ <article class="metric metric-warn"><span>需要处理</span><strong id="statFailed">–</strong><small id="statFailedDetail">失败或中断的运行</small></article>
52
+ </div>
53
+ <div class="overview-grid">
54
+ <section class="panel run-panel"><div class="panel-heading"><div><p class="section-kicker">运行记录</p><h2>最近知识变更</h2></div><button class="quiet-link" data-goto="runs">全部运行 →</button></div><div class="table-wrap"><table><thead><tr><th>commit</th><th>项目</th><th>状态</th><th>产物</th><th>耗时</th></tr></thead><tbody id="overviewRuns"></tbody></table></div></section>
55
+ <section class="panel attention-panel"><div class="panel-heading"><div><p class="section-kicker">可操作项</p><h2 id="attentionTitle">正在检查链路</h2></div></div><p class="attention-body" id="attentionBody">正在读取运行、事件和服务健康状态。</p><dl class="service-list" id="serviceInfo"></dl></section>
56
+ </div>
57
+ </section>
58
+
59
+ <section id="view-runs" class="view">
60
+ <div class="view-intro compact"><p>查看提交证据、分析进度、知识晋升与索引结果。提交由事件入口自动进入维护队列。</p><button class="btn debug-only" id="btnShowTrigger">手动维护</button></div>
61
+ <div class="toolbar filters"><select id="runProjectFilter"><option value="">全部项目</option></select><select id="runStateFilter"><option value="">全部状态</option><option value="queued">排队</option><option value="running">运行中</option><option value="succeeded">可检索</option><option value="index-pending">索引待修复</option><option value="failed">失败</option></select></div>
62
+ <form id="triggerForm" class="panel form hidden"><div class="panel-heading"><div><p class="section-kicker">维护诊断</p><h2>分析一次真实提交</h2></div></div><div class="form-row"><label>项目<select name="projectId" id="triggerProject" required></select></label><label>commit SHA<input name="commitSha" placeholder="输入仓库中的真实提交 SHA" pattern="[a-fA-F0-9]{7,64}" required></label></div><label class="checkbox debug-only"><input type="checkbox" name="simulateFailure"> 模拟失败态(调试模式)</label><div class="form-actions"><button type="submit" class="btn primary">开始维护</button><button type="button" class="btn" id="btnCancelTrigger">取消</button></div></form>
63
+ <section class="panel"><div class="table-wrap"><table><thead><tr><th>commit</th><th>项目</th><th>入口</th><th>状态</th><th>开始</th><th>耗时</th></tr></thead><tbody id="runsBody"></tbody></table></div></section>
64
+ <section class="panel hidden" id="runDetail"><div class="panel-heading"><div><p class="section-kicker">运行追溯</p><h2>运行 <span class="mono" id="runDetailId"></span></h2></div></div><ol class="run-timeline" id="runTimeline" aria-label="运行状态时间线"></ol><div class="detail-grid"><dl class="kv" id="runMeta"></dl><div><p class="panel-sub">staging manifest</p><pre class="code" id="runStaging">该运行没有 staging 产物。</pre></div></div></section>
65
+ </section>
66
+
67
+ <section id="view-knowledge" class="view">
68
+ <div class="view-intro compact"><p>知识树是唯一事实源;引擎只记录其生成证据、晋升结果与派生索引的同步状态。</p><button class="btn" data-goto="retrieval">验证检索</button></div>
69
+ <div class="knowledge-hero panel"><div><p class="section-kicker">知识树</p><h2 id="knowledgeProjectName">选择一个项目</h2><p class="mono knowledge-path" id="knowledgePath">–</p></div><div class="knowledge-summary"><span>最近晋升</span><strong id="knowledgeLastCommit">–</strong><small id="knowledgeLastAt">尚无成功运行</small></div></div>
70
+ <div class="overview-grid knowledge-grid"><section class="panel"><div class="panel-heading"><div><p class="section-kicker">本次知识影响</p><h2>已晋升变更清单</h2></div><span class="phase-pill" id="knowledgeOperationCount">0 项</span></div><div class="knowledge-list" id="knowledgeOperations"></div></section><section class="panel"><div class="panel-heading"><div><p class="section-kicker">可用性</p><h2>派生能力状态</h2></div></div><div class="readiness-list"><div><span class="readiness-dot is-ok"></span><p><strong>规范知识树</strong><small>由晋升路径单写;检索只读。</small></p><b>事实源</b></div><div><span class="readiness-dot is-ok"></span><p><strong>文件检索</strong><small>读取现存知识文件;向量离线时降级,不返回过期文件。</small></p><b>已接入</b></div><div><span class="readiness-dot is-pending"></span><p><strong>vector-hub 索引</strong><small>从知识树派生,可重建,不反向写入。</small></p><b id="knowledgeIndexState">等待运行</b></div></div></section></div>
71
+ </section>
72
+
73
+ <section id="view-retrieval" class="view">
74
+ <div class="view-intro compact"><p>验证开发 agent 实际获得的上下文:命中来源、得分、摘要和可追溯引用应在同一处可见。</p></div>
75
+ <div class="panel form retrieval-form"><div class="form-row"><label>项目<select id="retrievalProject"></select></label><label class="grow">查询<input id="retrievalQuery" placeholder="例如:commit 之后分析是怎么触发的"></label><label>limit<input id="retrievalLimit" type="number" value="5" min="1" max="30" class="w70"></label></div><div class="form-actions"><button class="btn primary" id="btnSearch">search</button><button class="btn" id="btnAsk">ask</button></div></div>
76
+ <section class="panel hidden" id="askPanel"><div class="panel-heading"><div><p class="section-kicker">带引用回答</p><h2>ask 结果</h2></div><span class="phase-pill">确定性拼接</span></div><div class="answer" id="askAnswer"></div><p class="panel-sub">引用</p><ol class="citations" id="askCitations"></ol></section>
77
+ <section class="panel"><div class="panel-heading"><div><p class="section-kicker">检索证据</p><h2>search 命中</h2></div></div><div class="table-wrap"><table><thead><tr><th>路径</th><th>标题</th><th>得分</th><th>摘要</th></tr></thead><tbody id="searchBody"><tr><td colspan="4" class="empty">输入查询后执行 search。</td></tr></tbody></table></div></section>
78
+ </section>
79
+
80
+ <section id="view-projects" class="view">
81
+ <div class="view-intro compact"><p>项目主档、设置和密钥属于 <span class="mono">project-knowledge-base</span> 外壳。本页只用于核对其向引擎投递的运行接线。</p><button class="btn" id="btnShowRegister">调试注册</button></div>
82
+ <form id="registerForm" class="panel form hidden"><div class="panel-heading"><div><p class="section-kicker">维护诊断</p><h2>登记真实目录绑定</h2></div></div><div class="form-row"><label>项目ID<input name="projectId" placeholder="my-project" required></label><label>名称<input name="name" placeholder="显示名"></label></div><div class="form-row"><label>仓库路径<input name="repoPath" placeholder="D:\path\to\repo" required></label><label>知识库路径<input name="knowledgePath" placeholder="D:\path\to\knowledge" required></label></div><div class="form-actions"><button type="submit" class="btn primary">登记</button><button type="button" class="btn" id="btnCancelRegister">取消</button></div></form>
83
+ <section class="panel"><div class="table-wrap"><table><thead><tr><th>项目</th><th>仓库</th><th>知识树</th><th>最近 commit</th><th>接线</th></tr></thead><tbody id="projectsBody"></tbody></table></div></section>
84
+ </section>
85
+
86
+ <section id="view-events" class="view">
87
+ <div class="view-intro compact"><p>event-bridge 是唯一入口。引擎先持久化证据与任务,再推进各项目的消费游标;封存提交不代表消费者已经读取。</p><button class="btn debug-only" id="btnInjectEvent">注入测试事件</button></div>
88
+ <section class="panel"><div class="panel-heading"><div><p class="section-kicker">原始事实</p><h2>事件流</h2></div><span class="phase-pill" id="eventCursor">游标 –</span></div><div class="table-wrap"><table><thead><tr><th>seq</th><th>类型</th><th>项目</th><th>摘要</th><th>时间</th></tr></thead><tbody id="eventsBody"></tbody></table></div></section>
89
+ </section>
90
+ </main>
91
+ </div>
92
+ <div id="toast" role="status" aria-live="polite"></div>
93
+ <script src="./app.js"></script>
94
+ </body>
95
+ </html>
package/_site/README.md CHANGED
@@ -1,30 +1,30 @@
1
- # KB Management Site
2
-
3
- 本地知识库管理界面,默认监听 `http://127.0.0.1:5757`。
4
-
5
- ## 启动
6
-
7
- ```bash
8
- node _site/server.js
9
- ```
10
-
11
- 也可以运行项目根目录的 `npm start`,或使用 `_site/start.bat`。
12
-
13
- ## Commit 自动化
14
-
15
- - Git `post-commit` Hook 在应用运行时立即通知服务端。
16
- - 应用启动时会扫描关闭期间新增的 commit。
17
- - 每个 commit 生成一个独立 Claude Code 后台任务。
18
- - 同一项目按提交时间串行执行,不同项目可以并行。
19
- - 任务直接更新当前项目知识库,没有草稿审核、`autoApply` 或人工批准阶段。
20
- - 每个 commit 的 `discovered / queued / running / completed / failed` 状态持久化在项目 AI 工作目录中,完成的 commit 不会重复派发。
21
-
22
- ## 主要接口
23
-
24
- - `GET /api/state`:项目与知识库状态。
25
- - `POST /api/hooks/post-commit`:接收 Hook 事件并执行 commit 对账。
26
- - `POST /api/projects/:slug/scan`:只读扫描 Git 状态。
27
- - `GET /api/projects/:slug/automation/runs`:查看自动化运行记录。
28
- - `GET /api/claude/sessions-stream`:订阅 Claude Code 会话状态。
29
-
30
- 服务只绑定回环地址。不要将端口直接暴露到公网。
1
+ # KB Management Site
2
+
3
+ 本地知识库管理界面,默认监听 `http://127.0.0.1:5757`。
4
+
5
+ ## 启动
6
+
7
+ ```bash
8
+ node _site/server.js
9
+ ```
10
+
11
+ 也可以运行项目根目录的 `npm start`,或使用 `_site/start.bat`。
12
+
13
+ ## Commit 自动化
14
+
15
+ - Git `post-commit` Hook 在应用运行时立即通知服务端。
16
+ - 应用启动时会扫描关闭期间新增的 commit。
17
+ - 每个 commit 生成一个独立 Claude Code 后台任务。
18
+ - 同一项目按提交时间串行执行,不同项目可以并行。
19
+ - 任务直接更新当前项目知识库,没有草稿审核、`autoApply` 或人工批准阶段。
20
+ - 每个 commit 的 `discovered / queued / running / completed / failed` 状态持久化在项目 AI 工作目录中,完成的 commit 不会重复派发。
21
+
22
+ ## 主要接口
23
+
24
+ - `GET /api/state`:项目与知识库状态。
25
+ - `POST /api/hooks/post-commit`:接收 Hook 事件并执行 commit 对账。
26
+ - `POST /api/projects/:slug/scan`:只读扫描 Git 状态。
27
+ - `GET /api/projects/:slug/automation/runs`:查看自动化运行记录。
28
+ - `GET /api/claude/sessions-stream`:订阅 Claude Code 会话状态。
29
+
30
+ 服务只绑定回环地址。不要将端口直接暴露到公网。