@boyingliu01/xp-gate 0.32.1 → 0.32.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (47) hide show
  1. package/adapters/powershell.sh +33 -7
  2. package/gate-3.sh +37 -2
  3. package/hooks/gate-3.sh +37 -2
  4. package/lib/mattpocock-license-verdict.json +1 -1
  5. package/mock-policy/AGENTS.md +3 -3
  6. package/mutation/AGENTS.md +3 -3
  7. package/package.json +1 -1
  8. package/plugins/claude-code/.claude-plugin/plugin.json +1 -1
  9. package/plugins/claude-code/skills/delphi-review/AGENTS.md +3 -3
  10. package/plugins/claude-code/skills/sprint-flow/AGENTS.md +3 -3
  11. package/plugins/claude-code/skills/test-specification-alignment/AGENTS.md +3 -3
  12. package/plugins/dsh/README.md +51 -2
  13. package/plugins/dsh/client/client.js +760 -0
  14. package/plugins/dsh/lib/client.js +760 -0
  15. package/plugins/dsh/lib/delphi-settings.d.ts +52 -0
  16. package/plugins/dsh/lib/delphi-settings.d.ts.map +1 -0
  17. package/plugins/dsh/lib/delphi-settings.js +76 -0
  18. package/plugins/dsh/lib/hooks.d.ts +14 -1
  19. package/plugins/dsh/lib/hooks.d.ts.map +1 -1
  20. package/plugins/dsh/lib/hooks.js +44 -5
  21. package/plugins/dsh/lib/index.d.ts +63 -0
  22. package/plugins/dsh/lib/index.d.ts.map +1 -1
  23. package/plugins/dsh/lib/index.js +126 -21
  24. package/plugins/dsh/package.json +13 -3
  25. package/plugins/dsh/scripts/copy-client.cjs +32 -0
  26. package/plugins/dsh/skills/delphi-review/AGENTS.md +3 -3
  27. package/plugins/dsh/skills/sprint-flow/AGENTS.md +3 -3
  28. package/plugins/dsh/skills/test-specification-alignment/AGENTS.md +3 -3
  29. package/plugins/dsh/src/client.test.ts +231 -0
  30. package/plugins/dsh/src/delphi-settings.test.ts +101 -0
  31. package/plugins/dsh/src/delphi-settings.ts +111 -0
  32. package/plugins/dsh/src/hooks.test.ts +48 -0
  33. package/plugins/dsh/src/hooks.ts +45 -5
  34. package/plugins/dsh/src/index.test.ts +3 -2
  35. package/plugins/dsh/src/index.ts +147 -25
  36. package/plugins/opencode/package.json +1 -1
  37. package/plugins/opencode/skills/delphi-review/AGENTS.md +3 -3
  38. package/plugins/opencode/skills/sprint-flow/AGENTS.md +3 -3
  39. package/plugins/opencode/skills/test-specification-alignment/AGENTS.md +3 -3
  40. package/plugins/qoder/plugin.json +1 -1
  41. package/plugins/qoder/skills/delphi-review/AGENTS.md +3 -3
  42. package/plugins/qoder/skills/sprint-flow/AGENTS.md +3 -3
  43. package/plugins/qoder/skills/test-specification-alignment/AGENTS.md +3 -3
  44. package/principles/AGENTS.md +3 -3
  45. package/skills/delphi-review/AGENTS.md +3 -3
  46. package/skills/sprint-flow/AGENTS.md +3 -3
  47. package/skills/test-specification-alignment/AGENTS.md +3 -3
