@sanqianx/project-knowledge 5.0.16-rc.1 → 5.0.16-rc.2

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 (31) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/README.md +5 -0
  3. package/_modules/knowledge-engine/README.md +2 -2
  4. package/_modules/knowledge-engine/package.json +1 -1
  5. package/_modules/knowledge-engine/src/retrieval.js +119 -24
  6. package/_modules/knowledge-engine/src/runtime/knowledge-promotion.js +80 -24
  7. package/_modules/knowledge-engine/src/runtime/knowledge-retrieval-service.js +8 -6
  8. package/_modules/knowledge-engine/src/runtime/markdown-knowledge-indexer.js +105 -64
  9. package/_site/_test/agent-capture-mcp-ui-test.js +67 -0
  10. package/_site/_test/agent-capture-ui-test.js +2 -1
  11. package/_site/_test/integration-adapters-test.js +7 -1
  12. package/_site/_test/integration-runtime-binding-a2-test.js +566 -0
  13. package/_site/_test/integration-runtime-binding-a3-test.js +227 -0
  14. package/_site/_test/integration-runtime-binding-test.js +473 -0
  15. package/_site/_test/native-context-integrations-test.js +1 -1
  16. package/_site/_test/release-version-sync-test.js +12 -1
  17. package/_site/lib/integration-installer.js +325 -8
  18. package/_site/lib/module-bridge.js +6 -2
  19. package/_site/scripts/sync-product-content.js +1 -1
  20. package/_site/scripts/sync-release-version.js +9 -5
  21. package/_site/scripts/verify-product-runtime.js +1 -1
  22. package/docs/leader-glm-workbench-playbook.md +87 -0
  23. package/docs/release-5.0.16-acceptance.md +53 -0
  24. package/module-runtime-manifest.json +29 -28
  25. package/package.json +5 -3
  26. package/plugins/project-knowledge/.claude-plugin/plugin.json +1 -1
  27. package/plugins/project-knowledge/.codex-plugin/plugin.json +1 -1
  28. package/plugins/project-knowledge/.mcp.json +7 -6
  29. package/plugins/project-knowledge/scripts/mcp-launcher.cjs +78 -0
  30. package/ui/product.js +3 -1
  31. package/vendor-manifest.json +8 -8
