project-knowledge 0.1.0 → 1.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 (34) hide show
  1. package/CHANGELOG.md +41 -0
  2. package/README.md +184 -58
  3. package/_site/_test/ai-profile-test.js +59 -1
  4. package/_site/_test/baseline-schema-test.js +4 -3
  5. package/_site/_test/claude-workbench-test.js +72 -0
  6. package/_site/_test/draft-apply-test.js +12 -6
  7. package/_site/_test/kb-v2-templates-test.js +31 -43
  8. package/_site/_test/knowledge-store-logs-supervision-test.js +143 -0
  9. package/_site/_test/project-control-panel-task14-test.js +151 -0
  10. package/_site/_test/task15-20-integration-test.js +194 -0
  11. package/_site/_test/task15-20-ui-flow-test.js +144 -0
  12. package/_site/_test/ui-smoke-test.js +2 -2
  13. package/_site/index.html +1640 -90
  14. package/_site/lib/ai-adapter.js +3 -3
  15. package/_site/lib/ai-workspace.js +120 -0
  16. package/_site/lib/analysis-orchestrator.js +117 -32
  17. package/_site/lib/claude-cli-runner.js +862 -0
  18. package/_site/lib/context-pack-builder.js +19 -11
  19. package/_site/lib/draft-apply.js +80 -31
  20. package/_site/lib/index-builder.js +100 -0
  21. package/_site/lib/job-orchestrator.js +12 -9
  22. package/_site/lib/kb-v3.js +188 -0
  23. package/_site/lib/kb-validator.js +84 -0
  24. package/_site/lib/knowledge-store.js +141 -0
  25. package/_site/lib/llm-client.js +103 -56
  26. package/_site/lib/prompt-registry.js +102 -0
  27. package/_site/lib/structured-logger.js +120 -0
  28. package/_site/lib/supervision.js +103 -0
  29. package/_site/server.js +835 -19
  30. package/_site/vendor/tailwind-browser.js +947 -0
  31. package/_site/vendor/vue.global.prod.js +9 -0
  32. package/ai-profiles.json +12 -10
  33. package/docs/development-progress.md +141 -0
  34. package/package.json +7 -2
