@boyingliu01/xp-gate 0.35.13 → 0.35.15

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 (41) hide show
  1. package/lib/__tests__/delphi-channel-doctor.test.js +116 -0
  2. package/lib/__tests__/delphi-channel-uninstall.test.js +128 -0
  3. package/lib/__tests__/delphi-channel.test.js +109 -0
  4. package/lib/delphi-channel.js +181 -0
  5. package/lib/delphi-setup.js +66 -1
  6. package/lib/doctor.js +36 -6
  7. package/lib/init.js +15 -0
  8. package/lib/mattpocock-license-verdict.json +1 -1
  9. package/mock-policy/AGENTS.md +2 -2
  10. package/mutation/AGENTS.md +2 -2
  11. package/package.json +1 -1
  12. package/plugins/claude-code/.claude-plugin/plugin.json +1 -1
  13. package/plugins/claude-code/skills/delphi-review/AGENTS.md +2 -2
  14. package/plugins/claude-code/skills/delphi-review/SKILL.md +32 -0
  15. package/plugins/claude-code/skills/sprint-flow/AGENTS.md +2 -2
  16. package/plugins/claude-code/skills/test-specification-alignment/AGENTS.md +2 -2
  17. package/plugins/dsh/lib/index.d.ts +10 -0
  18. package/plugins/dsh/lib/index.d.ts.map +1 -1
  19. package/plugins/dsh/lib/index.js +10 -2
  20. package/plugins/dsh/package.json +1 -1
  21. package/plugins/dsh/skills/delphi-review/AGENTS.md +2 -2
  22. package/plugins/dsh/skills/delphi-review/SKILL.md +32 -0
  23. package/plugins/dsh/skills/sprint-flow/AGENTS.md +2 -2
  24. package/plugins/dsh/skills/test-specification-alignment/AGENTS.md +2 -2
  25. package/plugins/dsh/src/index.test.ts +44 -2
  26. package/plugins/dsh/src/index.ts +10 -2
  27. package/plugins/opencode/package.json +1 -1
  28. package/plugins/opencode/skills/delphi-review/AGENTS.md +2 -2
  29. package/plugins/opencode/skills/delphi-review/SKILL.md +32 -0
  30. package/plugins/opencode/skills/sprint-flow/AGENTS.md +2 -2
  31. package/plugins/opencode/skills/test-specification-alignment/AGENTS.md +2 -2
  32. package/plugins/qoder/plugin.json +1 -1
  33. package/plugins/qoder/skills/delphi-review/AGENTS.md +2 -2
  34. package/plugins/qoder/skills/delphi-review/SKILL.md +32 -0
  35. package/plugins/qoder/skills/sprint-flow/AGENTS.md +2 -2
  36. package/plugins/qoder/skills/test-specification-alignment/AGENTS.md +2 -2
  37. package/principles/AGENTS.md +2 -2
  38. package/skills/delphi-review/AGENTS.md +2 -2
  39. package/skills/delphi-review/SKILL.md +32 -0
  40. package/skills/sprint-flow/AGENTS.md +2 -2
  41. package/skills/test-specification-alignment/AGENTS.md +2 -2