@@ -268,6 +268,19 @@ run_static_analysis() {
268
268
  return 3
269
269
  fi
270
270
  fi
271
+ # Downstream hardening (ported): a scope file narrows the gate to product
272
+ # code, but it must be analysed under a DECLARED rule set. With no
273
+ # PSScriptAnalyzerSettings.psd1 the -Settings binding falls back to defaults,
274
+ # so a repo that declared its scope silently swapped rule sets. Refuse the
275
+ # run rather than grade against an unknown rule set.
276
+ #
277
+ # Mirrors the missing-module semantics (return 3 = fail closed upstream).
278
+ if [ "${#analysis_targets[@]}" -gt 0 ] && [ ! -f "PSScriptAnalyzerSettings.psd1" ]; then
279
+ echo "XP_G1_REASON: analysis-settings-missing:PSScriptAnalyzerSettings.psd1"
280
+ echo " ⚠️ $scope_file is in use but PSScriptAnalyzerSettings.psd1 is absent - refusing to analyse with an unknown rule set"
281
+ return 3
282
+ fi
283
+
271
284
 
272
285
  if [ "${#analysis_targets[@]}" -gt 0 ]; then
273
286
  # Scoped per-path run. -Path binds ONE string only (an array argument
@@ -430,14 +443,27 @@ run_lint() {
430
443
  # A hanging Pester test (e.g. a test that dot-sources a script whose body performs
431
444
  # WMI/registry/scheduled-task scans) would otherwise block `git commit` forever.
432
445
  # Default 300s, override via XP_GATE_POWERSHELL_TEST_TIMEOUT_S.
446
+ # #599: ONE helper, TWO budgets. The instrumented coverage run enables
447
+ # CodeCoverage, so instrumentation alone costs more than a plain run: a shared
448
+ # 300s budget is not the same headroom for both. The caller passes its default
449
+ # explicitly and the timeout message names the variable that actually governs it,
450
+ # otherwise a budget shortfall gets reported as "a test is hanging" and the
451
+ # reader is sent to the wrong place.
433
452
  _pester_timeout() {
434
- local default_s="${XP_GATE_POWERSHELL_TEST_TIMEOUT_S:-300}"
453
+ local budget_var="$1"
454
+ local default_s="${2:-300}"
455
+ local effective_s
456
+ if [ "$budget_var" = "XP_GATE_POWERSHELL_COVERAGE_TIMEOUT_S" ]; then
457
+ effective_s="${XP_GATE_POWERSHELL_COVERAGE_TIMEOUT_S:-$default_s}"
458
+ else
459
+ effective_s="${XP_GATE_POWERSHELL_TEST_TIMEOUT_S:-$default_s}"
460
+ fi
435
461
  # Prefer GNU timeout (Git Bash / Linux). On Windows where `timeout` resolves to
436
462
  # the native timeout.exe, fall back to running without a hard timeout.
437
463
  if command -v timeout >/dev/null 2>&1 && timeout --version 2>/dev/null | grep -qi "coreutils"; then
438
- timeout "${default_s}s" "$@"
464
+ timeout "${effective_s}s" "${@:3}"
439
465
  else
440
- "$@"
466
+ "${@:3}"
441
467
  fi
442
468
  }
443
469
 
@@ -465,7 +491,7 @@ run_tests() {
465
491
  if [ "${#test_files[@]}" -gt 0 ]; then
466
492
  test_paths=$(_powershell_path_array "${test_files[@]}")
467
493
  echo "Running Pester tests..."
468
- _pester_timeout "$PWSH" "${POWERSHELL_ARGS[@]}" "
494
+ _pester_timeout "XP_GATE_POWERSHELL_TEST_TIMEOUT_S" "${XP_GATE_POWERSHELL_TEST_TIMEOUT_S:-300}" "$PWSH" "${POWERSHELL_ARGS[@]}" "
469
495
  \$results = Invoke-Pester -Path $test_paths -PassThru
470
496
  if (\$results.FailedCount -gt 0) {
471
497
  Write-Host \"FAILED: \$(\$results.FailedCount) test(s)\"
@@ -476,7 +502,7 @@ run_tests() {
476
502
  "
477
503
  local run_exit=$?
478
504
  if [ "$run_exit" = "124" ]; then
479
- echo "⚠️ Pester tests TIMED OUT after ${XP_GATE_POWERSHELL_TEST_TIMEOUT_S:-300}s (may be hanging)."
505
+ echo "⚠️ Pester tests TIMED OUT after ${XP_GATE_POWERSHELL_TEST_TIMEOUT_S:-300}s (XP_GATE_POWERSHELL_TEST_TIMEOUT_S; may be hanging)."
480
506
  echo " A test may be blocking on a system call (WMI/registry/scheduled-task)."
481
507
  echo " Fix the hanging test, or set XP_GATE_POWERSHELL_TEST_TIMEOUT_S to a higher value."
482
508
  return 1
@@ -572,7 +598,7 @@ run_coverage() {
572
598
  # is the deprecated Pester 4 parameter set and fails outright on Pester 5.x
573
599
  # ("cannot resolve parameter set"). Using it silently produced no coverage.xml,
574
600
  # which the pre-commit hook then reported as an unparseable/zero result.
575
- _pester_timeout "$PWSH" "${POWERSHELL_ARGS[@]}" "
601
+ _pester_timeout "XP_GATE_POWERSHELL_COVERAGE_TIMEOUT_S" "${XP_GATE_POWERSHELL_COVERAGE_TIMEOUT_S:-900}" "$PWSH" "${POWERSHELL_ARGS[@]}" "
576
602
  \$cfg = New-PesterConfiguration
577
603
  \$cfg.Run.Path = $test_paths
578
604
  \$cfg.CodeCoverage.Enabled = \$true
@@ -587,7 +613,7 @@ run_coverage() {
587
613
  " 2>&1
588
614
  local cov_exit=$?
589
615
  if [ "$cov_exit" = "124" ]; then
590
- echo "⚠️ Pester coverage TIMED OUT after ${XP_GATE_POWERSHELL_TEST_TIMEOUT_S:-300}s (may be hanging)."
616
+ echo "⚠️ Pester coverage TIMED OUT after ${XP_GATE_POWERSHELL_COVERAGE_TIMEOUT_S:-900}s (XP_GATE_POWERSHELL_COVERAGE_TIMEOUT_S; instrumentation costs more than a plain run - raise this rather than suspecting a hang)."
591
617
  return 1
592
618
  fi
593
619
  return $cov_exit
package/gate-3.sh CHANGED
@@ -176,7 +176,42 @@ elif [ "$PROJECT_LANG" = "powershell" ]; then
176
176
  known.set(norm(e.file) + "::" + e.name, Number(e.ccn) || 0);
177
177
  }
178
178
  }
179
- function norm(p) { return String(p).replace(/\\\\/g, "/").replace(/^\.\//, ""); }
179
+ // The measurement emits ABSOLUTE paths (pwsh resolved them), while
180
+ // a baseline recorded by a repo-side generator holds paths relative
181
+ // to the repo root. Normalising alone cannot bridge that, so a key
182
+ // miss falls back to a suffix match on the file path: exactly one
183
+ // known entry whose normalised path ends with "<sep>measured" is the
184
+ // same file. Without this every existing function looked brand new,
185
+ // and a real regression was reported as "new function" -- the right
186
+ // verdict for the wrong reason.
187
+ //
188
+ // Backslashes are normalised with split/join, NOT -replace: this JS
189
+ // is embedded in a double-quoted bash command, where a regex escape
190
+ // loses a level and silently matches the wrong number of
191
+ // backslashes. Measured: /\\\\/g arrived as /\\/g, matched only
192
+ // doubled separators, and every Windows path kept its backslashes.
193
+ function norm(p) {
194
+ return String(p)
195
+ .split("\\").join("/")
196
+ .replace(/^[A-Za-z]:\//, "")
197
+ .replace(/^\.\//, "")
198
+ .replace(/^\//, "");
199
+ }
200
+ function lookup(path, name) {
201
+ const key = norm(path) + "::" + name;
202
+ if (known.has(key)) return known.get(key);
203
+ const target = norm(path);
204
+ let hit = null;
205
+ for (const [k, v] of known) {
206
+ const [kp, kn] = k.split("::");
207
+ if (kn !== name) continue;
208
+ if (target.endsWith("/" + kp) || kp.endsWith("/" + target) || kp === target) {
209
+ if (hit !== null && hit !== v) return null; // ambiguous: treat as new
210
+ hit = v;
211
+ }
212
+ }
213
+ return hit;
214
+ }
180
215
  const seen = new Set();
181
216
  const regressions = [];
182
217
  const improved = [];
@@ -186,7 +221,7 @@ elif [ "$PROJECT_LANG" = "powershell" ]; then
186
221
  const key = norm(m[1]) + "::" + m[3];
187
222
  const now = Number(m[4]);
188
223
  seen.add(key);
189
- const was = known.has(key) ? known.get(key) : null;
224
+ const was = lookup(m[1], m[3]);
190
225
  if (was === null) {
191
226
  if (now > threshold) {
192
227
  regressions.push(m[1] + ":" + m[2] + " " + m[3] + " new function at ccn=" + now);
package/hooks/gate-3.sh CHANGED
@@ -176,7 +176,42 @@ elif [ "$PROJECT_LANG" = "powershell" ]; then
176
176
  known.set(norm(e.file) + "::" + e.name, Number(e.ccn) || 0);
177
177
  }
178
178
  }
179
- function norm(p) { return String(p).replace(/\\\\/g, "/").replace(/^\.\//, ""); }
179
+ // The measurement emits ABSOLUTE paths (pwsh resolved them), while
180
+ // a baseline recorded by a repo-side generator holds paths relative
181
+ // to the repo root. Normalising alone cannot bridge that, so a key
182
+ // miss falls back to a suffix match on the file path: exactly one
183
+ // known entry whose normalised path ends with "<sep>measured" is the
184
+ // same file. Without this every existing function looked brand new,
185
+ // and a real regression was reported as "new function" -- the right
186
+ // verdict for the wrong reason.
187
+ //
188
+ // Backslashes are normalised with split/join, NOT -replace: this JS
189
+ // is embedded in a double-quoted bash command, where a regex escape
190
+ // loses a level and silently matches the wrong number of
191
+ // backslashes. Measured: /\\\\/g arrived as /\\/g, matched only
192
+ // doubled separators, and every Windows path kept its backslashes.
193
+ function norm(p) {
194
+ return String(p)
195
+ .split("\\").join("/")
196
+ .replace(/^[A-Za-z]:\//, "")
197
+ .replace(/^\.\//, "")
198
+ .replace(/^\//, "");
199
+ }
200
+ function lookup(path, name) {
201
+ const key = norm(path) + "::" + name;
202
+ if (known.has(key)) return known.get(key);
203
+ const target = norm(path);
204
+ let hit = null;
205
+ for (const [k, v] of known) {
206
+ const [kp, kn] = k.split("::");
207
+ if (kn !== name) continue;
208
+ if (target.endsWith("/" + kp) || kp.endsWith("/" + target) || kp === target) {
209
+ if (hit !== null && hit !== v) return null; // ambiguous: treat as new
210
+ hit = v;
211
+ }
212
+ }
213
+ return hit;
214
+ }
180
215
  const seen = new Set();
181
216
  const regressions = [];
182
217
  const improved = [];
@@ -186,7 +221,7 @@ elif [ "$PROJECT_LANG" = "powershell" ]; then
186
221
  const key = norm(m[1]) + "::" + m[3];
187
222
  const now = Number(m[4]);
188
223
  seen.add(key);
189
- const was = known.has(key) ? known.get(key) : null;
224
+ const was = lookup(m[1], m[3]);
190
225
  if (was === null) {
191
226
  if (now > threshold) {
192
227
  regressions.push(m[1] + ":" + m[2] + " " + m[3] + " new function at ccn=" + now);
@@ -2,6 +2,6 @@
2
2
  "verdict": "PASS",
3
3
  "license_type": "MIT",
4
4
  "sha256": "0e7ac423bf2c6e223b7c5b156f8cf72da49d748e56a1641402c31f22ad07dbb5",
5
- "checked_at": "2026-10-29T23:47:25.119Z",
5
+ "checked_at": "2026-10-30T08:22:09.989Z",
6
6
  "source": "network"
7
7
  }
@@ -1,9 +1,9 @@
1
1
  # SRC/MOCK-POLICY KNOWLEDGE BASE
2
2
 
3
- **Generated:** 2026-10-29
4
- **Commit:** 101a0ff3
3
+ **Generated:** 2026-10-30
4
+ **Commit:** 78786c3f
5
5
  **Branch:** main
6
- **Version:** 0.32.1.0
6
+ **Version:** 0.32.2.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
- **Generated:** 2026-10-29
4
- **Commit:** 101a0ff3
3
+ **Generated:** 2026-10-30
4
+ **Commit:** 78786c3f
5
5
  **Branch:** main
6
- **Version:** 0.32.1.0
6
+ **Version:** 0.32.2.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.32.1",
3
+ "version": "0.32.2",
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.32.1",
3
+ "version": "0.32.2",
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
- **Generated:** 2026-10-29
4
- **Commit:** 101a0ff3
3
+ **Generated:** 2026-10-30
4
+ **Commit:** 78786c3f
5
5
  **Branch:** main
6
- **Version:** 0.32.1.0
6
+ **Version:** 0.32.2.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.
@@ -1,9 +1,9 @@
1
1
  # SKILLS/SPRINT-FLOW KNOWLEDGE BASE
2
2
 
3
- **Generated:** 2026-10-29
4
- **Commit:** 101a0ff3
3
+ **Generated:** 2026-10-30
4
+ **Commit:** 78786c3f
5
5
  **Branch:** main
6
- **Version:** 0.32.1.0
6
+ **Version:** 0.32.2.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
- **Generated:** 2026-10-29
4
- **Commit:** 101a0ff3
3
+ **Generated:** 2026-10-30
4
+ **Commit:** 78786c3f
5
5
  **Branch:** main
6
- **Version:** 0.32.1.0
6
+ **Version:** 0.32.2.0
7
7
 
8
8
  ## OVERVIEW
9
9
  Test-Specification Alignment Engine — two-stage validation ensuring tests accurately reflect requirements and design specs.
@@ -124,6 +124,48 @@ Delphi 方法的价值**完全建立在「评审者是不同模型」之上**。
124
124
  `{ ok: true }` 是**无条件**返回的常量,"把退出码契约换成了一个常量,而不是真正的结果信号"。
125
125
  机制**拒绝**给出共识,而不是盖章通过 —— 这正是它该有的行为。
126
126
 
127
+ ## 配置界面(Plugins 页)
128
+
129
+ 插件声明了 `dsh.client`(`platform: web`),在 Web GUI 里注册一个**行配置页**:
130
+
131
+ **侧边栏 Plugins → `@boyingliu01/dsh-plugin-xp-gate` → 组件 `tool-xp-gate` → 配置**
132
+
133
+ 该页面可以编辑 Delphi 三席(角色 / 模型)、网关、密钥引用与共识参数;模型可从
134
+ **宿主已配置的 provider 目录**下拉选择,也可手填。
135
+
136
+ ### 来源优先级
137
+
138
+ | 优先级 | 来源 | 说明 |
139
+ |---|---|---|
140
+ | 1 | 本插件配置里的 `delphi` 段(本页写入) | 一旦保存了席位,评审就用它 |
141
+ | 2 | 项目根 `.delphi-config.json` | 席位为空时自动回落,老项目**无需改动** |
142
+
143
+ 页面上如果 `seats` 为空,评审继续走 `.delphi-config.json`,所以**打开页面不会静默改变行为**。
144
+
145
+ ### 宿主强制的不变量(不是表单装饰)
146
+
147
+ 表单只是方便,**宿主是权威**。每次评审前 `seatsFromSettings` 重新校验:
148
+
149
+ - 恰好 3 席
150
+ - 3 个 model 去空白后两两不同
151
+ - 模型不得为空或未展开的 `${...}` 占位符
152
+ - 配了席位就必须有 `base_url`
153
+ - `api_key` 必须是**凭据引用**(`${VAR}` / `{env:VAR}`);写入明文 token 会被拒绝,
154
+ 因为那会把密钥落到 profile 文件里
155
+
156
+ > 为什么不在表单里就拦死:DSH 的 settings 表单只能编辑 `.volatile()` 字段,
157
+ > 而 volatile 意味着**保存即热生效**。对"每次运行都花钱"的配置来说,前端校验不够,
158
+ > 所以校验放在宿主、在发出任何请求之前。
159
+
160
+ ### 关于 `bash` 守卫
161
+
162
+ `delphiGuard` 用**只读工具白名单**判定:只有 `read` / `grep` / `glob` / `read_image` /
163
+ XP-Gate 自己的报告类工具算只读,**其余一律视为可写**(包括 `bash`、`pwsh` 和将来新增的工具)。
164
+
165
+ 早期版本用"已知写工具黑名单"(`write` / `edit` / `str_replace_editor`),
166
+ `bash` 一条命令就能改文件,等于这道 HARD-GATE 可绕过。白名单的代价是可能**误拦**新加的
167
+ 只读工具——那会表现为"拦得太狠",而不是"悄悄放行"。
168
+
127
169
  ## Skills
128
170
 
129
171
  本包在 publish 时通过 `scripts/prepack.cjs` 捆绑仓库 `skills/`(当前 12 个)。
@@ -171,7 +213,7 @@ subagent model selection:
171
213
 
172
214
  ```bash
173
215
  npm install --ignore-scripts # --ignore-scripts 绕过 esbuild postinstall(受限环境会 EBUSY)
174
- npm run build # tsc -> lib/
216
+ npm run build # tsc -> lib/,并把 client/client.js 复制成 lib/client.js
175
217
  npm run prepare-skills # 从仓库 skills/ 复制
176
218
  npm test # vitest
177
219
  ```
@@ -182,9 +224,16 @@ npm test # vitest
182
224
  npm run build:dsh-plugin # = node scripts/build-plugin.mjs --platform dsh
183
225
  ```
184
226
 
227
+ > `client/client.js` 是**手写**的浏览器 bundle,不走 tsc:它以 DSH 的客户端模块格式
228
+ > (`window.__ModuleLoader__.load`) 编写,引用了浏览器全局,不是 TypeScript 模块。
229
+ > `npm run build` 的第二步(`scripts/copy-client.cjs`)负责把它放进 `lib/`。
230
+
185
231
  ## 注意
186
232
 
187
- - `lib/` 与 `skills/` 是**构建产物**(已 gitignore),不提交。
233
+ - `lib/` 与 `skills/` 是**构建产物**(已 gitignore),不提交;`client/client.js` 是**源文件**,要提交。
188
234
  - `scripts/prepack.cjs` 的 skill 列表为**动态发现**(读取仓库 `skills/`),不硬编码。
189
235
  - `plugins/dsh/package.json` 的 peerDeps 仍 pin `0.1.1-rc.2`,但实测运行在 `0.2.0-rc.2` 上
190
236
  `defineTool` / `TOOL_ABORTED` / `HarnessError` 签名未变,无需改写。
237
+ - **配置项只有在导出 `Config` 之后才会显示。** 0.31.4 及更早的构建没有导出
238
+ `Config`,所以 `dsh --profile <name> --dump-config-schema` 里该行落在
239
+ `unknownConfig`,Plugins 页也就没有可编辑的字段。安装 0.32.x 后这一项自愈。