@sanqianx/project-knowledge 5.0.16-rc.5 → 5.0.16-rc.7

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.
@@ -9,7 +9,7 @@
9
9
  "plugins": [
10
10
  {
11
11
  "name": "project-knowledge",
12
- "version": "5.0.16-rc.5",
12
+ "version": "5.0.16-rc.7",
13
13
  "source": "./plugins/project-knowledge",
14
14
  "description": "Search durable project decisions and implementation history through MCP."
15
15
  }
package/CHANGELOG.md CHANGED
@@ -1,5 +1,18 @@
1
1
  # Changelog
2
2
 
3
+ ## [5.0.16-rc.7] - 2026-10-15
4
+
5
+ - 纳入引擎 0.2.7-rc.8:修复 commit 标识与主题混合查询只返回概述、漏掉已有正文证据的问题。已识别 SHA 只限定来源,不重复参与主题打分;提交内按主题 BM25 选择片段,普通语义查询保留 RRF,并补中英单写者与引擎词汇。
6
+ - 保持每文档两段、正文/向量区间校验、授权范围与上下文预算;同分排序稳定,不以标题中的 SHA 冒充任务事实已送达。
7
+ - 新增真实原文遗漏的红/绿用例及 topic-context 发布门禁;真实工作台网页测试验证具体事实和实际回答,其他模块版本不变。候选未完成安装版与原生验收前不更新 latest。
8
+
9
+ ## [5.0.16-rc.6] - 2026-10-15
10
+
11
+ - 修复 Codex 0.137 将 scoped npm 目录中的 `@` 误作 Git ref:本地目录含 `@` 时从该目录执行 `marketplace add .`,更换和回退分别使用自己的工作目录,不复制目录、不访问 GitHub。
12
+ - CLI 子操作日志关联原操作 ID,允许继续处理的失败仍记录安全诊断;本地路径误判保留 `CODEX_LOCAL_SOURCE_REF`,不写入原始 CLI 输出。
13
+ - 保留 rc.5 的已验证四模块版本、索引与知识功能。rc.5 本机保真升级已通过:7 项目、43 历史文件、565 索引文件保留;真实记忆闭环与双 Node 浏览器门禁通过,热查询 P95 为 693ms(1000 文档合成语料)。
14
+ - 本机 Codex 已通过原生本地市场操作恢复,MCP 声明入口 initialize/六工具/resolve/search/get 验证通过;Hook 尚待用户审核,不计自动上下文通过。候选不更新 latest。
15
+
3
16
  ## [5.0.16-rc.5] - 2026-10-15
4
17
 
5
18
  - 精确锁定 Workbench 0.13.2-rc.13:模型目录加载失败不再伪装成空目录;保留上次有效结果,提供明确错误与重试,打开菜单可恢复请求,关闭菜单后的迟到响应不会重开菜单。保存或删除供应商后不复用保存前的旧快照,普通菜单打开仍共享请求。
@@ -82,7 +82,7 @@ node src/bin.js serve [--port 5790] [--host 127.0.0.1] [--data DIR]
82
82
 
83
83
  上下文明确区分当前已确认记忆与历史事实:项目约定优先于领域,领域优先于全局;同范围冲突参考记忆的更新时间。历史提交中的旧实现、偏好描述不能覆盖本次下发的现行记忆;记忆也不扩大当前用户授权或替代安全限制。保留旧事实及其提交依据,不删除历史来制造新记忆生效的结果。
84
84
 
85
- 检索中明确命名且当前知识已有依据的源文件与 commit 标识优先做身份匹配,再进行语义排序,避免重复历史范例挤出指定文件。尚无知识依据的新文件名仍允许检索相似背景;不把没有范围授权的项目或原始会话当作匹配来源。
85
+ 检索中明确命名且当前知识已有依据的源文件与 commit 标识优先做身份匹配,避免重复历史范例挤出指定文件。已识别 commit 的 SHA 只限定提交依据,不再重复计入主题词分;该提交内按其余主题的 BM25 选择片段,分数为 `1 + BM25 / (1 + BM25)`(排序分,不是置信概率)。普通语义查询继续使用原 RRF。中英引擎、晋升、单写者词汇使用双向别名,保持每文档最多两个互补片段。尚无知识依据的新文件名仍允许检索相似背景;不把没有范围授权的项目或原始会话当作匹配来源。
86
86
 
87
87
  - journal 游标按来源隔离;证据投影及可恢复任务先落盘,再 ACK。不等待模型才能消费下一条事件。
88
88
  - 会话来源接受 bridge 的四客户端契约(Claude Code、Codex、OpenCode、ZCode)。单个 journal 的非法证据不被跳过或 ACK,但不阻塞其他项目;失败位置和错误码写入日志,健康接口提供 `journal.failures`,重复轮询不刷屏,恢复后续接原游标。
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sanqianx/ai-coding-knowledge-engine",
3
- "version": "0.2.7-rc.7",
3
+ "version": "0.2.7-rc.8",
4
4
  "description": "Knowledge engine module for project-knowledge: commit-boundary triggered knowledge analysis/generation (trigger plane) and retrieval for development assistants (retrieval plane).",
5
5
  "license": "Apache-2.0",
6
6
  "main": "src/server.js",
@@ -8,6 +8,8 @@ const ALIASES = [
8
8
  ['recipe', '配方'], ['plate', '配方'], ['alignment', '对齐'], ['report', '报表'], ['commit', '提交'],
9
9
  ['vector', '向量'], ['retrieval', '检索'], ['search', '检索'], ['knowledge', '知识'], ['timeout', '超时'],
10
10
  ['cancel', '取消'], ['connection', '连接'], ['save', '保存'],
11
+ ['engine', '引擎'], ['promotion', '晋升'], ['writer', '写入'],
12
+ ['one writer', '单写者'], ['single-writer', '单写者'],
11
13
  ];
12
14
  const segmenter = typeof Intl.Segmenter === 'function' ? new Intl.Segmenter('zh', { granularity: 'word' }) : null;
13
15
  const termCache = new Map();
@@ -50,9 +52,10 @@ function intent(query) {
50
52
  return 'search';
51
53
  }
52
54
 
53
- function lexical(candidates, query) {
55
+ function lexical(candidates, query, ignoredTerms = []) {
54
56
  const q = terms(query), wanted = new Map();
55
- for (const word of q.main) wanted.set(word, 1);
57
+ const ignored = new Set(ignoredTerms);
58
+ for (const word of q.main) if (!ignored.has(word)) wanted.set(word, 1);
56
59
  for (const gram of q.grams) if (!wanted.has(gram)) wanted.set(gram, 0.2);
57
60
  const rows = candidates.map(c => {
58
61
  const body = terms(c.chunkText), title = terms(`${c.title} ${c.headingPath.join(' ')} ${c.entryId}`), freq = new Map();
@@ -76,8 +79,10 @@ function lexical(candidates, query) {
76
79
  }
77
80
  }
78
81
 
79
- function rank(candidates, query, scoreIdentity) {
80
- lexical(candidates, query);
82
+ function rank(candidates, query, scoreIdentity, options = {}) {
83
+ // A resolved commit already constrains provenance. Repeated SHA text in a
84
+ // heading is not evidence for the topic asked about within that commit.
85
+ lexical(candidates, query, options.commitPrefixes || []);
81
86
  const eligible = [];
82
87
  for (const c of candidates) {
83
88
  const identified = scoreIdentity(c);
@@ -90,6 +95,7 @@ function rank(candidates, query, scoreIdentity) {
90
95
  c.selectionSignals = [...exact, ...(c.lexicalScore > 0 ? ['lexical:bm25'] : []), ...(c.vectorVerified ? ['index:vector-hub'] : []),
91
96
  `scope:${c.scopeReason}`, ...(c.fromOverlay ? ['markdown-delta-overlay'] : [])];
92
97
  c.identityMatch = exact.length > 0;
98
+ c.commitMatch = exact.includes('exact-commit');
93
99
  eligible.push(c);
94
100
  }
95
101
  const order = (a, b) => a.recordId.localeCompare(b.recordId);
@@ -97,8 +103,12 @@ function rank(candidates, query, scoreIdentity) {
97
103
  const vectorRows = eligible.filter(c => c.vectorVerified && c.indexScore > 0).sort((a, b) => b.indexScore - a.indexScore || order(a, b));
98
104
  const scores = new Map();
99
105
  for (const list of [lexicalRows, vectorRows]) list.forEach((c, i) => scores.set(c.recordId, (scores.get(c.recordId) || 0) + 1 / (60 + i + 1)));
100
- for (const c of eligible) c.relevanceScore = (scores.get(c.recordId) || 0) + (c.identityMatch ? 1 : 0);
101
- eligible.sort((a, b) => b.relevanceScore - a.relevanceScore || (a.scopeReason === 'primary' ? -1 : b.scopeReason === 'primary' ? 1 : 0) || order(a, b));
106
+ // For a resolved commit, order its snippets by the remaining topic, not by
107
+ // having two recall channels. Normal semantic queries keep RRF unchanged.
108
+ for (const c of eligible) c.relevanceScore = c.commitMatch ? 1 + c.lexicalScore / (1 + c.lexicalScore)
109
+ : (scores.get(c.recordId) || 0) + (c.identityMatch ? 1 : 0);
110
+ eligible.sort((a, b) => b.relevanceScore - a.relevanceScore
111
+ || Number(b.scopeReason === 'primary') - Number(a.scopeReason === 'primary') || order(a, b));
102
112
  const selected = [], byDocument = new Map();
103
113
  for (const c of eligible) {
104
114
  const key = `${c.spaceId}:${c.entryId}`, prior = byDocument.get(key) || [];
@@ -73,7 +73,7 @@ class EngineRetrieval extends KnowledgeRetrievalService {
73
73
  const ranked = ['greeting', 'followup'].includes(ranking.intent(signals.indexQuery)) ? [] : ranking.rank([...scanned.candidates.values()], signals.indexQuery, c => {
74
74
  const scored = this.score(c, signals);
75
75
  return { ...scored, excluded: Boolean(signals.commitPrefixes?.length || c.knownFileQuery) && scored.score <= 0 };
76
- });
76
+ }, { commitPrefixes: signals.commitPrefixes });
77
77
  const ordered = input.policy === 'project-first' ? [...ranked.filter(c => c.scopeReason === 'primary'), ...ranked.filter(c => c.scopeReason !== 'primary')] : ranked;
78
78
  const checked = new Map(), candidates = [];
79
79
  // Revalidate every returned document after the asynchronous vector call,
@@ -0,0 +1,186 @@
1
+ 'use strict';
2
+ // Red-first regression (Q7): Codex CLI 0.137 rejects
3
+ // `plugin marketplace add <SOURCE>` with
4
+ // "Error: --ref is only supported for git marketplace sources"
5
+ // whenever the SOURCE string contains '@', which every scoped npm root
6
+ // (node_modules/@sanqianx/project-knowledge) does. Running the same add from
7
+ // the package directory's own cwd with SOURCE '.' avoids the '@' entirely.
8
+ // ensureCodexMarketplace() must use that form for the target add and for the
9
+ // rollback re-add of a previous scoped root, while non-'@' absolute paths keep
10
+ // the original invocation and third-party same-name sources stay untouched.
11
+ const assert = require('node:assert/strict');
12
+ const fs = require('node:fs');
13
+ const os = require('node:os');
14
+ const path = require('node:path');
15
+ const { IntegrationManager } = require('../lib/integration-installer');
16
+
17
+ const TEMP_BASE = fs.mkdtempSync(path.join(os.tmpdir(), 'codex-marketplace-scoped-path-'));
18
+ const TEST_HOME = path.join(TEMP_BASE, 'home');
19
+ const MARKETPLACE = 'project-knowledge';
20
+ const REF_ERROR = 'Error: --ref is only supported for git marketplace sources';
21
+ const NEW_ROOT = path.join(TEMP_BASE, 'new-root', '@sanqianx', 'project-knowledge');
22
+ const OLD_ROOT = path.join(TEMP_BASE, 'old-root', '@sanqianx-previous', 'project-knowledge');
23
+ const PLAIN_ROOT = path.join(TEMP_BASE, 'plain-root', 'project-knowledge');
24
+ const THIRD_PARTY_ROOT = path.join(TEMP_BASE, 'third-party', 'marketplace');
25
+
26
+ function writePackage(directory, name, version) {
27
+ fs.mkdirSync(directory, { recursive: true });
28
+ fs.writeFileSync(path.join(directory, 'package.json'), JSON.stringify({ name, version }, null, 2));
29
+ }
30
+
31
+ // Native-style in-memory codex CLI mirroring 0.137 marketplace semantics:
32
+ // a marketplace add SOURCE containing '@' is parsed as <repo>@<ref> and
33
+ // rejected; SOURCE '.' resolves against runnerOptions.cwd.
34
+ function createCodexCli() {
35
+ const state = { marketplaces: new Map(), calls: [], addCalls: [], removeCalls: [], failAddDirs: new Set() };
36
+ const runner = (command, args, runnerOptions = {}) => {
37
+ state.calls.push({ command, args: [...args], cwd: runnerOptions.cwd || null });
38
+ if (args[0] !== 'plugin' || args[1] !== 'marketplace') {
39
+ return { status: 1, stdout: '', stderr: `fake codex CLI: unsupported command: ${args.join(' ')}` };
40
+ }
41
+ if (args[2] === 'list') {
42
+ const lines = [...state.marketplaces].map(([name, source]) => `${name} ${source}`);
43
+ return { status: 0, stdout: lines.length ? `${lines.join('\n')}\n` : '', stderr: '' };
44
+ }
45
+ if (args[2] === 'remove') {
46
+ state.marketplaces.delete(args[3]);
47
+ state.removeCalls.push(args[3]);
48
+ return { status: 0, stdout: '', stderr: '' };
49
+ }
50
+ if (args[2] === 'add') {
51
+ const source = String(args[3]);
52
+ const resolved = path.resolve(String(runnerOptions.cwd || process.cwd()), source);
53
+ state.addCalls.push({ source, cwd: runnerOptions.cwd || null, resolved });
54
+ if (source.includes('@')) return { status: 1, stdout: '', stderr: REF_ERROR };
55
+ if (state.failAddDirs.has(resolved)) return { status: 1, stdout: '', stderr: 'forced marketplace add failure (test)' };
56
+ let stat = null;
57
+ try { stat = fs.statSync(resolved); } catch { /* missing source handled below */ }
58
+ if (!stat || !stat.isDirectory()) return { status: 1, stdout: '', stderr: `Error: marketplace source not found: ${source}` };
59
+ state.marketplaces.set(MARKETPLACE, resolved);
60
+ return { status: 0, stdout: '', stderr: '' };
61
+ }
62
+ return { status: 1, stdout: '', stderr: `fake codex CLI: unsupported marketplace command: ${args.join(' ')}` };
63
+ };
64
+ return { runner, state };
65
+ }
66
+
67
+ function createManager(cli, rootDir) {
68
+ return new IntegrationManager({
69
+ rootDir,
70
+ homeDir: TEST_HOME,
71
+ bridgeHomeDir: path.join(TEMP_BASE, 'bridge'),
72
+ env: {},
73
+ commandResolver: name => `fake-${name}`,
74
+ runner: cli.runner,
75
+ });
76
+ }
77
+
78
+ function registeredSource(cli) {
79
+ const line = [...cli.state.marketplaces].map(([name, source]) => `${name} ${source}`)
80
+ .find(item => item.startsWith(`${MARKETPLACE} `));
81
+ return line ? line.slice(MARKETPLACE.length).trim() : null;
82
+ }
83
+
84
+ function lastAdd(cli) {
85
+ assert.ok(cli.state.addCalls.length, 'expected at least one marketplace add call');
86
+ return cli.state.addCalls[cli.state.addCalls.length - 1];
87
+ }
88
+
89
+ function assertDotAdd(add, expectedRoot, label) {
90
+ assert.equal(add.source, '.', `${label}: scoped root must be added via the "." source, not an "@"-containing path`);
91
+ assert.equal(path.resolve(add.cwd || ''), expectedRoot, `${label}: "." must resolve against the package directory's own cwd`);
92
+ }
93
+
94
+ function main() {
95
+ writePackage(NEW_ROOT, '@sanqianx/project-knowledge', '5.0.16');
96
+ writePackage(OLD_ROOT, '@sanqianx/project-knowledge', '5.0.15');
97
+ writePackage(PLAIN_ROOT, '@sanqianx/project-knowledge', '5.0.16');
98
+ writePackage(THIRD_PARTY_ROOT, 'third-party-market', '1.0.0');
99
+
100
+ const results = [];
101
+ const scenario = (name, fn) => {
102
+ try { fn(); results.push({ name, ok: true }); }
103
+ catch (error) { results.push({ name, ok: false, error: String((error && error.message) || error) }); }
104
+ };
105
+
106
+ scenario('fresh: scoped npm root installs via "." from its own cwd', () => {
107
+ const cli = createCodexCli();
108
+ createManager(cli, NEW_ROOT).ensureCodexMarketplace();
109
+ assert.equal(registeredSource(cli), NEW_ROOT, 'fresh install must register the scoped npm root');
110
+ assertDotAdd(lastAdd(cli), NEW_ROOT, 'fresh install');
111
+ assert.equal(cli.state.removeCalls.length, 0, 'fresh install must not remove anything');
112
+ });
113
+
114
+ scenario('switch: replacing a previous scoped root registers the new root', () => {
115
+ const cli = createCodexCli();
116
+ cli.state.marketplaces.set(MARKETPLACE, OLD_ROOT);
117
+ createManager(cli, NEW_ROOT).ensureCodexMarketplace();
118
+ assert.ok(cli.state.removeCalls.includes(MARKETPLACE), 'switch must remove the previous marketplace first');
119
+ assert.equal(registeredSource(cli), NEW_ROOT, 'switch must end registered on the new scoped root');
120
+ assertDotAdd(lastAdd(cli), NEW_ROOT, 'switch');
121
+ assert.ok(cli.state.addCalls.every(add => !add.source.includes('@')), 'no add may pass an "@"-containing source');
122
+ });
123
+
124
+ scenario('rollback: failed switch restores the old scoped root', () => {
125
+ const cli = createCodexCli();
126
+ cli.state.marketplaces.set(MARKETPLACE, OLD_ROOT);
127
+ cli.state.failAddDirs.add(NEW_ROOT);
128
+ assert.throws(() => createManager(cli, NEW_ROOT).ensureCodexMarketplace(), /已恢复原市场来源/);
129
+ assert.equal(registeredSource(cli), OLD_ROOT, 'rollback must restore the previous scoped root');
130
+ assertDotAdd(lastAdd(cli), OLD_ROOT, 'rollback');
131
+ assert.ok(cli.state.addCalls.every(add => !add.source.includes('@')), 'rollback add must also avoid "@" in the source');
132
+ });
133
+
134
+ scenario('plain path: non-"@" absolute source keeps the original invocation', () => {
135
+ const cli = createCodexCli();
136
+ createManager(cli, PLAIN_ROOT).ensureCodexMarketplace();
137
+ assert.equal(registeredSource(cli), PLAIN_ROOT);
138
+ const add = lastAdd(cli);
139
+ assert.equal(add.source, PLAIN_ROOT, 'non-scoped absolute path must keep being passed as the add source');
140
+ assert.notEqual(add.source, '.', 'the "." form is reserved for "@"-containing paths');
141
+ });
142
+
143
+ scenario('third-party: same-name marketplace is never overwritten', () => {
144
+ const cli = createCodexCli();
145
+ cli.state.marketplaces.set(MARKETPLACE, THIRD_PARTY_ROOT);
146
+ assert.throws(() => createManager(cli, NEW_ROOT).ensureCodexMarketplace(), /未覆盖第三方来源/);
147
+ assert.equal(cli.state.addCalls.length, 0, 'third-party source must not be replaced');
148
+ assert.equal(cli.state.removeCalls.length, 0, 'third-party source must not be removed');
149
+ assert.equal(registeredSource(cli), THIRD_PARTY_ROOT, 'third-party registration must be untouched');
150
+ });
151
+
152
+ scenario('allowed failure keeps operation linkage and a safe diagnostic code', () => {
153
+ const cli = createCodexCli();
154
+ const logs = [];
155
+ const manager = createManager(cli, NEW_ROOT);
156
+ manager.operationId = 'op-native-source-test';
157
+ manager.logger = Object.fromEntries(['info', 'warn', 'error'].map(level => [level, (event, message, meta) => logs.push({ event, message, meta })]));
158
+ manager.execute('codex', ['plugin', 'marketplace', 'add', NEW_ROOT], { allowFailure: true });
159
+ const failure = logs.find(log => log.event === 'integration.command_failed');
160
+ assert.ok(failure, 'a tolerated CLI failure must not be logged as success');
161
+ assert.equal(failure.meta.operationId, 'op-native-source-test');
162
+ assert.equal(failure.meta.error.code, 'CODEX_LOCAL_SOURCE_REF');
163
+ assert.equal(failure.meta.context.status, 1);
164
+ assert.ok(!JSON.stringify(logs).includes(REF_ERROR), 'never save raw CLI output');
165
+ });
166
+
167
+ assert.equal(path.dirname(TEMP_BASE), path.resolve(os.tmpdir()));
168
+ assert.equal(fs.lstatSync(TEMP_BASE).isSymbolicLink(), false);
169
+ fs.rmSync(TEMP_BASE, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 });
170
+
171
+ const failed = results.filter(item => !item.ok);
172
+ for (const item of results) console.log(`${item.ok ? 'PASS' : 'FAIL'} ${item.name}${item.ok ? '' : ` :: ${item.error}`}`);
173
+ if (failed.length) {
174
+ console.error(`codex-marketplace-scoped-path-test: ${failed.length} of ${results.length} case(s) failed.`);
175
+ process.exitCode = 1;
176
+ return;
177
+ }
178
+ console.log('codex-marketplace-scoped-path-test passed.');
179
+ }
180
+
181
+ try {
182
+ main();
183
+ } catch (error) {
184
+ console.error(error);
185
+ process.exitCode = 1;
186
+ }
@@ -436,7 +436,7 @@ class IntegrationManager {
436
436
 
437
437
  log(level, event, message, input = {}) {
438
438
  if (!this.logger || typeof this.logger[level] !== 'function') return;
439
- Promise.resolve(this.logger[level](event, message, { component: 'integration-manager', ...input })).catch(() => {
439
+ Promise.resolve(this.logger[level](event, message, { component: 'integration-manager', operationId: this.operationId, ...input })).catch(() => {
440
440
  try { process.stderr.write(`[integration-manager logger fallback] ${event}\n`); } catch {
441
441
  // stderr is the final non-recursive observer fallback.
442
442
  }
@@ -638,19 +638,29 @@ class IntegrationManager {
638
638
  }
639
639
  const result = this.runner(command, args, {
640
640
  env: this.env,
641
- cwd: this.rootDir,
641
+ cwd: options.cwd || this.rootDir,
642
642
  timeout: options.timeout,
643
643
  });
644
644
  if (result.error) {
645
645
  this.log('error', 'integration.command_failed', 'Integration CLI command failed.', { durationMs: Date.now() - startedAt, error: Object.assign(new Error('Integration CLI command failed.'), { code: result.error.code || 'INTEGRATION_COMMAND_FAILED' }), context: { client, argsHash } });
646
646
  throw result.error;
647
647
  }
648
+ const localRefFailure = client === 'codex' && String(result.stderr || '').trim() === 'Error: --ref is only supported for git marketplace sources';
648
649
  if (result.status !== 0 && !options.allowFailure) {
649
650
  const detail = String(result.stderr || result.stdout || '').trim();
650
- this.log('error', 'integration.command_failed', 'Integration CLI command failed.', { durationMs: Date.now() - startedAt, error: Object.assign(new Error('Integration CLI command failed.'), { code: 'INTEGRATION_COMMAND_FAILED' }), context: { client, status: result.status, argsHash, outputHash: `sha256:${crypto.createHash('sha256').update(detail, 'utf8').digest('hex')}` } });
651
+ this.log('error', 'integration.command_failed', 'Integration CLI command failed.', { durationMs: Date.now() - startedAt, error: Object.assign(new Error('Integration CLI command failed.'), { code: localRefFailure ? 'CODEX_LOCAL_SOURCE_REF' : 'INTEGRATION_COMMAND_FAILED' }), context: { client, status: result.status, argsHash, outputHash: `sha256:${crypto.createHash('sha256').update(detail, 'utf8').digest('hex')}` } });
651
652
  throw new Error(detail || `${CLIENT_LABELS[client]} command failed with exit code ${result.status}`);
652
653
  }
653
- this.log('info', 'integration.command_completed', 'Integration CLI command completed.', { durationMs: Date.now() - startedAt, context: { client, status: result.status, argsHash } });
654
+ const completed = result.status === 0;
655
+ const safeError = completed ? undefined : Object.assign(new Error(localRefFailure
656
+ ? 'Codex 把本地目录中的 @ 误当作 Git ref;请使用目录内相对路径重试。'
657
+ : 'Integration CLI command returned a failure.'), {
658
+ code: localRefFailure ? 'CODEX_LOCAL_SOURCE_REF' : 'INTEGRATION_COMMAND_FAILED',
659
+ });
660
+ this.log(completed ? 'info' : 'warn', completed ? 'integration.command_completed' : 'integration.command_failed', completed ? 'Integration CLI command completed.' : 'Integration CLI command returned a failure.', {
661
+ durationMs: Date.now() - startedAt, error: safeError,
662
+ context: { client, status: result.status, argsHash },
663
+ });
654
664
  return result;
655
665
  }
656
666
 
@@ -718,6 +728,16 @@ class IntegrationManager {
718
728
  this.execute('claude', ['plugin', 'marketplace', 'add', this.claudeMarketplaceSource]);
719
729
  }
720
730
 
731
+ addCodexMarketplaceSource(source, options = {}) {
732
+ // Codex 0.137 parses @ in an absolute npm path as a Git ref. Resolve a
733
+ // real local directory via cwd instead; rollback must use its own cwd.
734
+ let local = false;
735
+ if (path.isAbsolute(source) && source.includes('@')) {
736
+ try { local = fs.statSync(source).isDirectory(); } catch {}
737
+ }
738
+ return this.execute('codex', ['plugin', 'marketplace', 'add', local ? '.' : source], local ? { ...options, cwd: source } : options);
739
+ }
740
+
721
741
  ensureCodexMarketplace() {
722
742
  const target = path.resolve(this.codexMarketplaceAddSource);
723
743
  if (!this.dryRun) {
@@ -727,9 +747,9 @@ class IntegrationManager {
727
747
  if (path.resolve(existing).toLowerCase() === target.toLowerCase()) return;
728
748
  if (!this.isOwnCodexMarketplaceSource(existing)) throw new Error('同名 Codex 插件市场来源不是当前安装包,且无法确认属于本产品;未覆盖第三方来源。请检查 Codex 插件市场配置。');
729
749
  this.execute('codex', ['plugin', 'marketplace', 'remove', MARKETPLACE_NAME]);
730
- const added = this.execute('codex', ['plugin', 'marketplace', 'add', target], { allowFailure: true });
750
+ const added = this.addCodexMarketplaceSource(target, { allowFailure: true });
731
751
  if (added.status !== 0) {
732
- const restored = this.execute('codex', ['plugin', 'marketplace', 'add', existing], { allowFailure: true });
752
+ const restored = this.addCodexMarketplaceSource(existing, { allowFailure: true });
733
753
  throw new Error(restored.status === 0
734
754
  ? 'Codex 插件市场更换为当前安装包失败,已恢复原市场来源;请重试接入。'
735
755
  : 'Codex 插件市场更换为当前安装包失败,恢复原市场来源也未成功;请手动检查 codex 插件市场。');
@@ -737,7 +757,7 @@ class IntegrationManager {
737
757
  return;
738
758
  }
739
759
  }
740
- this.execute('codex', ['plugin', 'marketplace', 'add', target]);
760
+ this.addCodexMarketplaceSource(target);
741
761
  }
742
762
 
743
763
  installClaude(options = {}) {
@@ -70,7 +70,9 @@ Leader 负责知识检索、关键开发、接口决策、打包升级和最终
70
70
 
71
71
  Leader 只读取简洁检查点、相关 diff、测试结果和关键失败片段,不持续拉取完整思考流或整个事件历史。
72
72
 
73
- `0.13.2-rc.10` 已复现长历史截断后的游标重建缺陷;rc.11 的源码及独立包恢复测试已通过,实际产品升级与浏览器验收仍在进行。旧版本的快照序号和合成事件时间不能作为全会话游标或实际运行耗时证据;优先读取轻量状态、真实审计时间及操作收据,不因 UI 没有新消息就重复派发任务。
73
+ `0.13.2-rc.10` 已复现长历史截断后的游标重建缺陷;修复保留在当前 rc.13,统一产品 rc.6 安装版的桌面、故障和重启验收已通过。旧版本的快照序号和合成事件时间不能作为全会话游标或实际运行耗时证据;优先读取轻量状态、真实审计时间及操作收据,不因 UI 没有新消息就重复派发任务。
74
+
75
+ 本轮已实际并行三条 GLM 检查线:安装版八项回归、真模型记忆十项闭环、包内容与隔离原生安装。Leader 独立核对每份安装收据与模块版本,不只接受 GLM 的最终回答。三条线均 idle 后才升级主实例;性能测试也在其他重任务结束后单独执行。旧的 Workbench contextId 可能不在当前项目列表中,升级前还应核对自己已派发的会话,而不是只检查当前筛选项目。
74
76
 
75
77
  ## 审查与放行
76
78
 
@@ -2,6 +2,42 @@
2
2
 
3
3
  ## 当前结论
4
4
 
5
+ 正在准备 `5.0.16-rc.7`,纳入引擎 `0.2.7-rc.8` 的真实主题片段遗漏修复,其他三个模块保持已锁定版本。源码修复已通过固定真实向量质量集及双 Node 回归;新安装包仍须复测,不能将 rc.6 的结果记为 rc.7 通过。latest 仍为 5.0.14。
6
+
7
+ rc.6 桌面回答的人工核对发现:已有文档 `changes/9aec087aa155.md` 含单写者 Patch 原文,但注入上下文只含标题和概述,旧验收只查 SHA 而漏报。GLM 独立复现;实际 rc.6 `topic-context` 失败记录保留。最小修复保持每文档两段和范围/正文安全校验:已识别 SHA 仅约束提交来源,不再参与主题词评分;该提交内按其余主题选择片段,并补中英引擎、晋升、单写者别名。新增发布必测 `topic-context24`;真实网页发送同时检查原始事实送达及真实回答引用该事实,不能只凭 SHA 判通过。候选现为 30 项门禁,rc.6 原 28 项通过不抵消新增的真实失败。
8
+
9
+ `5.0.16-rc.6` 已发布 npm `next`、官方下载并保留现有 v5 数据升级到本机。其四模块为 Workbench rc.13、引擎 rc.7、vector-hub rc.8、bridge 0.7.2。Codex 原生 Hook 审核后的模型送达仍未通过;正式包下载、正式升级和日常 `latest` 更新验收尚未完成。
10
+
11
+ 本机运行 rc.6(PID 30924,5757),保真升级保留 7 个项目、既有历史、知识、记忆、配置及 D 盘索引,官方下载字节核对通过。实际“Agent 接入/修复”操作 `op-92a4925f9e904f7584fda255cf048a87` 成功接线四客户端。Codex 实际插件 MCP 已完成 initialize、版本与六工具检查;原生 Hook 为 modified,测试在模型调用之前停止,未代写信任记录。
12
+
13
+ ### rc.6 已核对结果
14
+
15
+ | 范围 | 结果与证据 |
16
+ |---|---|
17
+ | 官方下载 | 收据 `kb-registry-product-pWscTw/registry-installation.json`;sourceCommit `0f052cf6d3e380dcc23a7bf6d8ec96daa4f7f7c4`;主包 305 文件;integrity `sha512-BsR64nrciugXOEkrnuXjG46ceX5kicqnHhjNZ6QbOTMZlamuF3SZvvhIEtcsCa30i9ToOreidigJMRMEkmTUbQ==` |
18
+ | 冻结源码 | 外壳 Node 22/24 各 139/139;引擎各 169 通过、1 项 Windows 符号链接权限跳过;Workbench 365、vector-hub 73、bridge 30 的已锁定同提交源码结果保持有效 |
19
+ | 安装版桌面 | 正常网页、一次 503 恢复、故障、生命周期共八项,Node 22/24,全部绑定 rc.6 收据;1920×1080 |
20
+ | 真模型记忆 | MiniMax 真实提取、项目/领域/全局范围、确认编辑、1536 维向量、流式回答、单/并发提交及重启保真 10/10;`scoped-memories/run-1792021974460/report.json` |
21
+ | 真向量质量与性能 | 固定合成留出集 Recall@5=1、MRR=0.9667;8 项目/1000 文档热查询 P95=669.0993ms,单独运行,四次查询 embedding;不等于另一台电脑私有语料验收 |
22
+ | 二次开发 | 实际 search/get → 修改前引用 → 小改动与测试 → 真实 commit → 自动晋升与索引 → 再检索通过;`retrieval-rc6-secondary24-1792022320274.json` |
23
+ | 真实页面与知识面 | UI 发送 → 真模型 → 28 条文本增量与引用 → 后端最终 turn;API/CLI/MCP 事实、引用一致;非空 embo-01 索引 1536 维 |
24
+ | 原生客户端 | Claude Code、ZCode 首次普通开发请求自动收到当前事实与产品记忆,实际代码采用记忆;Claude 提交后维护及下次检索通过。现有真实 knowledge-engine 项目 Claude 只读上下文通过、工作树保持不变。OpenCode 实际知识 CLI 调用与引用通过,不宣称自动前置注入 |
25
+ | Codex 安装器 | GLM 独立用 npm 包真实 installCodex 在隔离 CODEX_HOME 下验证 Node 22/24:scoped 路径、本地目录来源、幂等、版本及文件匹配通过;第三方与 trust 未改。该项不代替原生模型送达 |
26
+
27
+ 收据根为 `knowledge-engine/.agent-state/acceptance/system-regression/`,候选汇总为 `retrieval-release-5.0.16-rc.6.json`。原生 Codex 首失败 `retrieval-rc6-codex-real24-1792022749447.json` 明确为 modified 而非 trusted,模型尚未调用。
28
+
29
+ OpenCode 首次失败保留:工具实际返回了权威的精确提交事实,但旧验收硬编码 `hybrid+markdown-truth`,并要求最新提交包含单写者规则。测试修正允许合法文本事实后端,改为摘要实际返回事实;同时加强 matching tool-id、顺序、项目、服务来源、完整 SHA 与路径检查。4000 字符显示截断仅接受已完整的第一条引用,不把文本检索称为向量成功;独立向量门禁不变。首次红测试与真实复测分别保存。
30
+
31
+ Leader 另有一次补验参数错误:`--verify-only` 未配 `--resume`,重置了受控 fixture 的提交验收报告,未改用户数据或知识。保留事故记录,重新用唯一 `rc6-leader1` 轮次执行三次真实提交,不重造原成功报告。原十项记忆报告及 GLM 证据仍保留。详情见 `glm-plan-5.0.16/rc6-leader-harness-incident.json`。
32
+
33
+ ## 历史批次
34
+
35
+ rc.5 官方安装收据 `kb-registry-product-2PqRja/registry-installation.json` 已验证。五仓库冻结回归在 Node 22/24 均通过:外壳 138、Workbench 365、vector-hub 73、bridge 30;引擎 169 通过、1 项 Windows 符号链接权限跳过。安装版正常页面、一次 503 恢复、故障和生命周期八项均通过。真实 MiniMax 记忆、流式、单/并发提交、1536 维索引及重启 10 项通过;固定留出集 Recall@5=1、MRR=0.9667,单独热查询 P95=692.9615ms。
36
+
37
+ 已通过保护备份与 npm 全局升级把本机安装为 rc.5,7 个现有项目、43 个历史文件、565 个 D 盘索引文件及配置保持不变,运行字节与官方下载包一致。首次本机客户端修复的 Codex 失败操作 `op-29b0bc154aa94f43aa2dc759cf898697` 保留:隔离原生 CLI 复现绝对路径中的 `@` 被当作 ref,目录内 `add .` 成功;随后仅通过原生命令恢复本产品市场,正常修复操作 `op-d6d22efd9e534b1f9d06cd0ba06680cb` 成功。未修改信任记录。
38
+
39
+ Codex 声明入口的真实 MCP initialize/六工具/resolve/search/get 已通过;该结果不等于 Codex 原生模型任务自动收到知识。当前 Hook 为 modified,尚未运行此模型门禁。Claude、ZCode、OpenCode 接线已完成,实际原生送达、二次开发及正式包验收仍需完成。用于实际 Codex 路径回归的 GLM 测试文件经 Leader 修正为跨平台唯一临时目录,避免共享目录清理;首次红测试保留。
40
+
5
41
  2026-10-14 开始执行本轮计划。状态:**实施中,未完成系统验收,不更新 latest**。
6
42
 
7
43
  2026-10-15 收敛到候选 `5.0.16-rc.5`,精确锁定 Workbench `0.13.2-rc.13`,其余模块保留 rc.4 的已验收运行文件。Leader 负责关键开发与交付,GLM 独立并行故障测试、回归和审查;不新增产品功能。
@@ -10,7 +46,7 @@ rc.4 官方安装收据 `kb-registry-product-NPyWjR/registry-installation.json`
10
46
 
11
47
  rc.4 首次 Node 22 网页失败的历史原因仍无法确定,后续正常路径重复通过。另以一次 `/profiles` HTTP 503 在真实 Workbench iframe 中稳定复现了「首次失败后模型列表一直为空且不再请求」;两次失败记录保留。Workbench rc.12 已修复这一缺陷类,Leader 冻结源码回归 Node 22/24 各 362/362;新整包必须通过同一故障注入用例才能计为恢复。
12
48
 
13
- 用户当前仍运行 rc.1。其六条索引失败已通过公开索引阶段重试恢复,知识正文、晋升时间和分析运行 ID 不变;原 `532d` 运行在操作前已成功,未重复分析。候选安装、原生客户端、新版保真升级与正式发布门禁仍待完成。
49
+ rc.4 阶段用户仍运行 rc.1。其六条索引失败已通过公开索引阶段重试恢复,知识正文、晋升时间和分析运行 ID 不变;原 `532d` 运行在操作前已成功,未重复分析。当时的候选安装、原生客户端、新版保真升级与正式发布门禁仍待完成。
14
50
 
15
51
  GLM 独立审查复现 rc.12 连续保存供应商时复用保存前快照,Leader 修复为保存后等待旧读取结束、再发起可共享的新读取;普通菜单仍 single-flight。两条新竞态测试先失败后修复。菜单异步重绘清空过滤词属于已记录的低风险体验限制,未作为核心修复扩展。
16
52
 
@@ -9,9 +9,9 @@
9
9
  "plugins/project-knowledge/hooks/codex.json": "216e03c07295606ef6ebb2c4f669ad0d790b5e7c4f3f34600488033db75e4239",
10
10
  "plugins/project-knowledge/scripts/context-hook.cjs": "da666153ad5723592cce42d25337ae649f7d93e43fab76b3c6d1263933de063f",
11
11
  "plugins/project-knowledge/scripts/mcp-launcher.cjs": "7c68830b5c73eb53ef030756a4de33987f7eab6b1a6cc15602835aac3f954893",
12
- "plugins/project-knowledge/.claude-plugin/plugin.json": "c23181cb440688eedd0a6e307ceda43a5e5553d847d4589ad14780d548b57bd3",
13
- "plugins/project-knowledge/.codex-plugin/plugin.json": "991b0a4ae85318968e96b511508cae6a359c3d936b5d5bc99105570e95b171f0",
14
- "plugins/project-knowledge/.mcp.json": "488fba785e30d53071b433bbf6339c8b4bd7733a7838c3d1e925cc4d1a86af45"
12
+ "plugins/project-knowledge/.claude-plugin/plugin.json": "280f0e6a4a28ff651687aaa7580a2546a8fdb2dd9228c7927d881105ac8ec81c",
13
+ "plugins/project-knowledge/.codex-plugin/plugin.json": "d78289598df58e456404dc806d56cd96d7757f2f1467bfff1e0eedbf1e016619",
14
+ "plugins/project-knowledge/.mcp.json": "c6206be7c504dffc6e0116cbdfb76bef5324a33331da35d131fede5c88335239"
15
15
  },
16
16
  "modules": {
17
17
  "terminal": {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sanqianx/project-knowledge",
3
- "version": "5.0.16-rc.5",
3
+ "version": "5.0.16-rc.7",
4
4
  "description": "Knowledge base manager with Git integration, AI-driven analysis, and bilingual (zh-CN/en-US) knowledge output",
5
5
  "main": "_site/server.js",
6
6
  "scripts": {
@@ -2,7 +2,7 @@
2
2
  "$schema": "https://json.schemastore.org/claude-code-plugin-manifest.json",
3
3
  "name": "project-knowledge",
4
4
  "hooks": "./hooks/claude.json",
5
- "version": "5.0.16-rc.5",
5
+ "version": "5.0.16-rc.7",
6
6
  "description": "Read durable project decisions and implementation history from Project Knowledge.",
7
7
  "author": {
8
8
  "name": "SanQianX",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "project-knowledge",
3
- "version": "5.0.16-rc.5",
3
+ "version": "5.0.16-rc.7",
4
4
  "description": "Read durable project decisions and implementation history from Project Knowledge.",
5
5
  "author": {
6
6
  "name": "SanQianX",
@@ -8,7 +8,7 @@
8
8
  "const fs=require('fs'),path=require('path'),os=require('os');const PKG='@sanqianx/project-knowledge',SC='project-knowledge/integration-runtime/v1';let f='';const root=process.env.CLAUDE_PLUGIN_ROOT||process.env.PLUGIN_ROOT;if(root){f=path.join(root,'scripts','mcp-launcher.cjs');if(!fs.existsSync(f))f=''}if(!f){const dd=process.env.KB_DATA_DIR||path.join(os.homedir(),'.project-knowledge-v5');try{const bp=path.join(dd,'integration-runtime.json');const st=fs.statSync(bp);if(st.isFile()&&st.size<=32768){const b=JSON.parse(fs.readFileSync(bp,'utf8'));if(b&&b.schema===SC&&b.packageName===PKG&&typeof b.packageRoot==='string'&&path.isAbsolute(b.packageRoot)){const c=path.join(b.packageRoot,'plugins','project-knowledge','scripts','mcp-launcher.cjs');const rr=fs.realpathSync(b.packageRoot),cr=fs.realpathSync(c);const rel=path.relative(rr,cr);if(rel&&rel!=='..'&&!rel.startsWith('..'+path.sep)&&!path.isAbsolute(rel)&&fs.statSync(cr).isFile())f=cr}}}catch(e){}}if(!f){console.error('[project-knowledge-mcp] 未能通过受信任插件根或已验证的安装绑定定位启动器,已停止;不访问网络,也不加载工作目录源码。');process.exit(127)}require(f);"
9
9
  ],
10
10
  "env": {
11
- "KB_PLUGIN_VERSION": "5.0.16-rc.5"
11
+ "KB_PLUGIN_VERSION": "5.0.16-rc.7"
12
12
  }
13
13
  }
14
14
  }
@@ -3,11 +3,11 @@
3
3
  "modules": {
4
4
  "knowledge-engine": {
5
5
  "name": "@sanqianx/ai-coding-knowledge-engine",
6
- "version": "0.2.7-rc.7",
6
+ "version": "0.2.7-rc.8",
7
7
  "root": "_modules/knowledge-engine",
8
8
  "files": {
9
- "README.md": "399d94bdb9b6844b9c1965799400212c99bcbbf872cdec953fba09ba4eea7acd",
10
- "package.json": "576b6b849d4fecec421c639fa7a3c84ce6b9ee70686baaad48d0153300b113f1",
9
+ "README.md": "3cb4388840732e3ceba3e7ab027248815387cdd35f8ca1e515c38038a7aef710",
10
+ "package.json": "f1386b7d2d7bc3c26130ba55ffffd9ba4626b718b3317c0f9f8bb911db1fcdb0",
11
11
  "src/analysis-contract.js": "913a63b63fb4a4a9df071f95bbeb8f8c40ef87e2f8445ab0742086ced70ffbf7",
12
12
  "src/analysis-output.js": "95a3c19dcc9ab85d1dba7fee5b9aec80f9c000dfba89da3fd0be25a81e2fb680",
13
13
  "src/analysis-worker.js": "a105cabe6e1573b2d986b1e7a5aaf615e2fb53524156fc8e4e2c0a71f2c97e55",
@@ -21,8 +21,8 @@
21
21
  "src/knowledge-targets.js": "aa44ec285d16d7b1c0b63bf084058a1d6f867fa9929c753b6b0cbb24f7b20ee2",
22
22
  "src/memories.js": "bba8e19380e906a80fd3e5d2a7f809d940466924578c8187d1f376f906f10f2b",
23
23
  "src/retrieval-policy.json": "3205fb4eb114a839a74f902e6fae6a61e1460958e5a9d7e29add32b60ba4e05a",
24
- "src/retrieval-ranking.js": "422c068ea995d4f8c7e1ffb64b4b72921017862add74da8996b1827c6c64c23d",
25
- "src/retrieval.js": "341c558ffd53873744cfb36139c84b92775886c0e41804e85f93aa9004d91bd9",
24
+ "src/retrieval-ranking.js": "e27a869a3b2f3dc96763f6a12c988b42098e461362b456f8cbe26ef5e37c3d21",
25
+ "src/retrieval.js": "32f17494eb838cc6b209df35dbe33a155c1728420c9e501a4974c4f2a1b33ceb",
26
26
  "src/runtime/README.md": "9be676eca76a7857b34d7357194e6f0ee733c22e61d962d53984715dbec7f673",
27
27
  "src/runtime/atomic-file.js": "9846b4b69db5ded811acf19a1181ef93615d93efaa542b858e552a7f3124ad23",
28
28
  "src/runtime/automation-config.js": "6498c5397be12b155829740595a1553d4dcd6eaa7db6ced3ef88fdc60218f00e",
@@ -47,7 +47,7 @@
47
47
  "ui/app.js": "33a2ba38f0dafbbb2e30a1eb3fc93476bfcaf528e124e1a9b2edd2fb69221e9e",
48
48
  "ui/index.html": "ce3565cfefd4838b11d37a91a906ef26ca5a1834844fe3f7c897b56851a3c418"
49
49
  },
50
- "sourceCommit": "61e5205c8c7a37645fa2be8c9e3cb2d3c250cc31"
50
+ "sourceCommit": "dcdeb3d1589cae2baf9a3445261acc990a5b0702"
51
51
  }
52
52
  }
53
53
  }