@boyingliu01/xp-gate 0.19.3 → 0.20.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.
- package/adapters/gate-4.sh +83 -18
- package/adapters/gate-8.sh +49 -5
- package/adapters/iac.sh +1 -1
- package/adapters/typescript.sh +15 -60
- package/bin/xp-gate.js +15 -16
- package/gate-10.sh +35 -2
- package/gate-4.sh +110 -21
- package/gate-8.sh +49 -5
- package/hooks/gate-10.sh +35 -2
- package/hooks/gate-4.sh +110 -21
- package/hooks/gate-8.sh +49 -5
- package/hooks/lib/jscpd-run.sh +63 -0
- package/hooks/lib/sprint-gate-report.sh +58 -0
- package/hooks/lib/test-failure.sh +152 -0
- package/hooks/lib/typecheck.sh +141 -0
- package/hooks/lib/validate-code-walkthrough.cjs +43 -5
- package/hooks/post-merge +100 -0
- package/hooks/pre-commit +389 -162
- package/hooks/pre-push +75 -19
- package/lib/__tests__/bootstrap.test.js +23 -41
- package/lib/__tests__/check-version.test.js +8 -1
- package/lib/__tests__/detect-deps.test.js +16 -0
- package/lib/__tests__/doctor-drift-round2.test.js +212 -0
- package/lib/__tests__/doctor-executed-surface.test.js +209 -0
- package/lib/__tests__/doctor-hook-drift.test.js +329 -0
- package/lib/__tests__/doctor-machine-mutation.test.js +116 -0
- package/lib/__tests__/doctor-module-drift.test.js +206 -0
- package/lib/__tests__/doctor-module-resolution-parity.test.js +314 -0
- package/lib/__tests__/doctor-path-equivalence.test.js +59 -0
- package/lib/__tests__/doctor-repair-boundary.test.js +52 -0
- package/lib/__tests__/doctor-tui-skill-freshness.test.js +105 -0
- package/lib/__tests__/doctor.test.js +155 -0
- package/lib/__tests__/gate-runner.test.js +19 -3
- package/lib/__tests__/helpers/lib-sandbox.cjs +55 -0
- package/lib/__tests__/init.test.js +44 -17
- package/lib/__tests__/install-skill-full-dir.test.js +151 -0
- package/lib/__tests__/install-skill.test.js +51 -132
- package/lib/__tests__/language-tools-detect.test.js +158 -0
- package/lib/__tests__/phase-transition.test.js +15 -1
- package/lib/__tests__/qoder-delphi-agents.test.js +285 -0
- package/lib/__tests__/shared-paths.test.js +20 -0
- package/lib/__tests__/shared-utils.test.js +69 -0
- package/lib/__tests__/skill-flags.test.js +48 -0
- package/lib/__tests__/ui-detector.test.ts +6 -2
- package/lib/__tests__/uninstall.test.js +20 -0
- package/lib/__tests__/update-hooks.test.js +12 -0
- package/lib/__tests__/update-skill.test.js +7 -4
- package/lib/detect-deps.js +3 -20
- package/lib/doctor-tui.js +22 -5
- package/lib/doctor.js +1178 -31
- package/lib/init.js +184 -33
- package/lib/install-cmd.js +7 -4
- package/lib/install-skill.js +38 -95
- package/lib/language-tools.js +83 -4
- package/lib/mattpocock-license-verdict.json +1 -1
- package/lib/shared-paths.js +5 -2
- package/lib/shared-utils.js +47 -3
- package/lib/uninstall.js +12 -0
- package/lib/update-hooks.js +4 -4
- package/lib/update-skill.js +6 -7
- package/mock-policy/AGENTS.md +4 -4
- package/mutation/AGENTS.md +4 -4
- package/mutation/__tests__/gate-m.test.ts +66 -14
- package/mutation/__tests__/mutmut-runner.test.ts +91 -42
- package/mutation/__tests__/test-filter-parity.test.ts +37 -0
- package/mutation/gate-m.ts +21 -8
- package/package.json +1 -1
- package/plugins/claude-code/.claude-plugin/plugin.json +1 -1
- package/plugins/claude-code/skills/clipboard-vision/__tests__/clipboard-vision-distribution.test.cjs +0 -1
- package/plugins/claude-code/skills/delphi-review/AGENTS.md +4 -4
- package/plugins/claude-code/skills/delphi-review/SKILL.md +98 -14
- package/plugins/claude-code/skills/delphi-review/references/code-walkthrough.md +36 -3
- package/plugins/claude-code/skills/delphi-review/references/requirements.md +6 -6
- package/plugins/claude-code/skills/sprint-flow/AGENTS.md +4 -4
- package/plugins/claude-code/skills/sprint-flow/references/phase-2-design.md +3 -3
- package/plugins/claude-code/skills/test-specification-alignment/AGENTS.md +4 -4
- package/plugins/dsh/README.md +169 -4
- package/plugins/dsh/dsh-overlay.patch.yml +67 -0
- package/plugins/dsh/package.json +1 -1
- package/plugins/dsh/scripts/prepack.cjs +24 -19
- package/plugins/dsh/setup-dsh.ps1 +160 -0
- package/plugins/dsh/skills/clipboard-vision/__tests__/clipboard-vision-distribution.test.cjs +0 -1
- package/plugins/dsh/skills/delphi-review/AGENTS.md +4 -4
- package/plugins/dsh/skills/delphi-review/SKILL.md +98 -14
- package/plugins/dsh/skills/delphi-review/references/code-walkthrough.md +36 -3
- package/plugins/dsh/skills/delphi-review/references/requirements.md +6 -6
- package/plugins/dsh/skills/sprint-flow/AGENTS.md +4 -4
- package/plugins/dsh/skills/sprint-flow/references/phase-2-design.md +3 -3
- package/plugins/dsh/skills/test-specification-alignment/AGENTS.md +4 -4
- package/plugins/dsh/src/command.test.ts +66 -2
- package/plugins/dsh/src/command.ts +36 -14
- package/plugins/dsh/src/contract.test.ts +31 -1
- package/plugins/dsh/src/delphi-config.test.ts +190 -0
- package/plugins/dsh/src/delphi-config.ts +223 -0
- package/plugins/dsh/src/delphi-run.test.ts +181 -0
- package/plugins/dsh/src/delphi-run.ts +129 -0
- package/plugins/dsh/src/delphi.test.ts +222 -0
- package/plugins/dsh/src/delphi.ts +247 -0
- package/plugins/dsh/src/dialect.test.ts +59 -0
- package/plugins/dsh/src/dialect.ts +40 -0
- package/plugins/dsh/src/index.ts +70 -4
- package/plugins/dsh/src/skills.test.ts +26 -19
- package/plugins/opencode/package.json +1 -1
- package/plugins/opencode/skills/clipboard-vision/__tests__/clipboard-vision-distribution.test.cjs +0 -1
- package/plugins/opencode/skills/delphi-review/AGENTS.md +4 -4
- package/plugins/opencode/skills/delphi-review/SKILL.md +98 -14
- package/plugins/opencode/skills/delphi-review/references/code-walkthrough.md +36 -3
- package/plugins/opencode/skills/delphi-review/references/requirements.md +6 -6
- package/plugins/opencode/skills/sprint-flow/AGENTS.md +4 -4
- package/plugins/opencode/skills/sprint-flow/references/phase-2-design.md +3 -3
- package/plugins/opencode/skills/test-specification-alignment/AGENTS.md +4 -4
- package/plugins/qoder/agents/delphi-architecture.md +1 -1
- package/plugins/qoder/agents/delphi-feasibility.md +1 -1
- package/plugins/qoder/agents/delphi-technical.md +1 -1
- package/plugins/qoder/plugin.json +1 -1
- package/plugins/qoder/skills/clipboard-vision/__tests__/clipboard-vision-distribution.test.cjs +0 -1
- package/plugins/qoder/skills/delphi-review/AGENTS.md +4 -4
- package/plugins/qoder/skills/delphi-review/SKILL.md +98 -14
- package/plugins/qoder/skills/delphi-review/references/code-walkthrough.md +36 -3
- package/plugins/qoder/skills/delphi-review/references/requirements.md +6 -6
- package/plugins/qoder/skills/sprint-flow/AGENTS.md +4 -4
- package/plugins/qoder/skills/sprint-flow/references/phase-2-design.md +3 -3
- package/plugins/qoder/skills/test-specification-alignment/AGENTS.md +4 -4
- package/principles/AGENTS.md +9 -8
- package/principles/__tests__/adapter-metrics.test.ts +292 -0
- package/principles/__tests__/analyzer.test.ts +29 -6
- package/principles/__tests__/baseline-prune-timeout.test.ts +71 -0
- package/principles/__tests__/baseline-prune.test.ts +338 -0
- package/principles/__tests__/boy-scout-init-baseline.test.ts +167 -0
- package/principles/__tests__/boy-scout-integration.test.ts +97 -6
- package/principles/__tests__/boy-scout.test.ts +253 -7
- package/principles/__tests__/config-enforcement.test.ts +469 -0
- package/principles/__tests__/config-git-timeout.test.ts +66 -0
- package/principles/__tests__/direct-execution.test.ts +187 -0
- package/principles/__tests__/index.test.ts +16 -3
- package/principles/__tests__/warnings-baseline-budgets.test.ts +74 -0
- package/principles/adapters/typescript.ts +613 -13
- package/principles/analyzer.ts +22 -0
- package/principles/baseline-prune.ts +217 -0
- package/principles/boy-scout.ts +481 -51
- package/principles/config.ts +362 -54
- package/principles/direct-execution.ts +60 -0
- package/principles/index.ts +40 -17
- package/principles/rules/__tests__/clean-code/many-exports.test.ts +16 -10
- package/principles/rules/clean-code/code-duplication.ts +8 -8
- package/principles/rules/clean-code/deep-nesting.ts +8 -8
- package/principles/rules/clean-code/god-class.ts +8 -8
- package/principles/rules/clean-code/large-file.ts +15 -11
- package/principles/rules/clean-code/long-function.ts +8 -8
- package/principles/rules/clean-code/magic-numbers.ts +10 -7
- package/principles/rules/clean-code/many-exports.ts +7 -7
- package/principles/rules/clean-code/missing-error-handling.ts +5 -5
- package/principles/rules/clean-code/too-many-params.ts +8 -8
- package/principles/rules/clean-code/unused-imports.ts +5 -5
- package/principles/rules/solid/dip.ts +18 -8
- package/principles/rules/solid/isp.ts +9 -8
- package/principles/rules/solid/lsp.ts +6 -6
- package/principles/rules/solid/ocp.ts +6 -5
- package/principles/rules/solid/srp.ts +7 -8
- package/principles/types.ts +15 -0
- package/scripts/__tests__/sync-package-content.test.js +28 -0
- package/scripts/delphi-external-review.cjs +193 -10
- package/scripts/sync-package-content.js +7 -4
- package/skills/clipboard-vision/__tests__/clipboard-vision-distribution.test.cjs +0 -1
- package/skills/delphi-review/AGENTS.md +4 -4
- package/skills/delphi-review/SKILL.md +98 -14
- package/skills/delphi-review/references/code-walkthrough.md +36 -3
- package/skills/delphi-review/references/requirements.md +6 -6
- package/skills/sprint-flow/AGENTS.md +4 -4
- package/skills/sprint-flow/references/phase-2-design.md +3 -3
- package/skills/test-specification-alignment/AGENTS.md +4 -4
- package/lib/__tests__/download-skill.test.js +0 -281
- package/lib/download-skill.js +0 -96
|
@@ -18,9 +18,16 @@ function checkNodeVersion() {
|
|
|
18
18
|
// ── Argument parsing ───────────────────────────────────────────────────
|
|
19
19
|
const VALID_EXPERTS = ['architecture', 'technical', 'feasibility'];
|
|
20
20
|
const VALID_MODES = ['design', 'code-walkthrough', 'requirements'];
|
|
21
|
+
const DEFAULT_TIMEOUT_MS = 30000;
|
|
22
|
+
|
|
23
|
+
// Shared by CLI --timeout-ms and provider timeout_ms validation.
|
|
24
|
+
function isValidTimeoutMs(value) {
|
|
25
|
+
return Number.isInteger(value) && value >= 1000 && value <= 600000;
|
|
26
|
+
}
|
|
21
27
|
|
|
22
28
|
function parseArgs(argv) {
|
|
23
29
|
const args = {};
|
|
30
|
+
let timeoutMsRaw;
|
|
24
31
|
for (let i = 0; i < argv.length; i++) {
|
|
25
32
|
switch (argv[i]) {
|
|
26
33
|
case '--expert': args.expert = argv[++i]; break;
|
|
@@ -31,6 +38,7 @@ function parseArgs(argv) {
|
|
|
31
38
|
case '--mode': args.mode = argv[++i]; break;
|
|
32
39
|
case '--profile': args.profile = argv[++i]; break;
|
|
33
40
|
case '--other-experts-file': args.otherExpertsFile = argv[++i]; break;
|
|
41
|
+
case '--timeout-ms': timeoutMsRaw = argv[++i]; break;
|
|
34
42
|
case '--fallback-local': args.fallbackLocal = true; break;
|
|
35
43
|
default: break;
|
|
36
44
|
}
|
|
@@ -60,6 +68,16 @@ function parseArgs(argv) {
|
|
|
60
68
|
process.exit(1);
|
|
61
69
|
}
|
|
62
70
|
|
|
71
|
+
// Validate --timeout-ms: integer within [1000, 600000]
|
|
72
|
+
if (timeoutMsRaw !== undefined) {
|
|
73
|
+
const parsed = /^\d+$/.test(timeoutMsRaw) ? Number(timeoutMsRaw) : NaN;
|
|
74
|
+
if (!isValidTimeoutMs(parsed)) {
|
|
75
|
+
console.error('[delphi-review] ERROR: --timeout-ms must be an integer between 1000 and 600000.');
|
|
76
|
+
process.exit(1);
|
|
77
|
+
}
|
|
78
|
+
args.timeoutMs = parsed;
|
|
79
|
+
}
|
|
80
|
+
|
|
63
81
|
// Defaults
|
|
64
82
|
if (!args.mode) args.mode = 'design';
|
|
65
83
|
if (!VALID_MODES.includes(args.mode)) {
|
|
@@ -71,6 +89,28 @@ function parseArgs(argv) {
|
|
|
71
89
|
}
|
|
72
90
|
|
|
73
91
|
// ── Config reading ─────────────────────────────────────────────────────
|
|
92
|
+
// Provider fields used to be checked only inside the per-expert loop, so a
|
|
93
|
+
// malformed provider nobody currently points an expert at survived silently and
|
|
94
|
+
// only surfaced at runtime (a bad timeout_ms fell back to the default). The
|
|
95
|
+
// whole profile is checked here instead, at the system boundary, naming the
|
|
96
|
+
// provider so the fix is obvious (#429).
|
|
97
|
+
function validateProviders(providers, profileName) {
|
|
98
|
+
for (const [name, provider] of Object.entries(providers)) {
|
|
99
|
+
const providerPath = `profiles.${profileName}.providers.${name}`;
|
|
100
|
+
const fail = (detail) => {
|
|
101
|
+
console.error(`[delphi-review] ERROR: ${providerPath} ${detail}`);
|
|
102
|
+
process.exit(1);
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
if (!isPlainObject(provider)) fail('must be an object.');
|
|
106
|
+
if (typeof provider.base_url !== 'string' || provider.base_url.trim() === '') fail('must define a non-empty base_url.');
|
|
107
|
+
if (typeof provider.api_key !== 'string' || provider.api_key.trim() === '') fail('must define a non-empty api_key.');
|
|
108
|
+
if (provider.timeout_ms !== undefined && !isValidTimeoutMs(provider.timeout_ms)) {
|
|
109
|
+
fail('timeout_ms must be an integer between 1000 and 600000.');
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
74
114
|
function readConfig(configPath, profileOverride) {
|
|
75
115
|
if (!fs.existsSync(configPath)) {
|
|
76
116
|
console.error(`[delphi-review] ERROR: Config file not found: ${configPath}`);
|
|
@@ -97,6 +137,7 @@ function readConfig(configPath, profileOverride) {
|
|
|
97
137
|
|
|
98
138
|
// Resolve ${ENV_VAR} references in provider api_key fields
|
|
99
139
|
const providers = profile.providers || {};
|
|
140
|
+
validateProviders(providers, profileName);
|
|
100
141
|
for (const [name, prov] of Object.entries(providers)) {
|
|
101
142
|
if (prov.api_key && prov.api_key.startsWith('${') && prov.api_key.endsWith('}')) {
|
|
102
143
|
const envName = prov.api_key.slice(2, -1);
|
|
@@ -104,7 +145,11 @@ function readConfig(configPath, profileOverride) {
|
|
|
104
145
|
if (envVal) {
|
|
105
146
|
prov.api_key = envVal;
|
|
106
147
|
} else {
|
|
107
|
-
|
|
148
|
+
// A missing variable used to be a WARNING, after which the runner sent the
|
|
149
|
+
// literal `Bearer ${NAME}` and reported the 401 as "check API key" — the
|
|
150
|
+
// diagnosis pointed at a wrong key while the real cause was the shell.
|
|
151
|
+
console.error(`[delphi-review] ERROR: Environment variable ${envName} is not set (profiles.${profileName}.providers.${name}.api_key). Export it or point that provider at a literal key.`);
|
|
152
|
+
process.exit(1);
|
|
108
153
|
}
|
|
109
154
|
}
|
|
110
155
|
}
|
|
@@ -201,6 +246,12 @@ function validateDistinctModels(experts, providers, consensus = {}) {
|
|
|
201
246
|
if (typeof provider.api_key !== 'string' || provider.api_key.trim() === '') {
|
|
202
247
|
return { valid: false, reason: `Expert ${role} provider must define a non-empty api_key.` };
|
|
203
248
|
}
|
|
249
|
+
if (provider.timeout_ms !== undefined && !isValidTimeoutMs(provider.timeout_ms)) {
|
|
250
|
+
return {
|
|
251
|
+
valid: false,
|
|
252
|
+
reason: `Expert ${role} provider ${expert.provider} timeout_ms must be an integer between 1000 and 600000.`,
|
|
253
|
+
};
|
|
254
|
+
}
|
|
204
255
|
}
|
|
205
256
|
|
|
206
257
|
if (consensus.cross_provider_required === true) {
|
|
@@ -257,7 +308,7 @@ const SYSTEM_PROMPTS = {
|
|
|
257
308
|
4) 架构演进性 — 是否为未来扩展留有空间
|
|
258
309
|
5) 技术选型合理性 — 技术栈选择是否有充分依据
|
|
259
310
|
|
|
260
|
-
输出要求:返回结构化 JSON,包含 verdict (APPROVED/REQUEST_CHANGES/REJECTED)、confidence (1-10)、critical_issues、major_concerns、minor_concerns
|
|
311
|
+
输出要求:返回结构化 JSON,包含 verdict (APPROVED/REQUEST_CHANGES/REJECTED)、confidence (1-10,整数,10 为最高)、summary (字符串)。critical_issues、major_concerns、minor_concerns 必须是字符串数组(string[]),每项是一句自包含的完整描述(可加 "CI-01: " 前缀,但整体必须是字符串,不是对象)。不要把问题写成 {id, title, description} 对象。`,
|
|
261
312
|
|
|
262
313
|
technical: `你是技术实现评审专家(Delphi Method - Technical Expert)。
|
|
263
314
|
|
|
@@ -268,7 +319,7 @@ const SYSTEM_PROMPTS = {
|
|
|
268
319
|
4) 性能影响 — 是否有性能瓶颈或资源泄漏风险
|
|
269
320
|
5) 可测试性 — 代码是否易于编写单元测试
|
|
270
321
|
|
|
271
|
-
输出要求:返回结构化 JSON,包含 verdict (APPROVED/REQUEST_CHANGES/REJECTED)、confidence (1-10)、critical_issues、major_concerns、minor_concerns
|
|
322
|
+
输出要求:返回结构化 JSON,包含 verdict (APPROVED/REQUEST_CHANGES/REJECTED)、confidence (1-10,整数,10 为最高)、summary (字符串)。critical_issues、major_concerns、minor_concerns 必须是字符串数组(string[]),每项是一句自包含的完整描述(可加 "MC-01: " 前缀,但整体必须是字符串,不是对象)。不要把问题写成 {id, title, description} 对象。`,
|
|
272
323
|
|
|
273
324
|
feasibility: `你是可行性分析专家(Delphi Method - Feasibility Expert)。
|
|
274
325
|
|
|
@@ -279,7 +330,7 @@ const SYSTEM_PROMPTS = {
|
|
|
279
330
|
4) 替代方案 — 是否有更简单或更可靠的替代方案
|
|
280
331
|
5) 回滚策略 — 如果实施失败,是否有退路
|
|
281
332
|
|
|
282
|
-
输出要求:返回结构化 JSON,包含 verdict (APPROVED/REQUEST_CHANGES/REJECTED)、confidence (1-10)、critical_issues、major_concerns、minor_concerns
|
|
333
|
+
输出要求:返回结构化 JSON,包含 verdict (APPROVED/REQUEST_CHANGES/REJECTED)、confidence (1-10,整数,10 为最高)、summary (字符串)。critical_issues、major_concerns、minor_concerns 必须是字符串数组(string[]),每项是一句自包含的完整描述(可加 "FC-01: " 前缀,但整体必须是字符串,不是对象)。不要把问题写成 {id, title, description} 对象。`,
|
|
283
334
|
};
|
|
284
335
|
|
|
285
336
|
const MODE_FOCUS_PROMPTS = {
|
|
@@ -316,6 +367,60 @@ function resolveInputContent(args) {
|
|
|
316
367
|
return args.input || '';
|
|
317
368
|
}
|
|
318
369
|
|
|
370
|
+
// ── Timeout resolution ─────────────────────────────────────────────────
|
|
371
|
+
// CLI --timeout-ms > provider timeout_ms > default (30000, unchanged behavior).
|
|
372
|
+
function resolveTimeoutMs(args, providerConfig) {
|
|
373
|
+
if (args && isValidTimeoutMs(args.timeoutMs)) return args.timeoutMs;
|
|
374
|
+
if (providerConfig && isValidTimeoutMs(providerConfig.timeout_ms)) return providerConfig.timeout_ms;
|
|
375
|
+
return DEFAULT_TIMEOUT_MS;
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
// ── Prompt budget ──────────────────────────────────────────────────────
|
|
379
|
+
// Measured on the whalecloud gateway during the #457 walkthrough: a 40 KB prompt
|
|
380
|
+
// answered normally, a 50 KB one killed the seat with no usable error, and a
|
|
381
|
+
// dead expert seat blocks the whole review. The ceiling is therefore checked
|
|
382
|
+
// before the request is made, where the message can say what to narrow.
|
|
383
|
+
const DEFAULT_PROMPT_BUDGET_BYTES = 40000;
|
|
384
|
+
|
|
385
|
+
// A seat-level override (`max_prompt_bytes` on the expert entry) exists because
|
|
386
|
+
// the ceiling is a property of the model behind the seat, not of the runner.
|
|
387
|
+
// Values that cannot be honoured fall back to the default rather than disabling
|
|
388
|
+
// the guard -- `max_prompt_bytes: 0` reads as "no limit" and is not.
|
|
389
|
+
function resolvePromptBudgetBytes(expertConfig) {
|
|
390
|
+
const configured = expertConfig && expertConfig.max_prompt_bytes;
|
|
391
|
+
if (typeof configured === 'number' && Number.isFinite(configured) && configured > 0) {
|
|
392
|
+
return Math.trunc(configured);
|
|
393
|
+
}
|
|
394
|
+
if (configured !== undefined) {
|
|
395
|
+
// Silently falling back is how a raised ceiling looks like a still-broken
|
|
396
|
+
// guard: the operator edits the config, the run still refuses, and nothing
|
|
397
|
+
// says the value never took effect.
|
|
398
|
+
console.error(
|
|
399
|
+
`[delphi-review] WARNING: max_prompt_bytes=${JSON.stringify(configured)} cannot be honoured ` +
|
|
400
|
+
`(needs a finite number > 0); using the ${DEFAULT_PROMPT_BUDGET_BYTES} byte default instead.`
|
|
401
|
+
);
|
|
402
|
+
}
|
|
403
|
+
return DEFAULT_PROMPT_BUDGET_BYTES;
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
// Bytes, never `String.length`: review content is Chinese prose, and a code-unit
|
|
407
|
+
// count understates what the gateway actually receives by up to 3x.
|
|
408
|
+
function checkPromptBudget({ systemPrompt, userPrompt, maxBytes }) {
|
|
409
|
+
const bytes = Buffer.byteLength(String(systemPrompt ?? ''), 'utf8')
|
|
410
|
+
+ Buffer.byteLength(String(userPrompt ?? ''), 'utf8');
|
|
411
|
+
return { ok: bytes <= maxBytes, bytes, maxBytes };
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
function describePromptBudgetFailure({ bytes, maxBytes, expert }) {
|
|
415
|
+
return (
|
|
416
|
+
`Prompt for expert "${expert}" is ${bytes} bytes, over the ${maxBytes} byte budget. ` +
|
|
417
|
+
'The gateway drops oversized requests without a diagnosable error, so this run ' +
|
|
418
|
+
'refuses to send it. Narrow the review range (fewer commits or files, or exclude ' +
|
|
419
|
+
'vendored/lockfile content), or raise the seat\'s `max_prompt_bytes` in ' +
|
|
420
|
+
'.delphi-config.json if this model is known to accept larger requests.'
|
|
421
|
+
);
|
|
422
|
+
}
|
|
423
|
+
|
|
319
424
|
// ── API call ───────────────────────────────────────────────────────────
|
|
320
425
|
async function callModelAPI(providerConfig, model, systemPrompt, userPrompt, options = {}) {
|
|
321
426
|
const url = `${providerConfig.base_url.replace(/\/$/, '')}/chat/completions`;
|
|
@@ -330,7 +435,9 @@ async function callModelAPI(providerConfig, model, systemPrompt, userPrompt, opt
|
|
|
330
435
|
response_format: { type: 'json_object' },
|
|
331
436
|
};
|
|
332
437
|
|
|
333
|
-
|
|
438
|
+
// Same constant as the runner default: a second literal here silently
|
|
439
|
+
// diverges whenever the default is retuned (#429).
|
|
440
|
+
const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
334
441
|
const controller = new AbortController();
|
|
335
442
|
const timeout = setTimeout(() => controller.abort(), timeoutMs);
|
|
336
443
|
let responseReceived = false;
|
|
@@ -352,6 +459,29 @@ async function callModelAPI(providerConfig, model, systemPrompt, userPrompt, opt
|
|
|
352
459
|
}
|
|
353
460
|
|
|
354
461
|
if (response.status === 429) {
|
|
462
|
+
// A 429 is not always a transient rate limit. WhaleCloud returns 429 with
|
|
463
|
+
// `type: "budget_exceeded"` in two distinct non-retryable cases, which a
|
|
464
|
+
// blind retry cannot clear and would otherwise burn two round-trips before
|
|
465
|
+
// failing:
|
|
466
|
+
// - `error.param === "category_quota"`: the account has no ECONOMY/STANDARD
|
|
467
|
+
// quota configured for the model ("需要专项额度...尚未配置") — needs an
|
|
468
|
+
// admin to add it.
|
|
469
|
+
// - `error.param === "cost_quota"`: the account's daily spending cap is
|
|
470
|
+
// exhausted ("今日额度已耗尽...限额: 100.00元") — resets the next day.
|
|
471
|
+
// Surface which kind it is so the caller knows whether to wait or to request
|
|
472
|
+
// an admin change, rather than treating both as a generic rate limit.
|
|
473
|
+
let quotaType = '';
|
|
474
|
+
try {
|
|
475
|
+
const body = await response.json();
|
|
476
|
+
if (body && body.error && body.error.type === 'budget_exceeded') {
|
|
477
|
+
quotaType = body.error.param === 'cost_quota' ? 'daily budget exhausted' : 'quota not configured';
|
|
478
|
+
}
|
|
479
|
+
} catch {
|
|
480
|
+
// body is not JSON; fall through to the generic rate-limit handling
|
|
481
|
+
}
|
|
482
|
+
if (quotaType) {
|
|
483
|
+
return { error: true, retryable: false, message: `WhaleCloud budget_exceeded: ${quotaType} for this model.` };
|
|
484
|
+
}
|
|
355
485
|
return { error: true, retryable: true, message: 'Rate limit exceeded (429).' };
|
|
356
486
|
}
|
|
357
487
|
|
|
@@ -374,18 +504,33 @@ async function callModelAPI(providerConfig, model, systemPrompt, userPrompt, opt
|
|
|
374
504
|
return { error: true, message: 'Invalid response from model.' };
|
|
375
505
|
}
|
|
376
506
|
|
|
507
|
+
// The gateway must echo which model actually served the request. Writing
|
|
508
|
+
// `null` here used to be tolerated downstream, which let a run that never
|
|
509
|
+
// resolved a model count as a successful expert call (#423). Fail at the
|
|
510
|
+
// source instead, so the evidence never records an unverifiable expert.
|
|
511
|
+
const resolvedModel = typeof data.model === 'string' ? data.model.trim() : '';
|
|
512
|
+
if (resolvedModel === '') {
|
|
513
|
+
return {
|
|
514
|
+
error: true,
|
|
515
|
+
message: 'Gateway response did not include a resolved model id; cannot prove which model ran.',
|
|
516
|
+
};
|
|
517
|
+
}
|
|
518
|
+
|
|
377
519
|
return {
|
|
378
520
|
success: true,
|
|
379
521
|
content: content.trim(),
|
|
380
|
-
resolved_model:
|
|
522
|
+
resolved_model: resolvedModel,
|
|
381
523
|
};
|
|
382
524
|
} catch (err) {
|
|
383
525
|
if (err instanceof Error && err.name === 'AbortError') {
|
|
384
|
-
return { error: true, retryable: true, message: `Request timed out (${timeoutMs}ms).` };
|
|
526
|
+
return { error: true, retryable: true, message: `Request timed out (${timeoutMs}ms). Raise --timeout-ms or the provider's timeout_ms.` };
|
|
385
527
|
}
|
|
386
528
|
if (responseReceived) {
|
|
387
529
|
return { error: true, message: 'Invalid response from model.' };
|
|
388
530
|
}
|
|
531
|
+
// Deliberately do NOT surface the underlying cause here. Provider errors can
|
|
532
|
+
// carry mutable metadata (tokens, keys, hostnames) that must not leak into the
|
|
533
|
+
// evidence file; the test suite asserts this redaction. Keep the generic form.
|
|
389
534
|
return { error: true, message: 'Network error.' };
|
|
390
535
|
} finally {
|
|
391
536
|
clearTimeout(timeout);
|
|
@@ -432,6 +577,11 @@ function buildReviewOutput(verdict, args, provenance) {
|
|
|
432
577
|
model_used: `${provenance.provider}/${provenance.requested_model}`,
|
|
433
578
|
requested_model: provenance.requested_model,
|
|
434
579
|
resolved_model: provenance.resolved_model,
|
|
580
|
+
// Provenance marker required by Gate MW (#423). This script talks to an
|
|
581
|
+
// external gateway, so every record it emits is `external`. The field is
|
|
582
|
+
// explicit rather than inferred: the validator rejects a missing value
|
|
583
|
+
// instead of defaulting it, which is what makes forgery costly.
|
|
584
|
+
channel: provenance.channel,
|
|
435
585
|
round: args.round,
|
|
436
586
|
mode: args.mode,
|
|
437
587
|
};
|
|
@@ -442,10 +592,10 @@ function buildReviewOutput(verdict, args, provenance) {
|
|
|
442
592
|
}
|
|
443
593
|
|
|
444
594
|
// ── Retry logic ────────────────────────────────────────────────────────
|
|
445
|
-
async function callWithRetry(providerConfig, model, systemPrompt, userPrompt, maxRetries = 2) {
|
|
595
|
+
async function callWithRetry(providerConfig, model, systemPrompt, userPrompt, maxRetries = 2, timeoutMs = DEFAULT_TIMEOUT_MS) {
|
|
446
596
|
let lastResult;
|
|
447
597
|
for (let attempt = 0; attempt <= maxRetries; attempt++) {
|
|
448
|
-
const result = await callModelAPI(providerConfig, model, systemPrompt, userPrompt);
|
|
598
|
+
const result = await callModelAPI(providerConfig, model, systemPrompt, userPrompt, { timeoutMs });
|
|
449
599
|
|
|
450
600
|
if (result.success) return result;
|
|
451
601
|
if (!result.retryable) return result;
|
|
@@ -507,8 +657,32 @@ async function main() {
|
|
|
507
657
|
const systemPrompt = buildSystemPrompt(args.expert, args.mode);
|
|
508
658
|
const userPrompt = buildUserPrompt(reviewContent, otherExpertsContent, args.round);
|
|
509
659
|
|
|
660
|
+
// Refuse before the request, not after the gateway kills it.
|
|
661
|
+
const budget = checkPromptBudget({
|
|
662
|
+
systemPrompt,
|
|
663
|
+
userPrompt,
|
|
664
|
+
maxBytes: resolvePromptBudgetBytes(expertConfig),
|
|
665
|
+
});
|
|
666
|
+
if (!budget.ok) {
|
|
667
|
+
const message = describePromptBudgetFailure({ ...budget, expert: args.expert });
|
|
668
|
+
console.error(`[delphi-review] ERROR: ${message}`);
|
|
669
|
+
// Same stdout contract as an API failure: an orchestrator parsing this stream
|
|
670
|
+
// must be able to tell "we never asked" from "the model answered wrongly".
|
|
671
|
+
console.log(JSON.stringify({
|
|
672
|
+
error: true,
|
|
673
|
+
error_type: 'prompt_budget_exceeded',
|
|
674
|
+
expert_role: args.expert,
|
|
675
|
+
message,
|
|
676
|
+
bytes: budget.bytes,
|
|
677
|
+
max_bytes: budget.maxBytes,
|
|
678
|
+
retryable: false,
|
|
679
|
+
}));
|
|
680
|
+
process.exit(1);
|
|
681
|
+
}
|
|
682
|
+
|
|
510
683
|
// Call API with retry
|
|
511
|
-
const
|
|
684
|
+
const timeoutMs = resolveTimeoutMs(args, provider);
|
|
685
|
+
const result = await callWithRetry(provider, expertConfig.model, systemPrompt, userPrompt, 2, timeoutMs);
|
|
512
686
|
|
|
513
687
|
if (result.error) {
|
|
514
688
|
const errorOutput = {
|
|
@@ -533,6 +707,9 @@ async function main() {
|
|
|
533
707
|
provider: expertConfig.provider,
|
|
534
708
|
requested_model: expertConfig.model,
|
|
535
709
|
resolved_model: result.resolved_model,
|
|
710
|
+
// This runner only ever reaches an external provider; local/offline
|
|
711
|
+
// fallbacks are recorded by a different path (#423).
|
|
712
|
+
channel: 'external',
|
|
536
713
|
});
|
|
537
714
|
|
|
538
715
|
console.log(JSON.stringify(output, null, 2));
|
|
@@ -549,6 +726,12 @@ if (require.main !== module) {
|
|
|
549
726
|
buildSystemPrompt,
|
|
550
727
|
buildUserPrompt,
|
|
551
728
|
resolveInputContent,
|
|
729
|
+
resolveTimeoutMs,
|
|
730
|
+
DEFAULT_TIMEOUT_MS,
|
|
731
|
+
resolvePromptBudgetBytes,
|
|
732
|
+
checkPromptBudget,
|
|
733
|
+
describePromptBudgetFailure,
|
|
734
|
+
DEFAULT_PROMPT_BUDGET_BYTES,
|
|
552
735
|
checkNodeVersion,
|
|
553
736
|
callModelAPI,
|
|
554
737
|
callWithRetry,
|
|
@@ -188,7 +188,7 @@ function syncHooks() {
|
|
|
188
188
|
fs.copyFileSync(adapterCommonSrc, path.join(PKG_ROOT, 'adapter-common.sh'));
|
|
189
189
|
}
|
|
190
190
|
|
|
191
|
-
const HOOK_FILES = ['pre-commit', 'pre-push', 'adapter-common.sh'];
|
|
191
|
+
const HOOK_FILES = ['pre-commit', 'pre-push', 'post-merge', 'adapter-common.sh'];
|
|
192
192
|
let copied = 0;
|
|
193
193
|
|
|
194
194
|
for (const name of HOOK_FILES) {
|
|
@@ -477,8 +477,11 @@ function main() {
|
|
|
477
477
|
// tui-plugin removed in v0.13.0 — validation skipped.
|
|
478
478
|
}
|
|
479
479
|
|
|
480
|
-
if (require.main
|
|
480
|
+
if (require.main === module) {
|
|
481
|
+
main();
|
|
482
|
+
} else {
|
|
483
|
+
// Requiring this script must not mutate the work tree: a test that only wants
|
|
484
|
+
// checkDocsDrift/checkAdapterDrift used to trigger a full mirror sync at
|
|
485
|
+
// require-time, which vitest-worktree-guard rightly reported as pollution.
|
|
481
486
|
module.exports = { checkDocsDrift, checkAdapterDrift };
|
|
482
487
|
}
|
|
483
|
-
|
|
484
|
-
main();
|
|
@@ -19,7 +19,6 @@ describe('clipboard-vision distribution', () => {
|
|
|
19
19
|
'scripts/build-plugin.mjs',
|
|
20
20
|
'scripts/build-plugin.sh',
|
|
21
21
|
'scripts/test-plugins.mjs',
|
|
22
|
-
'scripts/test-plugins.sh',
|
|
23
22
|
'src/npm-package/scripts/sync-package-content.js',
|
|
24
23
|
'plugins/opencode/scripts/prepack.cjs',
|
|
25
24
|
'src/npm-package/plugins/opencode/scripts/prepack.cjs',
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
# SKILLS/DELPHI-REVIEW KNOWLEDGE BASE
|
|
2
2
|
|
|
3
|
-
**Generated:** 2026-
|
|
4
|
-
**Commit:**
|
|
5
|
-
**Branch:**
|
|
6
|
-
**Version:** 0.
|
|
3
|
+
**Generated:** 2026-10-07
|
|
4
|
+
**Commit:** ff55f35b
|
|
5
|
+
**Branch:** fix/batch-defects-20261007
|
|
6
|
+
**Version:** 0.20.0.0
|
|
7
7
|
|
|
8
8
|
## OVERVIEW
|
|
9
9
|
Delphi Consensus Review — multi-round anonymous expert review (≥90% threshold, exactly 3 experts with distinct executable model IDs). Supports design, requirements, and code-walkthrough modes.
|
|
@@ -249,7 +249,7 @@ tools_denied:
|
|
|
249
249
|
|
|
250
250
|
1. **Step 0: Input Validation** — Check input contains reviewable content (design doc/code/spec/diff). Empty input → `[DelphiReview:BLOCKED]`.
|
|
251
251
|
2. **Round 1: Anonymous Independent Review** — Invoke architecture, technical, and feasibility independently without exposing their opinions to one another. Every successful result must use `result_type=delphi_expert_result`.
|
|
252
|
-
3. **Execution Verification** — Verify that all three results succeeded and that their `requested_model` values are three distinct trimmed IDs. A local fallback (`provider: local`) is not an executed expert and cannot satisfy this check; a local hosted endpoint configured as an ordinary callable provider can.
|
|
252
|
+
3. **Execution Verification** — Verify that all three results succeeded and that their `requested_model` values are three distinct trimmed IDs, confirmed against the platform's execution record rather than expert self-report alone (OpenCode: the provider call log; Qoder: the `model` field of `model.request.started` events under `~/.qoder/logs/sessions/<project>/<sessionId>/segments/*.jsonl` — the `--model` argv in `~/.qoder/logs/runs/*/manifest.json` is the session model and proves nothing about subagents). A local fallback (`provider: local`) is not an executed expert and cannot satisfy this check; a local hosted endpoint configured as an ordinary callable provider can. Each expert record must also carry an explicit `channel` of either `external` or `local`; the value is a whitelist and a missing or unknown value fails the gate (it is never defaulted, since defaulting would let a forged record pass by simply omitting the field). Where the platform pins subagents to the session model (current Qoder Custom Agent dispatch, see the Qoder section below), the check cannot pass on built-in models at all and the external provider path is required.
|
|
253
253
|
4. **Consensus Check** — Aggregate all three successful expert results. Consensus >=90% AND all APPROVED → complete. One expert result is never global approval.
|
|
254
254
|
5. **Rounds 2-5: Exchange and Final Positions** — If needed, expose prior aggregate evidence, re-evaluate, and stop at the first approved consensus or after five rounds.
|
|
255
255
|
6. **Failure Handling** — Any expert failure, missing result, duplicate model ID, or unverifiable execution blocks the review. Do not substitute a local fallback or silently reduce the expert count.
|
|
@@ -327,27 +327,111 @@ Each round MUST output a structured round marker:
|
|
|
327
327
|
- ✅ 三个角色可以共享一个 provider 和 token 计划
|
|
328
328
|
- ❌ `provider: local` 的特殊 fallback 不能计为一次成功执行
|
|
329
329
|
|
|
330
|
-
#### Qoder
|
|
330
|
+
#### Qoder 平台(Custom Agent 模式 — 只能做到同模型三角色)
|
|
331
331
|
|
|
332
|
-
Qoder
|
|
332
|
+
Qoder 的内置模型**没有本地推理端点**:推理请求由 Qoder 客户端经服务端代理发出,凭证是一次性 job token。所以不能照搬 OpenCode 的 `base_url + api_key` 直连方式;Qoder 侧内置模型的唯一入口是 **Custom Agent** —— 每个专家一个独立 agent 文件,frontmatter 用 `model` 字段声明模型。
|
|
333
333
|
|
|
334
|
-
|
|
335
|
-
|------|-----------|------|-------------|
|
|
336
|
-
| Architecture (A) | `.qoder/agents/delphi-architecture.md` | Qwen3.7-Max | 0.5× |
|
|
337
|
-
| Technical (B) | `.qoder/agents/delphi-technical.md` | GLM-5.2 | 0.6× |
|
|
338
|
-
| Feasibility (C) | `.qoder/agents/delphi-feasibility.md` | DeepSeek-V4-Pro | 0.5× |
|
|
334
|
+
**契约结论(先说结论)**:这条路径**不满足**"三个不同可执行模型 ID"契约。2026-09-23 做过一次排除混杂因素的复测:先把 `~/.qoder/agents/delphi-*.md` 三份文件绑成三个不同的 0.1× 内置模型(`qfmodel` / `dfmodel` / `gfmodel`;架构角与会话模型同名,所以真正有判别力的是技术角与可行性角),**再**新开会话——新 CLI 进程,`session.config.loaded` 记录 `model: "qfmodel"`,注册表加载的必然是已修好的绑定——然后只派发三个 subagent,各回一句话、不调工具。结果:三个独立的 `is_subagent: true` turn,`turn.started` / `model.request.started` / `model.response.completed` 三处的 `model` 全为会话模型 `qfmodel`,`dfmodel` / `gfmodel` 出现 0 次;同时三份回复分别命中架构 / 技术 / 可行性视角,说明角色提示词确实生效、agent 也确实被加载。即 `subagent_type` 派发路径采纳 Custom Agent 的提示词,但**丢弃**它的 `model` 绑定。
|
|
339
335
|
|
|
340
|
-
|
|
336
|
+
因此 Qoder 原生模式只能作为**同模型、三角色(persona)的自查**,其共识比例不能当作多模型交叉验证的证据,也不应用作 Gate MW 的三模型凭据。需要契约合规的 Delphi 时,按上面的外部 provider 方式配置 `.delphi-config.json`(Qoder 与 OpenCode 同用,条件是模型确有可调端点)。
|
|
341
337
|
|
|
342
|
-
|
|
338
|
+
| 角色 | Agent 文件 | 绑定的 modelId | 实际执行模型 |
|
|
339
|
+
|------|-----------|---------------|-------------|
|
|
340
|
+
| Architecture (A) | `.qoder/agents/delphi-architecture.md` | `qfmodel` | 会话模型 |
|
|
341
|
+
| Technical (B) | `.qoder/agents/delphi-technical.md` | `gfmodel` | 会话模型 |
|
|
342
|
+
| Feasibility (C) | `.qoder/agents/delphi-feasibility.md` | `dfmodel` | 会话模型 |
|
|
343
|
+
|
|
344
|
+
**安装位置**(`xp-gate init` 自动部署,已存在的文件不覆盖):
|
|
345
|
+
- 项目级:`<project>/.qoder/agents/`(`xp-gate init`)
|
|
346
|
+
- 用户级:`~/.qoder/agents/`(`xp-gate init --global`)
|
|
347
|
+
|
|
348
|
+
**model 字段格式**:仍须写成 `"[DisplayName](modelId)"`。裸名字或已下线 ID 不会报错,会被平台静默忽略。`xp-gate init` 会审计已部署的 `delphi-*.md`,格式非法或 modelId 与本版本模板不一致时点名文件并给出"删除 → 重跑 `init` → 重启会话"提示;因为部署语义是"已存在不覆盖",从 #417 之前的版本升级必须手工删旧文件再重跑 `init`。注意:修好绑定也只是让角色声明规范,并不改变上表的执行模型结论。
|
|
349
|
+
|
|
350
|
+
**执行方式**:用 Agent tool 并行派发 3 个 subagent,`subagent_type` 分别取 `delphi-architecture`、`delphi-technical`、`delphi-feasibility`。**新建或修改 agent 文件后必须重开会话**——注册表只在会话启动时加载,否则报 `Unknown agent type`。
|
|
351
|
+
|
|
352
|
+
**客观验证(不要采信专家自述)**:`~/.qoder/logs/runs/*/manifest.json` 的 argv `--model` 记录的是**会话/进程启动模型**,对 subagent 没有判别力(历史上所有运行都是同一个 ID)。真正的证据在按会话分段的日志里:
|
|
353
|
+
|
|
354
|
+
```bash
|
|
355
|
+
grep '"type":"model.request.started"' ~/.qoder/logs/sessions/<project>/<sessionId>/segments/*.jsonl \
|
|
356
|
+
| grep -oE '"model":"[^"]*"' | sort | uniq -c
|
|
357
|
+
```
|
|
358
|
+
|
|
359
|
+
同一文件里的 `turn.started`(`is_subagent:true`)与 `model.response.completed` 可交叉核对:前者证明三个角色各自起了独立 turn,后者带 `provider` 字段。两个坑:(1) 被验证的那个 modelId 必须**不等于会话模型**,否则该角色测不出判别力(会话模型通常是 `qfmodel`);(2) 改完 agent 文件必须**换新会话**再测,注册表只在会话启动时加载,用旧会话的数据下结论会被旧绑定污染。专家自述的 `requested_model` 只是在复述自己 frontmatter 里的声明,不构成证据。
|
|
360
|
+
|
|
361
|
+
**成本**:走这条路径不消耗外部 API key,只计 Qoder Credits。
|
|
343
362
|
|
|
344
363
|
#### OpenCode 平台(External API 模式)
|
|
345
364
|
|
|
346
365
|
OpenCode 环境下通过 `opencode.json` 的 agent 配置 + `.delphi-config.json` 调用外部 API:
|
|
347
366
|
- **MUST 从 `opencode.json` 的 agent 配置中读取模型**
|
|
348
|
-
- 通过 `scripts/delphi-external-review.cjs` 调用各 provider 的兼容 API
|
|
367
|
+
- 通过 `scripts/delphi-external-review.cjs` 调用各 provider 的兼容 API(注意:该脚本目前在仓库根 `scripts/` 下,尚未随 skill 目录分发)
|
|
349
368
|
- 需要用户自行配置 API key(环境变量注入)
|
|
350
369
|
|
|
370
|
+
#### DeepSeek Harness 平台(`delphi-review` 工具 — 推荐)
|
|
371
|
+
|
|
372
|
+
DSH 里**不要**用 `subagent` 工具派发三个专家。原因和 Qoder 同源:DSH 的 subagent
|
|
373
|
+
继承宿主配置的**单一**模型(`SubagentModelSelectionConfig.enabled` 默认 `false`),
|
|
374
|
+
三个"专家"会是同一模型的三个实例 —— 按本 skill 的规则必须 BLOCK。
|
|
375
|
+
|
|
376
|
+
DSH 的正确入口是插件提供的 **`delphi-review` 工具**(`@boyingliu01/dsh-plugin-xp-gate`):
|
|
377
|
+
|
|
378
|
+
```
|
|
379
|
+
delphi-review(
|
|
380
|
+
subject: <待评审材料:diff / 设计 / 规格>,
|
|
381
|
+
addressed: <可选:上一轮之后改了什么,用于后续轮次>,
|
|
382
|
+
maxRounds: <可选:覆盖默认 5 轮>
|
|
383
|
+
)
|
|
384
|
+
```
|
|
385
|
+
|
|
386
|
+
该工具**直连外部 OpenAI 兼容网关**,每个席位绑定一个不同模型,因此不依赖宿主配置即可
|
|
387
|
+
满足不变量。它复用**同一份** `.delphi-config.json`,无需额外配置。
|
|
388
|
+
|
|
389
|
+
**代码强制的不变量**(不是文档约定,违反即报错):
|
|
390
|
+
|
|
391
|
+
| 不变量 | 违反时 |
|
|
392
|
+
|---|---|
|
|
393
|
+
| 恰好 3 位专家 | `DelphiConfigError` |
|
|
394
|
+
| 3 个 `requested_model` 去空白后两两不同 | `DelphiConfigError` |
|
|
395
|
+
| 模型不得为未展开的 `${...}` 占位符 | `DelphiConfigError`(在**任何网络调用之前**) |
|
|
396
|
+
| 缺密钥 / 缺 provider / 专家跨网关 | `DelphiSetupError`,报变量名而非回显配置 |
|
|
397
|
+
| 报告缺 `### VERDICT` 段 | 记为 `INVALID_NO_VERDICT`,**不计入共识** |
|
|
398
|
+
| 退出码 0 但内容为空 | 记为 ERROR(推理模型可能把预算全烧在隐藏 token 上) |
|
|
399
|
+
|
|
400
|
+
**轮次语义与本 skill 一致**:Round 1 匿名且并发;Round 2+ 回传**汇总票型**与"已修改项",
|
|
401
|
+
个体报告始终匿名。达到阈值即停,否则到 `max_review_rounds` 为止并如实返回 `NO CONSENSUS`。
|
|
402
|
+
|
|
403
|
+
**推理模型注意**:`g-glm-5.3-flash` 这类模型会先产出隐藏推理 token。
|
|
404
|
+
实测 `max_tokens=4000` 时 `finish_reason=length` 且 **content 为空**;
|
|
405
|
+
席位需独立预算(该席位配置为 32000)。工具把"空内容"记为 ERROR 而非空评审。
|
|
406
|
+
|
|
407
|
+
> 备选:若希望 DSH **原生** `subagent` 也能选模型,需同时满足
|
|
408
|
+
> (1) `dsh-tool-subagent` 的 `modelSelectionSettings: true`(仅能力开关),
|
|
409
|
+
> (2) 宿主设置 `SubagentModelSelectionConfig.enabled = true` 且 `allowedModels` ≥3 个不同模型。
|
|
410
|
+
> 该路线依赖宿主配置,CI / 一次性会话不可靠,故不作为默认路径。
|
|
411
|
+
#### WorkBuddy 平台(Expert 团队模式)
|
|
412
|
+
|
|
413
|
+
WorkBuddy 侧以 Team 型专家团(示例:`delphi-review-team`,安装在 `~/.workbuddy/plugins/marketplaces/my-experts/plugins/`,创建方式见 [docs/platform-workbuddy.md](../../docs/platform-workbuddy.md))承载评审,触发本技能后按此路由执行:
|
|
414
|
+
|
|
415
|
+
| 角色 | Agent ID | 绑定模型 | 倍率 |
|
|
416
|
+
|------|----------|---------|------|
|
|
417
|
+
| 主理人(衡定一,会话自身) | delphi-review-team-team-lead | 会话模型,固定用 `local-deepseek-v4-flash` | 零 credits |
|
|
418
|
+
| Architecture (A) | delphi-arch-reviewer | `deepseek-v4.1-flash` | x0.11 |
|
|
419
|
+
| Technical (B) | delphi-tech-reviewer | `glm-5.3-flash` | x0.06 |
|
|
420
|
+
| Feasibility (C) | delphi-feasibility-reviewer | `hy3` | x0.00(免费) |
|
|
421
|
+
|
|
422
|
+
**执行方式**:
|
|
423
|
+
- 主理人开场自检:会话模型不是 `local-deepseek-v4-flash` 时,先提醒用户切换再开始。
|
|
424
|
+
- 已启用专家团 → 按其团队机制 spawn 3 位成员;未启用 → 用 `Agent` 工具同一消息**并行** spawn 3 个 `general-purpose` 子代理,prompt = 对应专家 MD 全文人设(读专家团 `agents/` 目录)+ 匿名评审模板 + 待评审材料,`model` 参数按上表传。
|
|
425
|
+
- Round 1 三个子代理互不见对方输出(匿名铁律不变)。
|
|
426
|
+
|
|
427
|
+
**执行验证(适配本平台,2026-09-29 校准)**:
|
|
428
|
+
- 参数层路由 ✅ 已实测:`Agent` 工具 `model` 参数传 `deepseek-v4.1-flash` / `glm-5.3-flash` / `hy3` 三个 ID 均被接受,3 个 general-purpose 子代理并行执行成功。
|
|
429
|
+
- 执行层证据 ⚠️ 平台暂缺:WorkBuddy 审计日志(`~/.workbuddy/audit-log/`)只记 command-safety 事件,无 per-request 模型路由记录,无法像 Qoder/OpenCode 那样从日志取证 distinct models。
|
|
430
|
+
- **WorkBuddy 过渡验证标准**:参数级校准(model ID 被平台接受且子代理成功执行)+ 专家 `requested_model` 自述如实记录;报告中必须注明「执行层验证待平台日志支持」。若 `model` 参数被拒绝(报错/回落会话模型),按本技能 Failure Handling 处理,不得静默降级冒充三模型共识。
|
|
431
|
+
- 契约合规兜底:需要严格证据时,走外部 provider 路径(`.delphi-config.json` + 兼容 API,如 whalecloud 网关 3 个模型 ID)。
|
|
432
|
+
|
|
433
|
+
**成本**:3 专家合计 x0.17/轮;主理人选 local 会话模型则零 credits。
|
|
434
|
+
|
|
351
435
|
### 共识阈值
|
|
352
436
|
|
|
353
437
|
| 阈值 | 说明 |
|
|
@@ -454,9 +538,9 @@ Every Delphi mode MUST use the full JSON schema below. Architecture, technical,
|
|
|
454
538
|
"context_file_used": "CONTEXT.md",
|
|
455
539
|
"round": 1,
|
|
456
540
|
"expert_verdicts": [
|
|
457
|
-
{ "role": "architecture", "verdict": "APPROVED", "confidence": 9, "result_type": "delphi_expert_result", "requested_model": "provider/model-a" },
|
|
458
|
-
{ "role": "technical", "verdict": "APPROVED", "confidence": 8, "result_type": "delphi_expert_result", "requested_model": "provider/model-b" },
|
|
459
|
-
{ "role": "feasibility", "verdict": "APPROVED", "confidence": 8, "result_type": "delphi_expert_result", "requested_model": "provider/model-c" }
|
|
541
|
+
{ "role": "architecture", "verdict": "APPROVED", "confidence": 9, "result_type": "delphi_expert_result", "requested_model": "provider/model-a", "resolved_model": "provider/model-a", "channel": "external" },
|
|
542
|
+
{ "role": "technical", "verdict": "APPROVED", "confidence": 8, "result_type": "delphi_expert_result", "requested_model": "provider/model-b", "resolved_model": "provider/model-b", "channel": "external" },
|
|
543
|
+
{ "role": "feasibility", "verdict": "APPROVED", "confidence": 8, "result_type": "delphi_expert_result", "requested_model": "provider/model-c", "resolved_model": "provider/model-c", "channel": "external" }
|
|
460
544
|
],
|
|
461
545
|
"requirements_statement": "<short summary of what was reviewed>",
|
|
462
546
|
"gaps_found": [],
|
|
@@ -255,9 +255,15 @@ pre-push hook
|
|
|
255
255
|
|------|------|------|
|
|
256
256
|
| 单次走查成本 | ~$0.03 | 正常执行 |
|
|
257
257
|
| 变更规模 | 无硬性文件数或行数上限 | 大型变更必须完整评审或由用户选择拆分 |
|
|
258
|
+
| 单席位 prompt 体积 | 默认 40000 字节(`max_prompt_bytes` 可按席位覆盖) | runner 发送前拒绝,报错里给出处置办法 |
|
|
258
259
|
|
|
259
260
|
大型变更没有自动跳过或绕过路径。用户可以选择完整评审,或在评审前主动拆分变更;每个变更仍须通过完整的走查和证据验证。
|
|
260
261
|
|
|
262
|
+
**prompt 预算是硬约束,不是建议**:实测 `g-glm-5.3-flash` 在 ~40KB 正常应答(225s)、~50KB 直接崩溃
|
|
263
|
+
(305s,`0xC0000409` + libuv `UV_HANDLE_CLOSING`,表现为 "Network error.")——一个席位崩溃就会让整个共识无法完成。
|
|
264
|
+
构造输入时按重要性排序、给每个席位独立预算、并在报告里显式列出因体积省略的文件;
|
|
265
|
+
截断到「核心 hunk 看不见」比超限更糟:席位会花整轮报告「无法审计」,而这是评审者的真实缺陷而非代码缺陷。
|
|
266
|
+
|
|
261
267
|
---
|
|
262
268
|
|
|
263
269
|
## Agent 可用性检查 (MANDATORY)
|
|
@@ -356,9 +362,9 @@ IF 任何检查失败:
|
|
|
356
362
|
"verdict": "APPROVED",
|
|
357
363
|
"confidence": 9,
|
|
358
364
|
"experts": [
|
|
359
|
-
{ "id": "Expert A", "role": "architecture", "verdict": "APPROVED", "confidence": 9, "result_type": "delphi_expert_result", "requested_model": "provider/model-a", "resolved_model": "provider/model-a" },
|
|
360
|
-
{ "id": "Expert B", "role": "technical", "verdict": "APPROVED", "confidence": 8, "result_type": "delphi_expert_result", "requested_model": "provider/model-b", "resolved_model":
|
|
361
|
-
{ "id": "Expert C", "role": "feasibility", "verdict": "APPROVED", "confidence": 8, "result_type": "delphi_expert_result", "requested_model": "provider/model-c", "resolved_model": "provider/model-c" }
|
|
365
|
+
{ "id": "Expert A", "role": "architecture", "verdict": "APPROVED", "confidence": 9, "result_type": "delphi_expert_result", "requested_model": "provider/model-a", "resolved_model": "provider/model-a", "channel": "external" },
|
|
366
|
+
{ "id": "Expert B", "role": "technical", "verdict": "APPROVED", "confidence": 8, "result_type": "delphi_expert_result", "requested_model": "provider/model-b", "resolved_model": "provider/model-b", "channel": "external" },
|
|
367
|
+
{ "id": "Expert C", "role": "feasibility", "verdict": "APPROVED", "confidence": 8, "result_type": "delphi_expert_result", "requested_model": "provider/model-c", "resolved_model": "provider/model-c", "channel": "external" }
|
|
362
368
|
],
|
|
363
369
|
"issues": [],
|
|
364
370
|
"consensus_ratio": 1.0,
|
|
@@ -399,6 +405,33 @@ Hook 验证以下条件(全部满足才允许 push):
|
|
|
399
405
|
5. timestamp 未过期
|
|
400
406
|
6. expires 必须是 timestamp 之后恰好 1 小时
|
|
401
407
|
|
|
408
|
+
### experts[] 必填字段与 #423 的迁移说明
|
|
409
|
+
|
|
410
|
+
`validate-code-walkthrough.cjs` 对每个 expert 对象强制要求:
|
|
411
|
+
|
|
412
|
+
| 字段 | 约束 |
|
|
413
|
+
|------|------|
|
|
414
|
+
| `result_type` | 必须等于 `"delphi_expert_result"` |
|
|
415
|
+
| `role` | 非空,三个席位 role 互异 |
|
|
416
|
+
| `requested_model` | 非空;比较前只做 trim,**区分大小写**(规范原文是 "distinct trimmed requested model IDs",见 AGENTS.md) |
|
|
417
|
+
| `resolved_model` | 非空;比较前 trim + 转小写,三者互异 |
|
|
418
|
+
| `channel` | 必须严格属于 `["external", "local"]`;`provider: local` 的 fallback 记录不能计为成功执行 |
|
|
419
|
+
|
|
420
|
+
缺字段或取值不在白名单 → Gate MW **FAIL**(不是 SKIP)。
|
|
421
|
+
|
|
422
|
+
**升级后旧证据怎么处置**:唯一正确做法是在当前 HEAD 重跑一次评审、由 runner 重新生成证据文件。
|
|
423
|
+
不要手工给老文件补 `channel` —— 那等于伪造执行证据,而 `channel` 存在的目的正是让证据可追溯到一次真实调用。
|
|
424
|
+
证据本来就与 commit 绑定(`commit` 必须等于 HEAD,有效期 1 小时),跨 commit 复用从来不是受支持的路径。
|
|
425
|
+
|
|
426
|
+
**回滚语义**:validator 与证据生产者(`scripts/delphi-external-review.cjs`)必须成对回滚。
|
|
427
|
+
|
|
428
|
+
- 旧 validator 读新证据:安全,多余字段被忽略。
|
|
429
|
+
- 新 validator 读旧证据:必然 FAIL(缺 `channel`)。这是设计意图——宁可在发布关口停下,
|
|
430
|
+
也不放行一份无法证明模型来源的证据;把「三个不同模型真的跑过」降级为可选项,等于让门禁只剩形式。
|
|
431
|
+
- 临时降级开关:**没有**。既没有 `XP_GATE_SKIP_MW` 之类的环境变量,也不允许 `--no-verify`
|
|
432
|
+
(见 `githooks/QUALITY-GATES-CODE-OF-CONDUCT.md`)。如果评审确实无法完成,处理方式是缩小
|
|
433
|
+
`--range` 或按席位调整 prompt 预算重跑,而不是放行。
|
|
434
|
+
|
|
402
435
|
---
|
|
403
436
|
|
|
404
437
|
## 与现有系统集成
|
|
@@ -190,9 +190,9 @@ Round 2: delphi-review --mode requirements(带 Round 1 gaps 上下文)
|
|
|
190
190
|
"context_file_used": null,
|
|
191
191
|
"round": 1,
|
|
192
192
|
"expert_verdicts": [
|
|
193
|
-
{ "role": "architecture", "verdict": "APPROVED", "confidence": 9, "result_type": "delphi_expert_result", "requested_model": "bailian-tp/qwen-plus" },
|
|
194
|
-
{ "role": "technical", "verdict": "APPROVED", "confidence": 8, "result_type": "delphi_expert_result", "requested_model": "bailian-tp/deepseek-v3" },
|
|
195
|
-
{ "role": "feasibility", "verdict": "APPROVED", "confidence": 8, "result_type": "delphi_expert_result", "requested_model": "bailian-tp/glm-4.5" }
|
|
193
|
+
{ "role": "architecture", "verdict": "APPROVED", "confidence": 9, "result_type": "delphi_expert_result", "requested_model": "bailian-tp/qwen-plus", "resolved_model": "qwen-plus", "channel": "external" },
|
|
194
|
+
{ "role": "technical", "verdict": "APPROVED", "confidence": 8, "result_type": "delphi_expert_result", "requested_model": "bailian-tp/deepseek-v3", "resolved_model": "deepseek-v3", "channel": "external" },
|
|
195
|
+
{ "role": "feasibility", "verdict": "APPROVED", "confidence": 8, "result_type": "delphi_expert_result", "requested_model": "bailian-tp/glm-4.5", "resolved_model": "glm-4.5", "channel": "external" }
|
|
196
196
|
],
|
|
197
197
|
"requirements_statement": "实现用户注册流程,支持邮箱验证和密码重置",
|
|
198
198
|
"gaps_found": [],
|
|
@@ -214,9 +214,9 @@ Round 2: delphi-review --mode requirements(带 Round 1 gaps 上下文)
|
|
|
214
214
|
"context_file_used": null,
|
|
215
215
|
"round": 1,
|
|
216
216
|
"expert_verdicts": [
|
|
217
|
-
{ "role": "architecture", "verdict": "APPROVED", "confidence": 8, "result_type": "delphi_expert_result", "requested_model": "provider/model-a" },
|
|
218
|
-
{ "role": "technical", "verdict": "GAPS_FOUND", "confidence": 7, "result_type": "delphi_expert_result", "requested_model": "provider/model-b" },
|
|
219
|
-
{ "role": "feasibility", "verdict": "APPROVED", "confidence": 8, "result_type": "delphi_expert_result", "requested_model": "provider/model-c" }
|
|
217
|
+
{ "role": "architecture", "verdict": "APPROVED", "confidence": 8, "result_type": "delphi_expert_result", "requested_model": "provider/model-a", "resolved_model": "provider/model-a", "channel": "external" },
|
|
218
|
+
{ "role": "technical", "verdict": "GAPS_FOUND", "confidence": 7, "result_type": "delphi_expert_result", "requested_model": "provider/model-b", "resolved_model": "provider/model-b", "channel": "external" },
|
|
219
|
+
{ "role": "feasibility", "verdict": "APPROVED", "confidence": 8, "result_type": "delphi_expert_result", "requested_model": "provider/model-c", "resolved_model": "provider/model-c", "channel": "external" }
|
|
220
220
|
],
|
|
221
221
|
"requirements_statement": "实现用户注册流程,支持邮箱验证和密码重置",
|
|
222
222
|
"gaps_found": [
|