package/lib/doctor.js CHANGED
@@ -3238,6 +3238,21 @@ async function doctorDelphi(options = {}) {
3238
3238
  // retype: the detail names delphi-setup. Project-level agents (from
3239
3239
  // `delphi-setup --local --project`) take precedence when a complete set
3240
3240
  // exists there, so the doctor agrees with the repair command (#414 R1 FC-09).
3241
+ //
3242
+ // #646: when the machine is configured for the external channel only, the native
3243
+ // path is DISABLED BY CHOICE, not broken. Reporting FAIL here is what told external
3244
+ // users their working setup was broken and pointed them at a repair command that
3245
+ // reinstalls the thing they just removed. SKIP carries the reason so the line can
3246
+ // never be read as green either -- the same rule every other gate follows.
3247
+ const delphiChannel = require('./delphi-channel.js');
3248
+ // Pass the directory that CONTAINS `.config`, not the `.config` dir itself:
3249
+ // resolveChannels() appends `<home>/.config/xp-gate/...`, so handing it `.config`
3250
+ // would look in `.config/.config/xp-gate` and never find the file.
3251
+ const channelHome = process.env.XDG_CONFIG_HOME
3252
+ ? path.dirname(process.env.XDG_CONFIG_HOME)
3253
+ : undefined;
3254
+ const channelResolution = delphiChannel.resolveChannels({ home: channelHome });
3255
+ const nativeDisabled = !channelResolution.nativeEnabled;
3241
3256
  const { readQoderModelId } = require('./init.js');
3242
3257
  const roles = ['architecture', 'technical', 'feasibility'];
3243
3258
  const homeAgentsDir = path.join(HOME_DIR(), '.qoder', 'agents');
@@ -3252,7 +3267,16 @@ async function doctorDelphi(options = {}) {
3252
3267
  const agentIds = [];
3253
3268
  let agentsStatus = 'PASS';
3254
3269
  const agentDetails = [agentsSource];
3255
- for (const role of roles) {
3270
+ // #646: skip the whole native binding check when the machine is external-only.
3271
+ // The check exists to keep the native path healthy; when that path is switched off
3272
+ // by configuration there is nothing to keep healthy, and reporting its absence as
3273
+ // FAIL told users their working setup was broken.
3274
+ if (nativeDisabled) {
3275
+ add('Qoder Delphi agent bindings', 'SKIP',
3276
+ `native channel disabled (channels=${channelResolution.channels.join(',')} via ${channelResolution.source}) — `
3277
+ + 'Delphi review runs through the external provider');
3278
+ }
3279
+ for (const role of nativeDisabled ? [] : roles) {
3256
3280
  const agentPath = path.join(agentsDir, `delphi-${role}.md`);
3257
3281
  if (!fs.existsSync(agentPath)) {
3258
3282
  agentsStatus = 'FAIL';
@@ -3283,12 +3307,18 @@ async function doctorDelphi(options = {}) {
3283
3307
  // Report every problem class independently: missing files, unparseable
3284
3308
  // bindings and duplicates can coexist, and each has a different remedy
3285
3309
  // (#414 R1 MI-02).
3286
- if (agentIds.length !== roles.length) agentsStatus = 'FAIL';
3287
- if (new Set(agentIds).size !== agentIds.length) {
3288
- agentsStatus = 'FAIL';
3289
- agentDetails.push('duplicate model binding across experts — three DISTINCT ids are required');
3310
+ //
3311
+ // #646: only when the native channel is actually enabled. Running this over an
3312
+ // empty agentIds (the disabled path) would compute `0 !== 3` and re-report the very
3313
+ // FAIL the SKIP above just replaced.
3314
+ if (!nativeDisabled) {
3315
+ if (agentIds.length !== roles.length) agentsStatus = 'FAIL';
3316
+ if (new Set(agentIds).size !== agentIds.length) {
3317
+ agentsStatus = 'FAIL';
3318
+ agentDetails.push('duplicate model binding across experts — three DISTINCT ids are required');
3319
+ }
3320
+ add('Qoder Delphi agent bindings', agentsStatus, agentDetails.join('; ') || 'all three bound');
3290
3321
  }
3291
- add('Qoder Delphi agent bindings', agentsStatus, agentDetails.join('; ') || 'all three bound');
3292
3322
 
3293
3323
  // 3. Skill install parity: a drifted installed copy is how "reinstall from
3294
3324
  // an old local repo" became a downgrade (#414 B4). Every bundled file is
package/lib/init.js CHANGED
@@ -844,6 +844,21 @@ function configureQoderDelphiAgents(srcDir, targetRoot) {
844
844
  return; // Not Qoder — OpenCode uses .delphi-config.json instead
845
845
  }
846
846
 
847
+ // #646: honour the machine-level channel preference BEFORE looking at anything
848
+ // else. The native path is measurably disproved (Qoder's subagent dispatch adopts
849
+ // the persona but drops the model binding — recorded in skills/delphi-review), so
850
+ // a user who configured "external only" must not have it silently reinstalled on
851
+ // every upgrade. Say which file the decision came from: an unexplained SKIP is
852
+ // indistinguishable from a broken install.
853
+ const channel = require('./delphi-channel.js');
854
+ const resolved = channel.resolveChannels();
855
+ if (resolved.warning) console.warn(` ⚠ Delphi channel: ${resolved.warning}`);
856
+ if (!resolved.nativeEnabled) {
857
+ console.log(` Qoder Delphi agents: SKIP (channels=${resolved.channels.join(',')} via ${resolved.source})`);
858
+ console.log(' Delphi review runs through the external provider (see .delphi-config.json).');
859
+ return;
860
+ }
861
+
847
862
  const agentSrcDir = path.join(srcDir, 'plugins', 'qoder', 'agents');
848
863
  const agentsDestDir = path.join(targetRoot, '.qoder', 'agents');
849
864
 
@@ -2,6 +2,6 @@
2
2
  "verdict": "PASS",
3
3
  "license_type": "MIT",
4
4
  "sha256": "0e7ac423bf2c6e223b7c5b156f8cf72da49d748e56a1641402c31f22ad07dbb5",
5
- "checked_at": "2026-10-30T22:02:11.313Z",
5
+ "checked_at": "2026-10-30T22:49:35.688Z",
6
6
  "source": "network"
7
7
  }
@@ -1,9 +1,9 @@
1
1
  # SRC/MOCK-POLICY KNOWLEDGE BASE
2
2
 
3
3
  **Generated:** 2026-10-30
4
- **Commit:** 3978d361
4
+ **Commit:** 6091e238
5
5
  **Branch:** main
6
- **Version:** 0.35.13.0
6
+ **Version:** 0.35.15.0
7
7
 
8
8
  ## OVERVIEW
9
9
  Mock layering policy enforcement — Gate M3 of pre-push hook. Ensures integration tests use real implementations for internal dependencies, mock external dependencies, and annotate pending mocks with removal plans. Combines project scope scanning, mock decision engine, and per-file validation into a single pipeline.
@@ -1,9 +1,9 @@
1
1
  # SRC/MUTATION KNOWLEDGE BASE
2
2
 
3
3
  **Generated:** 2026-10-30
4
- **Commit:** 3978d361
4
+ **Commit:** 6091e238
5
5
  **Branch:** main
6
- **Version:** 0.35.13.0
6
+ **Version:** 0.35.15.0
7
7
 
8
8
  ## OVERVIEW
9
9
  **Gate M** (incremental mutation testing) + **Gate M2** helpers (test-layer detection used by `src/mock-policy/`). Pre-push quality gate. TypeScript-only; uses Stryker.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@boyingliu01/xp-gate",
3
- "version": "0.35.13",
3
+ "version": "0.35.15",
4
4
  "description": "AI-driven development workflow: 6 quality gates + Delphi review + Sprint Flow",
5
5
  "bin": {
6
6
  "xp-gate": "bin/xp-gate.js"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "xp-gate",
3
- "version": "0.35.13",
3
+ "version": "0.35.15",
4
4
  "displayName": "XP-Gate",
5
5
  "description": "Extreme Programming quality gates + AI workflow skills for Claude Code. Includes 12 pre-commit gates (Gate 0-11), 8 pre-push gates, Sprint Flow (6 phases), and Delphi multi-expert review (>=90% consensus).",
6
6
  "author": {
@@ -1,9 +1,9 @@
1
1
  # SKILLS/DELPHI-REVIEW KNOWLEDGE BASE
2
2
 
3
3
  **Generated:** 2026-10-30
4
- **Commit:** 3978d361
4
+ **Commit:** 6091e238
5
5
  **Branch:** main
6
- **Version:** 0.35.13.0
6
+ **Version:** 0.35.15.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.
@@ -384,6 +384,38 @@ Qoder 的内置模型**没有本地推理端点**:推理请求由 Qoder 客户
384
384
  - 项目级:`<project>/.qoder/agents/`(`xp-gate init`)
385
385
  - 用户级:`~/.qoder/agents/`(`xp-gate init --global`)
386
386
 
387
+ #### 推荐路径:external-only(#646)
388
+
389
+ 上面整条 native 通道在本机**实测不可用**。所以推荐配置是**关掉它**,只走外部 provider —— 这是唯一能满足三模型契约的路径,也是这份文档的默认建议。
390
+
391
+ **开启方式**(改一次机器级偏好,对所有仓库生效):
392
+
393
+ ```bash
394
+ # 1. 移除已部署的 native agent(幂等,重复执行不报错)
395
+ xp-gate delphi-setup --uninstall --global
396
+ # 或项目级:xp-gate delphi-setup --uninstall --local --project <dir>
397
+
398
+ # 2. 写通道偏好,阻止 `xp-gate init` 再装回来
399
+ # ~/.config/xp-gate/.delphi-config.json
400
+ # { "channels": ["external"] }
401
+ ```
402
+
403
+ 临时覆盖(CI、一次性运行):`XP_GATE_DELPHI_CHANNEL=external`。
404
+
405
+ **生效范围**:
406
+
407
+ | 位置 | external-only 时的行为 |
408
+ |---|---|
409
+ | `xp-gate init` / `init --global` | 不部署 `delphi-*.md`,打印 `SKIP (channels=external via config)` |
410
+ | `xp-gate doctor delphi` | native 检查报 **SKIP** 并注明来源,**不再 FAIL**;外部链路(config / runner / 三席互异)照常逐项校验 |
411
+ | 本技能的通道决策表 | 直接走 external,不再探测 native |
412
+
413
+ **为什么偏好放在机器级**:通道选择是"这台机器上 Delphi 怎么跑",与具体仓库无关。放进项目文件会让每个仓库各写一遍。
414
+
415
+ **为什么不默认 external-only**:那会悄悄改掉所有没提要求的人的行为。默认保持两条通道都可用,由用户显式关闭。
416
+
417
+ **doctor 的 SKIP 不等于绿灯**:它带着理由(`channels=external via config`),和"没装"、"坏了"能区分开。这是本项目所有门禁的统一规则——跳过必须说明为什么跳。
418
+
387
419
  **model 字段格式**:仍须写成 `"[DisplayName](modelId)"`。裸名字或已下线 ID 不会报错,会被平台静默忽略。`xp-gate init` 会审计已部署的 `delphi-*.md`,格式非法或 modelId 与本版本模板不一致时点名文件并给出"删除 → 重跑 `init` → 重启会话"提示;因为部署语义是"已存在不覆盖",从 #417 之前的版本升级必须手工删旧文件再重跑 `init`。注意:修好绑定也只是让角色声明规范,并不改变上表的执行模型结论。
388
420
 
389
421
  **执行方式**:用 Agent tool 并行派发 3 个 subagent,`subagent_type` 分别取 `delphi-architecture`、`delphi-technical`、`delphi-feasibility`。**新建或修改 agent 文件后必须重开会话**——注册表只在会话启动时加载,否则报 `Unknown agent type`。
@@ -1,9 +1,9 @@
1
1
  # SKILLS/SPRINT-FLOW KNOWLEDGE BASE
2
2
 
3
3
  **Generated:** 2026-10-30
4
- **Commit:** 3978d361
4
+ **Commit:** 6091e238
5
5
  **Branch:** main
6
- **Version:** 0.35.13.0
6
+ **Version:** 0.35.15.0
7
7
 
8
8
  ## OVERVIEW
9
9
  **6-phase** development pipeline (v2.0 compact redesign, Issue #290): PREP → DESIGN → BUILD → VERIFY → SHIP → CLOSE. Phase 3/6 BUILD default build mode is **ralph-loop** (REQ-level iteration, 40-67% token savings vs parallel). HARD-GATE between DESIGN (2/6) and BUILD (3/6): design must pass Delphi review (≥90% consensus) before any coding.
@@ -1,9 +1,9 @@
1
1
  # SKILLS/TEST-SPECIFICATION-ALIGNMENT KNOWLEDGE BASE
2
2
 
3
3
  **Generated:** 2026-10-30
4
- **Commit:** 3978d361
4
+ **Commit:** 6091e238
5
5
  **Branch:** main
6
- **Version:** 0.35.13.0
6
+ **Version:** 0.35.15.0
7
7
 
8
8
  ## OVERVIEW
9
9
  Test-Specification Alignment Engine — two-stage validation ensuring tests accurately reflect requirements and design specs.
@@ -23,6 +23,16 @@ export declare const renderText: <A>(_args: A, value: string) => {
23
23
  }[];
24
24
  export declare const name = "tool-xp-gate";
25
25
  export declare const inject: string[];
26
+ /**
27
+ * The first `${VAR}` / `{env:VAR}` name in a config value, or undefined.
28
+ *
29
+ * Exported for tests (#562). This one-line function is on the SECURITY path:
30
+ * `resolveExpressionKey` treats "no placeholder found" as "the user typed a literal
31
+ * token", and rejects it outright so the credential never lands in a profile file.
32
+ * So a regex mistake here is not cosmetic -- a false positive on a literal key would
33
+ * let it through, and a false negative would reject a perfectly valid reference.
34
+ */
35
+ export declare function findPlaceholderName(value: string): string | undefined;
26
36
  /**
27
37
  * Runtime schema for the keys above.
28
38
  *
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAOA,OAAO,CAAC,MAAM,0BAA0B,CAAA;AACxC,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAA;AA0BlD;;;;;;;;;;;;;;;;GAgBG;AACH,eAAO,MAAM,UAAU,GAAI,CAAC,EAAE,OAAO,CAAC,EAAE,OAAO,MAAM;;;GAA6C,CAAA;AAElG,eAAO,MAAM,IAAI,iBAAiB,CAAA;AAGlC,eAAO,MAAM,MAAM,UAA+B,CAAA;AAsZlD;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,MAAM;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;aA4BjB,CAAA;AAEF,wBAAgB,KAAK,CAAC,GAAG,EAAE,OAAO,EAAE,MAAM,GAAE,OAAY,GAAG,IAAI,CAgC9D"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAOA,OAAO,CAAC,MAAM,0BAA0B,CAAA;AACxC,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAA;AA0BlD;;;;;;;;;;;;;;;;GAgBG;AACH,eAAO,MAAM,UAAU,GAAI,CAAC,EAAE,OAAO,CAAC,EAAE,OAAO,MAAM;;;GAA6C,CAAA;AAElG,eAAO,MAAM,IAAI,iBAAiB,CAAA;AAGlC,eAAO,MAAM,MAAM,UAA+B,CAAA;AAkQlD;;;;;;;;GAQG;AACH,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAKrE;AA8ID;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,MAAM;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;aA4BjB,CAAA;AAEF,wBAAgB,KAAK,CAAC,GAAG,EAAE,OAAO,EAAE,MAAM,GAAE,OAAY,GAAG,IAAI,CAgC9D"}
@@ -258,8 +258,16 @@ async function resolveExpressionKey(expression, resolveApiKey) {
258
258
  return fromEnv.value;
259
259
  throw new DelphiSetupError(`API key variable ${placeholder} has no value. Set it, or store it in DSH's credential store.`);
260
260
  }
261
- /** The first `${VAR}` / `{env:VAR}` name in a config value, or undefined. */
262
- function findPlaceholderName(value) {
261
+ /**
262
+ * The first `${VAR}` / `{env:VAR}` name in a config value, or undefined.
263
+ *
264
+ * Exported for tests (#562). This one-line function is on the SECURITY path:
265
+ * `resolveExpressionKey` treats "no placeholder found" as "the user typed a literal
266
+ * token", and rejects it outright so the credential never lands in a profile file.
267
+ * So a regex mistake here is not cosmetic -- a false positive on a literal key would
268
+ * let it through, and a false negative would reject a perfectly valid reference.
269
+ */
270
+ export function findPlaceholderName(value) {
263
271
  const shell = /\$\{([A-Za-z_][A-Za-z0-9_]*)\}/.exec(value);
264
272
  if (shell)
265
273
  return shell[1];
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@boyingliu01/dsh-plugin-xp-gate",
3
- "version": "0.35.13",
3
+ "version": "0.35.15",
4
4
  "description": "XP-Gate deterministic quality gates + AI workflow skills as a DeepSeek Harness (DSH) native plugin: gate-check / gate-principles / gate-arch tools + 12 SKILL.md skills",
5
5
  "type": "module",
6
6
  "main": "./lib/index.js",
@@ -1,9 +1,9 @@
1
1
  # SKILLS/DELPHI-REVIEW KNOWLEDGE BASE
2
2
 
3
3
  **Generated:** 2026-10-30
4
- **Commit:** 3978d361
4
+ **Commit:** 6091e238
5
5
  **Branch:** main
6
- **Version:** 0.35.13.0
6
+ **Version:** 0.35.15.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.
@@ -384,6 +384,38 @@ Qoder 的内置模型**没有本地推理端点**:推理请求由 Qoder 客户
384
384
  - 项目级:`<project>/.qoder/agents/`(`xp-gate init`)
385
385
  - 用户级:`~/.qoder/agents/`(`xp-gate init --global`)
386
386
 
387
+ #### 推荐路径:external-only(#646)
388
+
389
+ 上面整条 native 通道在本机**实测不可用**。所以推荐配置是**关掉它**,只走外部 provider —— 这是唯一能满足三模型契约的路径,也是这份文档的默认建议。
390
+
391
+ **开启方式**(改一次机器级偏好,对所有仓库生效):
392
+
393
+ ```bash
394
+ # 1. 移除已部署的 native agent(幂等,重复执行不报错)
395
+ xp-gate delphi-setup --uninstall --global
396
+ # 或项目级:xp-gate delphi-setup --uninstall --local --project <dir>
397
+
398
+ # 2. 写通道偏好,阻止 `xp-gate init` 再装回来
399
+ # ~/.config/xp-gate/.delphi-config.json
400
+ # { "channels": ["external"] }
401
+ ```
402
+
403
+ 临时覆盖(CI、一次性运行):`XP_GATE_DELPHI_CHANNEL=external`。
404
+
405
+ **生效范围**:
406
+
407
+ | 位置 | external-only 时的行为 |
408
+ |---|---|
409
+ | `xp-gate init` / `init --global` | 不部署 `delphi-*.md`,打印 `SKIP (channels=external via config)` |
410
+ | `xp-gate doctor delphi` | native 检查报 **SKIP** 并注明来源,**不再 FAIL**;外部链路(config / runner / 三席互异)照常逐项校验 |
411
+ | 本技能的通道决策表 | 直接走 external,不再探测 native |
412
+
413
+ **为什么偏好放在机器级**:通道选择是"这台机器上 Delphi 怎么跑",与具体仓库无关。放进项目文件会让每个仓库各写一遍。
414
+
415
+ **为什么不默认 external-only**:那会悄悄改掉所有没提要求的人的行为。默认保持两条通道都可用,由用户显式关闭。
416
+
417
+ **doctor 的 SKIP 不等于绿灯**:它带着理由(`channels=external via config`),和"没装"、"坏了"能区分开。这是本项目所有门禁的统一规则——跳过必须说明为什么跳。
418
+
387
419
  **model 字段格式**:仍须写成 `"[DisplayName](modelId)"`。裸名字或已下线 ID 不会报错,会被平台静默忽略。`xp-gate init` 会审计已部署的 `delphi-*.md`,格式非法或 modelId 与本版本模板不一致时点名文件并给出"删除 → 重跑 `init` → 重启会话"提示;因为部署语义是"已存在不覆盖",从 #417 之前的版本升级必须手工删旧文件再重跑 `init`。注意:修好绑定也只是让角色声明规范,并不改变上表的执行模型结论。
388
420
 
389
421
  **执行方式**:用 Agent tool 并行派发 3 个 subagent,`subagent_type` 分别取 `delphi-architecture`、`delphi-technical`、`delphi-feasibility`。**新建或修改 agent 文件后必须重开会话**——注册表只在会话启动时加载,否则报 `Unknown agent type`。
@@ -1,9 +1,9 @@
1
1
  # SKILLS/SPRINT-FLOW KNOWLEDGE BASE
2
2
 
3
3
  **Generated:** 2026-10-30
4
- **Commit:** 3978d361
4
+ **Commit:** 6091e238
5
5
  **Branch:** main
6
- **Version:** 0.35.13.0
6
+ **Version:** 0.35.15.0
7
7
 
8
8
  ## OVERVIEW
9
9
  **6-phase** development pipeline (v2.0 compact redesign, Issue #290): PREP → DESIGN → BUILD → VERIFY → SHIP → CLOSE. Phase 3/6 BUILD default build mode is **ralph-loop** (REQ-level iteration, 40-67% token savings vs parallel). HARD-GATE between DESIGN (2/6) and BUILD (3/6): design must pass Delphi review (≥90% consensus) before any coding.
@@ -1,9 +1,9 @@
1
1
  # SKILLS/TEST-SPECIFICATION-ALIGNMENT KNOWLEDGE BASE
2
2
 
3
3
  **Generated:** 2026-10-30
4
- **Commit:** 3978d361
4
+ **Commit:** 6091e238
5
5
  **Branch:** main
6
- **Version:** 0.35.13.0
6
+ **Version:** 0.35.15.0
7
7
 
8
8
  ## OVERVIEW
9
9
  Test-Specification Alignment Engine — two-stage validation ensuring tests accurately reflect requirements and design specs.
@@ -2,7 +2,7 @@ import { describe, it, expect } from "vitest"
2
2
  import type { Context } from "@deepseek-ai/cordis"
3
3
  import type { ShellExecRequest, ShellExecSpec, ShellRunResult } from "@deepseek-ai/dsh-shell"
4
4
  import type { SkillProvider, SkillProviderControl, SkillProviderObservation } from "@deepseek-ai/dsh-skill"
5
- import { apply, inject, renderText } from "./index.js"
5
+ import { apply, findPlaceholderName, inject, renderText } from "./index.js"
6
6
 
7
7
  type ProviderFactory = (control: SkillProviderControl) => SkillProvider
8
8
 
@@ -186,4 +186,46 @@ describe("apply", () => {
186
186
  // [] would look to the host like the tool never answered.
187
187
  expect(renderText({ path: "x" }, "")).toEqual([{ type: "text", text: "" }])
188
188
  })
189
- })
189
+
190
+ /**
191
+ * @test REQ-562
192
+ * @intent `findPlaceholderName` 处在**安全路径**上:`resolveExpressionKey` 把"没找到占位符"
193
+ * 解释为"用户直接写了一个明文 token",并据此拒绝。所以这里正则错一下不是美观问题——
194
+ * 对明文 key 误判为占位符会**直接放行**,对合法引用漏判会拒绝正确配置。
195
+ * @covers AC-562-05
196
+ */
197
+ describe("findPlaceholderName", () => {
198
+ it("AC-562-05: recognizes the ${VAR} spelling", () => {
199
+ expect(findPlaceholderName("${WHALECLOUD_API_KEY}")).toBe("WHALECLOUD_API_KEY")
200
+ })
201
+
202
+ it("AC-562-05: recognizes the {env:VAR} spelling", () => {
203
+ expect(findPlaceholderName("{env:OPENAI_API_KEY}")).toBe("OPENAI_API_KEY")
204
+ })
205
+
206
+ it("AC-562-05: ${VAR} wins when a value carries both spellings", () => {
207
+ // Order matters: the shell form is checked first, so a value mixing both
208
+ // resolves deterministically rather than depending on regex engine order.
209
+ expect(findPlaceholderName("{env:A}${B}")).toBe("B")
210
+ })
211
+
212
+ it("AC-562-05: a literal token yields undefined -- this is what triggers the reject", () => {
213
+ // The security-relevant case: a real key must NOT look like a placeholder.
214
+ expect(findPlaceholderName("sk-live-abcdef123456")).toBeUndefined()
215
+ expect(findPlaceholderName("")).toBeUndefined()
216
+ })
217
+
218
+ it("AC-562-05: near-misses that must not count as placeholders", () => {
219
+ // A lowercase-first name, a digit-first name, and an empty ${} are all invalid
220
+ // identifiers; treating any of them as a reference would bypass the guard.
221
+ expect(findPlaceholderName("${1BAD}")).toBeUndefined()
222
+ expect(findPlaceholderName("${}")).toBeUndefined()
223
+ expect(findPlaceholderName("$VAR")).toBeUndefined()
224
+ expect(findPlaceholderName("{env:}")).toBeUndefined()
225
+ })
226
+
227
+ it("AC-562-05: a placeholder embedded in surrounding text is still found", () => {
228
+ expect(findPlaceholderName("Bearer ${TOKEN}")).toBe("TOKEN")
229
+ })
230
+ })
231
+ })
@@ -312,8 +312,16 @@ async function resolveExpressionKey(expression: string, resolveApiKey: ApiKeyRes
312
312
  )
313
313
  }
314
314
 
315
- /** The first `${VAR}` / `{env:VAR}` name in a config value, or undefined. */
316
- function findPlaceholderName(value: string): string | undefined {
315
+ /**
316
+ * The first `${VAR}` / `{env:VAR}` name in a config value, or undefined.
317
+ *
318
+ * Exported for tests (#562). This one-line function is on the SECURITY path:
319
+ * `resolveExpressionKey` treats "no placeholder found" as "the user typed a literal
320
+ * token", and rejects it outright so the credential never lands in a profile file.
321
+ * So a regex mistake here is not cosmetic -- a false positive on a literal key would
322
+ * let it through, and a false negative would reject a perfectly valid reference.
323
+ */
324
+ export function findPlaceholderName(value: string): string | undefined {
317
325
  const shell = /\$\{([A-Za-z_][A-Za-z0-9_]*)\}/.exec(value)
318
326
  if (shell) return shell[1]
319
327
  const envStyle = /\{env:([A-Za-z_][A-Za-z0-9_]*)\}/.exec(value)
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@boyingliu01/opencode-plugin",
3
- "version": "0.35.13",
3
+ "version": "0.35.15",
4
4
  "type": "module",
5
5
  "main": "index.ts",
6
6
  "description": "XP-Gate quality gates + AI workflow skills + Sprint Flow TUI sidebar for OpenCode",
@@ -1,9 +1,9 @@
1
1
  # SKILLS/DELPHI-REVIEW KNOWLEDGE BASE
2
2
 
3
3
  **Generated:** 2026-10-30
4
- **Commit:** 3978d361
4
+ **Commit:** 6091e238
5
5
  **Branch:** main
6
- **Version:** 0.35.13.0
6
+ **Version:** 0.35.15.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.
@@ -384,6 +384,38 @@ Qoder 的内置模型**没有本地推理端点**:推理请求由 Qoder 客户
384
384
  - 项目级:`<project>/.qoder/agents/`(`xp-gate init`)
385
385
  - 用户级:`~/.qoder/agents/`(`xp-gate init --global`)
386
386
 
387
+ #### 推荐路径:external-only(#646)
388
+
389
+ 上面整条 native 通道在本机**实测不可用**。所以推荐配置是**关掉它**,只走外部 provider —— 这是唯一能满足三模型契约的路径,也是这份文档的默认建议。
390
+
391
+ **开启方式**(改一次机器级偏好,对所有仓库生效):
392
+
393
+ ```bash
394
+ # 1. 移除已部署的 native agent(幂等,重复执行不报错)
395
+ xp-gate delphi-setup --uninstall --global
396
+ # 或项目级:xp-gate delphi-setup --uninstall --local --project <dir>
397
+
398
+ # 2. 写通道偏好,阻止 `xp-gate init` 再装回来
399
+ # ~/.config/xp-gate/.delphi-config.json
400
+ # { "channels": ["external"] }
401
+ ```
402
+
403
+ 临时覆盖(CI、一次性运行):`XP_GATE_DELPHI_CHANNEL=external`。
404
+
405
+ **生效范围**:
406
+
407
+ | 位置 | external-only 时的行为 |
408
+ |---|---|
409
+ | `xp-gate init` / `init --global` | 不部署 `delphi-*.md`,打印 `SKIP (channels=external via config)` |
410
+ | `xp-gate doctor delphi` | native 检查报 **SKIP** 并注明来源,**不再 FAIL**;外部链路(config / runner / 三席互异)照常逐项校验 |
411
+ | 本技能的通道决策表 | 直接走 external,不再探测 native |
412
+
413
+ **为什么偏好放在机器级**:通道选择是"这台机器上 Delphi 怎么跑",与具体仓库无关。放进项目文件会让每个仓库各写一遍。
414
+
415
+ **为什么不默认 external-only**:那会悄悄改掉所有没提要求的人的行为。默认保持两条通道都可用,由用户显式关闭。
416
+
417
+ **doctor 的 SKIP 不等于绿灯**:它带着理由(`channels=external via config`),和"没装"、"坏了"能区分开。这是本项目所有门禁的统一规则——跳过必须说明为什么跳。
418
+
387
419
  **model 字段格式**:仍须写成 `"[DisplayName](modelId)"`。裸名字或已下线 ID 不会报错,会被平台静默忽略。`xp-gate init` 会审计已部署的 `delphi-*.md`,格式非法或 modelId 与本版本模板不一致时点名文件并给出"删除 → 重跑 `init` → 重启会话"提示;因为部署语义是"已存在不覆盖",从 #417 之前的版本升级必须手工删旧文件再重跑 `init`。注意:修好绑定也只是让角色声明规范,并不改变上表的执行模型结论。
388
420
 
389
421
  **执行方式**:用 Agent tool 并行派发 3 个 subagent,`subagent_type` 分别取 `delphi-architecture`、`delphi-technical`、`delphi-feasibility`。**新建或修改 agent 文件后必须重开会话**——注册表只在会话启动时加载,否则报 `Unknown agent type`。
@@ -1,9 +1,9 @@
1
1
  # SKILLS/SPRINT-FLOW KNOWLEDGE BASE
2
2
 
3
3
  **Generated:** 2026-10-30
4
- **Commit:** 3978d361
4
+ **Commit:** 6091e238
5
5
  **Branch:** main
6
- **Version:** 0.35.13.0
6
+ **Version:** 0.35.15.0
7
7
 
8
8
  ## OVERVIEW
9
9
  **6-phase** development pipeline (v2.0 compact redesign, Issue #290): PREP → DESIGN → BUILD → VERIFY → SHIP → CLOSE. Phase 3/6 BUILD default build mode is **ralph-loop** (REQ-level iteration, 40-67% token savings vs parallel). HARD-GATE between DESIGN (2/6) and BUILD (3/6): design must pass Delphi review (≥90% consensus) before any coding.
@@ -1,9 +1,9 @@
1
1
  # SKILLS/TEST-SPECIFICATION-ALIGNMENT KNOWLEDGE BASE
2
2
 
3
3
  **Generated:** 2026-10-30
4
- **Commit:** 3978d361
4
+ **Commit:** 6091e238
5
5
  **Branch:** main
6
- **Version:** 0.35.13.0
6
+ **Version:** 0.35.15.0
7
7
 
8
8
  ## OVERVIEW
9
9
  Test-Specification Alignment Engine — two-stage validation ensuring tests accurately reflect requirements and design specs.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "xp-gate",
3
- "version": "0.35.13",
3
+ "version": "0.35.15",
4
4
  "displayName": "XP-Gate",
5
5
  "description": "Extreme Programming quality gates + AI workflow skills for Qoder. Includes 12 pre-commit gates (Gate 0-11), 8 pre-push gates, Sprint Flow (6 phases), and Delphi multi-expert review (>=90% consensus).",
6
6
  "author": {
@@ -1,9 +1,9 @@
1
1
  # SKILLS/DELPHI-REVIEW KNOWLEDGE BASE
2
2
 
3
3
  **Generated:** 2026-10-30
4
- **Commit:** 3978d361
4
+ **Commit:** 6091e238
5
5
  **Branch:** main
6
- **Version:** 0.35.13.0
6
+ **Version:** 0.35.15.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.
@@ -384,6 +384,38 @@ Qoder 的内置模型**没有本地推理端点**:推理请求由 Qoder 客户
384
384
  - 项目级:`<project>/.qoder/agents/`(`xp-gate init`)
385
385
  - 用户级:`~/.qoder/agents/`(`xp-gate init --global`)
386
386
 
387
+ #### 推荐路径:external-only(#646)
388
+
389
+ 上面整条 native 通道在本机**实测不可用**。所以推荐配置是**关掉它**,只走外部 provider —— 这是唯一能满足三模型契约的路径,也是这份文档的默认建议。
390
+
391
+ **开启方式**(改一次机器级偏好,对所有仓库生效):
392
+
393
+ ```bash
394
+ # 1. 移除已部署的 native agent(幂等,重复执行不报错)
395
+ xp-gate delphi-setup --uninstall --global
396
+ # 或项目级:xp-gate delphi-setup --uninstall --local --project <dir>
397
+
398
+ # 2. 写通道偏好,阻止 `xp-gate init` 再装回来
399
+ # ~/.config/xp-gate/.delphi-config.json
400
+ # { "channels": ["external"] }
401
+ ```
402
+
403
+ 临时覆盖(CI、一次性运行):`XP_GATE_DELPHI_CHANNEL=external`。
404
+
405
+ **生效范围**:
406
+
407
+ | 位置 | external-only 时的行为 |
408
+ |---|---|
409
+ | `xp-gate init` / `init --global` | 不部署 `delphi-*.md`,打印 `SKIP (channels=external via config)` |
410
+ | `xp-gate doctor delphi` | native 检查报 **SKIP** 并注明来源,**不再 FAIL**;外部链路(config / runner / 三席互异)照常逐项校验 |
411
+ | 本技能的通道决策表 | 直接走 external,不再探测 native |
412
+
413
+ **为什么偏好放在机器级**:通道选择是"这台机器上 Delphi 怎么跑",与具体仓库无关。放进项目文件会让每个仓库各写一遍。
414
+
415
+ **为什么不默认 external-only**:那会悄悄改掉所有没提要求的人的行为。默认保持两条通道都可用,由用户显式关闭。
416
+
417
+ **doctor 的 SKIP 不等于绿灯**:它带着理由(`channels=external via config`),和"没装"、"坏了"能区分开。这是本项目所有门禁的统一规则——跳过必须说明为什么跳。
418
+
387
419
  **model 字段格式**:仍须写成 `"[DisplayName](modelId)"`。裸名字或已下线 ID 不会报错,会被平台静默忽略。`xp-gate init` 会审计已部署的 `delphi-*.md`,格式非法或 modelId 与本版本模板不一致时点名文件并给出"删除 → 重跑 `init` → 重启会话"提示;因为部署语义是"已存在不覆盖",从 #417 之前的版本升级必须手工删旧文件再重跑 `init`。注意:修好绑定也只是让角色声明规范,并不改变上表的执行模型结论。
388
420
 
389
421
  **执行方式**:用 Agent tool 并行派发 3 个 subagent,`subagent_type` 分别取 `delphi-architecture`、`delphi-technical`、`delphi-feasibility`。**新建或修改 agent 文件后必须重开会话**——注册表只在会话启动时加载,否则报 `Unknown agent type`。
@@ -1,9 +1,9 @@
1
1
  # SKILLS/SPRINT-FLOW KNOWLEDGE BASE
2
2
 
3
3
  **Generated:** 2026-10-30
4
- **Commit:** 3978d361
4
+ **Commit:** 6091e238
5
5
  **Branch:** main
6
- **Version:** 0.35.13.0
6
+ **Version:** 0.35.15.0
7
7
 
8
8
  ## OVERVIEW
9
9
  **6-phase** development pipeline (v2.0 compact redesign, Issue #290): PREP → DESIGN → BUILD → VERIFY → SHIP → CLOSE. Phase 3/6 BUILD default build mode is **ralph-loop** (REQ-level iteration, 40-67% token savings vs parallel). HARD-GATE between DESIGN (2/6) and BUILD (3/6): design must pass Delphi review (≥90% consensus) before any coding.
@@ -1,9 +1,9 @@
1
1
  # SKILLS/TEST-SPECIFICATION-ALIGNMENT KNOWLEDGE BASE
2
2
 
3
3
  **Generated:** 2026-10-30
4
- **Commit:** 3978d361
4
+ **Commit:** 6091e238
5
5
  **Branch:** main
6
- **Version:** 0.35.13.0
6
+ **Version:** 0.35.15.0
7
7
 
8
8
  ## OVERVIEW
9
9
  Test-Specification Alignment Engine — two-stage validation ensuring tests accurately reflect requirements and design specs.
@@ -1,9 +1,9 @@
1
1
  # PRINCIPLES CHECKER MODULE
2
2
 
3
3
  **Generated:** 2026-10-30
4
- **Commit:** 3978d361
4
+ **Commit:** 6091e238
5
5
  **Branch:** main
6
- **Version:** 0.35.13.0
6
+ **Version:** 0.35.15.0
7
7
 
8
8
  ## OVERVIEW
9
9
  Clean Code & SOLID principles checker — **Gate 4** of pre-commit. 14 rules × 9 language adapters, SARIF 2.1.0 output. Houses the **Boy Scout Rule** enforcement engine (Gate 6) and warning-baseline storage.
@@ -1,9 +1,9 @@
1
1
  # SKILLS/DELPHI-REVIEW KNOWLEDGE BASE
2
2
 
3
3
  **Generated:** 2026-10-30
4
- **Commit:** 3978d361
4
+ **Commit:** 6091e238
5
5
  **Branch:** main
6
- **Version:** 0.35.13.0
6
+ **Version:** 0.35.15.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.