@@ -0,0 +1,67 @@
1
+ 'use strict';
2
+ const assert = require('node:assert/strict');
3
+ const fs = require('node:fs');
4
+ const os = require('node:os');
5
+ const path = require('node:path');
6
+ const http = require('node:http');
7
+ const { createRequire } = require('node:module');
8
+ const coreRoot = path.resolve(process.env.KB_CAPTURE_TEST_CORE_ROOT || path.join(__dirname, '../..'));
9
+ const fromCore = createRequire(path.join(coreRoot, 'package.json'));
10
+ const { createRuntime, initializeRuntime, createRequestHandler } = fromCore('./_site/lib/server-app');
11
+ const { BridgeAdapter } = fromCore('./_site/lib/bridge-adapter');
12
+ const { ModuleBridge } = fromCore('./_site/lib/module-bridge');
13
+ const { findChrome } = require('./helpers/find-chrome');
14
+ const { launchCdpBrowser, waitFor } = require('./helpers/cdp-browser');
15
+
16
+ const HOSTILE_DETAIL = '<img src=x onerror="window.__kbXssProbe=1"> INJECT-STATUS-MARKER sk-abcdef123456';
17
+
18
+ function clientFixture(client, label) {
19
+ const mcpCapable = client === 'claude' || client === 'codex';
20
+ return {
21
+ client, label, available: false, configFound: false,
22
+ knowledgeIntegration: { ok: true, state: 'installed' },
23
+ automaticContext: { supported: client !== 'opencode', configured: false, state: 'check-failed', detail: HOSTILE_DETAIL },
24
+ developmentCapture: { ok: true, state: 'installed', installed: true, detail: HOSTILE_DETAIL },
25
+ mcpStartup: mcpCapable ? { state: 'failed', checkedAt: new Date().toISOString(), scope: 'client:' + client, code: 'PROBE_TIMEOUT', expectedVersion: '9.9.9-test', detail: HOSTILE_DETAIL } : undefined,
26
+ };
27
+ }
28
+
29
+ async function main() {
30
+ const temp = fs.mkdtempSync(path.join(os.tmpdir(), 'kb-agent-mcp-ui-'));
31
+ const dataDir = path.join(temp, 'data');
32
+ const homeDir = path.join(temp, 'home');
33
+ const eventsHome = path.join(dataDir, 'events');
34
+ const offline = http.createServer((req, res) => { res.writeHead(503, { 'content-type': 'application/json' }); res.end(JSON.stringify({ ok: false, error: { message: 'offline stub' } })); });
35
+ await new Promise(resolve => offline.listen(0, '127.0.0.1', resolve));
36
+ const offlineUrl = 'http://127.0.0.1:' + offline.address().port;
37
+ const runtime = createRuntime({ dataDir, analysisOwner: 'engine', bridgeAdapter: new BridgeAdapter({ dataDir, bridgeHomeDir: eventsHome }), moduleBridge: new ModuleBridge({ terminalUrl: offlineUrl, engineUrl: offlineUrl, eventBridgeUrl: offlineUrl, vectorHubUrl: offlineUrl }) });
38
+ await initializeRuntime(runtime);
39
+ const server = http.createServer(createRequestHandler(runtime, { integrationOptions: { homeDir, env: {}, commandResolver: () => '' } }));
40
+ await new Promise(resolve => server.listen(0, '127.0.0.1', resolve));
41
+ const base = 'http://127.0.0.1:' + server.address().port;
42
+ const payload = JSON.stringify({ bridge: { ok: true }, scan: { state: 'ready' }, clients: [clientFixture('claude', 'Claude Code'), clientFixture('codex', 'Codex'), clientFixture('zcode', 'ZCode'), clientFixture('opencode', 'OpenCode')] });
43
+ let browser;
44
+ try {
45
+ browser = await launchCdpBrowser({ chrome: findChrome(), profileDir: path.join(temp, 'browser'), url: base, width: 1440, height: 900 });
46
+ const run = expression => browser.evaluate(expression);
47
+ const click = selector => run('document.querySelector(' + JSON.stringify(selector) + ').click()');
48
+ await run('(() => { const original = window.fetch; window.__originalFetch = original; window.fetch = (input, init) => { const url = String(input && input.url || input); if (url.includes(\'/api/integrations/status\')) { return Promise.resolve({ ok: true, json: async () => (' + payload + ') }); } return original(input, init); }; })()');
49
+ await click('#openSystem');
50
+ await waitFor(() => run("document.querySelectorAll('#agentCaptureList .agent-capture-row').length === 4"), 'capture rows render');
51
+ assert(await run("document.getElementById('agentCaptureList').textContent.includes('PROBE_TIMEOUT')"), 'the MCP startup failure code must be rendered for MCP-capable clients');
52
+ const claudeRowHtml = await run("document.querySelector('#agentCaptureList .agent-capture-row').innerHTML");
53
+ assert(claudeRowHtml.includes('&lt;img'), 'hostile mcpStartup detail must be HTML-escaped, got: ' + claudeRowHtml.slice(0, 300));
54
+ assert(!claudeRowHtml.includes('<img'), 'no raw hostile markup may reach the DOM');
55
+ assert.equal(await run('window.__kbXssProbe'), undefined, 'the hostile onerror must never execute');
56
+ const smalls = await run("[...document.querySelectorAll('#agentCaptureList small')].map(el => el.textContent).join('|')");
57
+ assert(smalls.split('|').some(text => text.startsWith('MCP')), 'the MCP diagnostic is its own line item, got: ' + smalls);
58
+ assert.equal(browser.exceptions.length, 0, JSON.stringify(browser.exceptions));
59
+ console.log('agent-capture-mcp-ui-test PASS (MCP line rendered separately; hostile detail escaped; no XSS execution)');
60
+ } finally {
61
+ await browser?.close(); server.closeAllConnections(); await new Promise(resolve => server.close(resolve));
62
+ offline.closeAllConnections(); await new Promise(resolve => offline.close(resolve)); await runtime.logger.close();
63
+ assert.equal(path.dirname(temp), path.resolve(os.tmpdir())); assert.ok(path.basename(temp).startsWith('kb-agent-mcp-ui-'));
64
+ fs.rmSync(temp, { recursive: true, force: true, maxRetries: 12, retryDelay: 200 });
65
+ }
66
+ }
67
+ main().catch(error => { console.error(error.stack); process.exitCode = 1; });
@@ -62,6 +62,7 @@ async function main() {
62
62
  await waitFor(() => run("document.querySelector('[data-agent-client=zcode][data-agent-capture=uninstall]') !== null"), 'theme retains capture status');
63
63
  await browser.screenshot(path.join(artifacts, '1920-dark.png'));
64
64
  fs.writeFileSync(configFile, '{invalid JSON');
65
+ await waitFor(() => run("(() => { const b = document.querySelector('[data-agent-client=zcode][data-agent-capture=install]'); return b !== null && !b.disabled; })()"), 'capture row settled before invalid config');
65
66
  await click('[data-agent-client=zcode][data-agent-capture=install]');
66
67
  await waitFor(() => run("document.getElementById('agentCaptureFeedback').textContent.includes('操作 ID')"), 'partial failure rendered');
67
68
  assert(await run("document.getElementById('agentCaptureFeedback').classList.contains('issue')"));
@@ -69,7 +70,7 @@ async function main() {
69
70
  assert(logs.length > 0 && logs[0].error.message.includes('valid JSON'));
70
71
  fs.writeFileSync(configFile, JSON.stringify(config));
71
72
  await click('#refreshAgentCapture');
72
- await waitFor(() => run("document.querySelector('[data-agent-client=zcode][data-agent-capture=uninstall]') !== null"), 'manual status retry');
73
+ await waitFor(() => run("(() => { const b = document.querySelector('[data-agent-client=zcode][data-agent-capture=uninstall]'); return b !== null && !b.disabled; })()"), 'manual status retry');
73
74
  await click('[data-agent-client=zcode][data-agent-capture=uninstall]');
74
75
  await waitFor(() => run("!document.querySelector('[data-agent-client=zcode]').disabled && document.querySelector('[data-agent-client=zcode][data-agent-capture=uninstall]') === null"), 'removed capture status');
75
76
  assert.equal(JSON.parse(fs.readFileSync(configFile)).provider.name, 'retain');
@@ -37,7 +37,8 @@ async function main() {
37
37
  assert(claudeMarketplace.plugins[0].version === packageInfo.version, 'Claude marketplace version should match the npm package');
38
38
  assert(codexMarketplace.name === 'project-knowledge', 'Codex marketplace should have a stable public name');
39
39
  assert(codexMarketplace.plugins[0].source.path === './plugins/project-knowledge', 'Codex marketplace should point at the shared plugin');
40
- assert(mcpManifest.mcpServers['project-knowledge'].args.includes(`${packageInfo.name}@${packageInfo.version}`), 'plugin MCP should pin the matching package name and version');
40
+ const mcpServer = mcpManifest.mcpServers['project-knowledge'];
41
+ assert(mcpServer.command === 'node' && mcpServer.args[0] === '-e' && mcpServer.cwd === undefined && String(mcpServer.args[1] || '').includes('mcp-launcher.cjs'), 'plugin MCP should launch the bundled local launcher instead of npx');
41
42
  assert(!skill.includes('[TODO:'), 'bundled Skill should not contain scaffold placeholders');
42
43
  assert(skill.includes('project_knowledge_resolve'), 'bundled Skill should teach the resolve-first workflow');
43
44
  assert(skill.includes('project_knowledge_record_requirement'), 'bundled Skill should document the explicit supplemental adapter');
@@ -98,6 +99,7 @@ async function main() {
98
99
  const claudeManager = new IntegrationManager({
99
100
  rootDir: ROOT,
100
101
  homeDir: claudeHome,
102
+ env: {},
101
103
  commandResolver: name => name,
102
104
  runner: (command, args) => {
103
105
  claudeCalls.push({ command, args });
@@ -117,6 +119,8 @@ async function main() {
117
119
  fs.mkdirSync(codexCache, { recursive: true }); fs.cpSync(pluginRoot, codexCache, { recursive: true });
118
120
  const codexManager = new IntegrationManager({
119
121
  rootDir: ROOT,
122
+ homeDir: path.join(TEMP, 'codex-home'),
123
+ env: {},
120
124
  commandResolver: name => name,
121
125
  runner: (command, args) => {
122
126
  codexCalls.push({ command, args });
@@ -218,6 +222,8 @@ async function main() {
218
222
  const integrationLogs = [];
219
223
  const failingManager = new IntegrationManager({
220
224
  rootDir: ROOT,
225
+ homeDir: path.join(TEMP, 'failing-home'),
226
+ env: {},
221
227
  commandResolver: name => name,
222
228
  runner: () => ({ status: 9, stdout: '', stderr: 'failure includes sk-do-not-log' }),
223
229
  logger: {