@@ -0,0 +1,144 @@
1
+ // UI flow coverage for TASK-015..020.
2
+ // Uses a real Chromium via CDP and writes screenshots to _site/_test/ui-screenshots.
3
+
4
+ const { spawn } = require('child_process');
5
+ const fs = require('fs');
6
+ const path = require('path');
7
+ const http = require('http');
8
+ const WebSocket = require('ws');
9
+
10
+ const CHROME = 'C:\\Users\\SanQian\\AppData\\Local\\ms-playwright\\chromium-1223\\chrome-win64\\chrome.exe';
11
+ const TARGET_URL = process.argv[2] || 'http://127.0.0.1:7777/';
12
+ const OUT_DIR = path.join(__dirname, 'ui-screenshots');
13
+ const PROFILE = path.join(OUT_DIR, 'task15-20-profile');
14
+ const PORT = 9341;
15
+
16
+ fs.mkdirSync(OUT_DIR, { recursive: true });
17
+ fs.rmSync(PROFILE, { recursive: true, force: true });
18
+ fs.mkdirSync(PROFILE, { recursive: true });
19
+
20
+ function assert(cond, msg) { if (!cond) throw new Error('ASSERT: ' + msg); }
21
+ function fetchJson(url) {
22
+ return new Promise((resolve, reject) => {
23
+ http.get(url, res => {
24
+ let body = '';
25
+ res.on('data', chunk => body += chunk);
26
+ res.on('end', () => {
27
+ try { resolve(JSON.parse(body)); } catch (e) { reject(e); }
28
+ });
29
+ }).on('error', reject);
30
+ });
31
+ }
32
+ async function waitFor(fn, label, ms = 15000) {
33
+ const deadline = Date.now() + ms;
34
+ let last;
35
+ while (Date.now() < deadline) {
36
+ try {
37
+ const got = await fn();
38
+ if (got) return got;
39
+ } catch (e) { last = e; }
40
+ await new Promise(resolve => setTimeout(resolve, 250));
41
+ }
42
+ throw new Error(`timeout: ${label}${last ? ` :: ${last.message}` : ''}`);
43
+ }
44
+
45
+ (async () => {
46
+ const child = spawn(CHROME, [
47
+ '--headless=new',
48
+ '--disable-gpu',
49
+ '--no-sandbox',
50
+ '--disable-dev-shm-usage',
51
+ `--remote-debugging-port=${PORT}`,
52
+ `--user-data-dir=${PROFILE}`,
53
+ '--window-size=1440,980',
54
+ 'about:blank',
55
+ ], { stdio: ['ignore', 'pipe', 'pipe'], windowsHide: true });
56
+
57
+ const errors = [];
58
+ try {
59
+ const pages = await waitFor(() => fetchJson(`http://127.0.0.1:${PORT}/json/list`), 'chrome page list');
60
+ const page = pages.find(p => p.type === 'page') || pages[0];
61
+ const ws = new WebSocket(page.webSocketDebuggerUrl);
62
+ await new Promise((resolve, reject) => { ws.on('open', resolve); ws.on('error', reject); });
63
+
64
+ let nextId = 1;
65
+ const pending = new Map();
66
+ ws.on('message', m => {
67
+ const msg = JSON.parse(m.toString());
68
+ if (msg.id && pending.has(msg.id)) {
69
+ const { resolve, reject } = pending.get(msg.id);
70
+ pending.delete(msg.id);
71
+ if (msg.error) reject(new Error(msg.error.message));
72
+ else resolve(msg.result);
73
+ } else if (msg.method === 'Runtime.consoleAPICalled' && msg.params.type === 'error') {
74
+ errors.push((msg.params.args || []).map(arg => arg.value || arg.description || '').join(' '));
75
+ }
76
+ });
77
+ function send(method, params = {}) {
78
+ const id = nextId++;
79
+ return new Promise((resolve, reject) => {
80
+ pending.set(id, { resolve, reject });
81
+ ws.send(JSON.stringify({ id, method, params }));
82
+ });
83
+ }
84
+ async function evalJs(expression) {
85
+ const r = await send('Runtime.evaluate', { expression, returnByValue: true, awaitPromise: true });
86
+ return r.result.value;
87
+ }
88
+ async function screenshot(name) {
89
+ const r = await send('Page.captureScreenshot', { format: 'png', captureBeyondViewport: true });
90
+ const file = path.join(OUT_DIR, name);
91
+ fs.writeFileSync(file, Buffer.from(r.data, 'base64'));
92
+ return file;
93
+ }
94
+
95
+ await send('Runtime.enable');
96
+ await send('Page.enable');
97
+ await send('Network.enable');
98
+ await send('Page.navigate', { url: TARGET_URL });
99
+ await waitFor(() => evalJs('document.readyState === "complete"'), 'document ready');
100
+ await evalJs('localStorage.setItem("kb-ui-language", "en"); location.reload()');
101
+ await waitFor(() => evalJs('document.querySelector("#app") && document.querySelector("#app").innerText.includes("Project Supervision")'), 'app english dashboard');
102
+ await new Promise(resolve => setTimeout(resolve, 800));
103
+ const dashboardShot = await screenshot('task15-20-dashboard.png');
104
+
105
+ await evalJs(`[...document.querySelectorAll('button')].find(b => b.innerText.includes('Pending'))?.click()`);
106
+ await waitFor(() => evalJs('document.body.innerText.includes("Pending commit details")'), 'pending panel open');
107
+ const pendingItems = await evalJs('document.querySelectorAll("section details").length');
108
+ if (pendingItems > 0) assert(pendingItems > 0, 'pending panel should render collapsible details when it has items');
109
+ assert(!(await evalJs('document.body.innerText.includes("Project Operations")')), 'project detail should be hidden while summary panel is open');
110
+ const pendingShot = await screenshot('task15-20-pending-panel.png');
111
+
112
+ await evalJs(`[...document.querySelectorAll('button')].find(b => b.innerText.includes('Pending'))?.click()`);
113
+ await evalJs(`[...document.querySelectorAll('button')].find(b => b.innerText.includes('Recent issues') || b.innerText.includes('Issues'))?.click()`);
114
+ await waitFor(() => evalJs('document.body.innerText.includes("Issue center")'), 'issues panel open');
115
+ const issueGroups = await evalJs('document.querySelectorAll("section details").length');
116
+ if (await evalJs('!document.body.innerText.includes("No blocking issue detected.")')) {
117
+ assert(issueGroups > 0, 'issues panel should render per-project collapsible details when issues exist');
118
+ }
119
+
120
+ await evalJs(`document.querySelector('aside .min-h-0 button')?.click()`);
121
+ await waitFor(() => evalJs('!document.body.innerText.includes("Pending commit details") && !document.body.innerText.includes("Issue center")'), 'summary panel closes on project switch');
122
+
123
+ await evalJs(`[...document.querySelectorAll('button')].find(b => b.innerText.includes('Remove Project'))?.click()`);
124
+ await waitFor(() => evalJs('document.body.innerText.includes("Also delete KB files")'), 'remove modal open');
125
+ await waitFor(() => evalJs('document.body.innerText.includes("D:\\\\SanQian.Xu\\\\knowledge") && !document.body.innerText.includes("Not found")'), 'remove preview loaded');
126
+ assert(await evalJs('document.body.innerText.includes("KB path")'), 'remove modal should show KB path');
127
+ const removeShot = await screenshot('task15-20-remove-modal.png');
128
+ await evalJs(`[...document.querySelectorAll('button')].find(b => b.innerText.trim() === '×')?.click()`);
129
+
130
+ await evalJs(`[...document.querySelectorAll('button')].find(b => b.innerText.includes('Runs / Drafts'))?.click()`);
131
+ await waitFor(() => evalJs('document.body.innerText.includes("Runs and Drafts")'), 'runs view');
132
+ assert(await evalJs('document.body.innerText.includes("All branches") || document.querySelector("select")'), 'runs view should expose branch filtering controls when a run is selected or controls area exists');
133
+ const runsShot = await screenshot('task15-20-runs-view.png');
134
+
135
+ assert(errors.length === 0, `console errors: ${errors.join('\\n')}`);
136
+ console.log('TASK-015..020 UI flow test passed');
137
+ console.log(`screenshots:\n${dashboardShot}\n${pendingShot}\n${removeShot}\n${runsShot}`);
138
+ } catch (e) {
139
+ console.error('TASK-015..020 UI flow test failed:', e.message);
140
+ process.exitCode = 1;
141
+ } finally {
142
+ child.kill();
143
+ }
144
+ })();
@@ -210,10 +210,10 @@ function assert(cond, msg) {
210
210
  });
211
211
  await new Promise(resolve => setTimeout(resolve, 1000));
212
212
  r = await send('Runtime.evaluate', {
213
- expression: '/Knowledge output language|知识库输出语言/.test(document.body.innerText)',
213
+ expression: '/Knowledge output language|知识库输出语言/.test(document.body.innerText) && /Test model|测试模型/.test(document.body.innerText) && /Base URL|Base 地址/.test(document.body.innerText)',
214
214
  returnByValue: true,
215
215
  });
216
- assert(r.result.value, 'AI language setting did not render');
216
+ assert(r.result.value, 'AI model settings did not render');
217
217
 
218
218
  const shot = await send('Page.captureScreenshot', { format: 'png', fullPage: true });
219
219
  fs.writeFileSync(path.join(OUT_DIR, 'smoke-final.png'), Buffer.from(shot.data, 'base64'));