@boyingliu01/xp-gate 0.35.11 → 0.35.13

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 (32) hide show
  1. package/hooks/pre-commit +35 -4
  2. package/lib/mattpocock-license-verdict.json +1 -1
  3. package/mock-policy/AGENTS.md +2 -2
  4. package/mutation/AGENTS.md +2 -2
  5. package/package.json +1 -1
  6. package/plugins/claude-code/.claude-plugin/plugin.json +1 -1
  7. package/plugins/claude-code/skills/delphi-review/AGENTS.md +2 -2
  8. package/plugins/claude-code/skills/sprint-flow/AGENTS.md +2 -2
  9. package/plugins/claude-code/skills/test-specification-alignment/AGENTS.md +2 -2
  10. package/plugins/dsh/lib/index.d.ts +21 -0
  11. package/plugins/dsh/lib/index.d.ts.map +1 -1
  12. package/plugins/dsh/lib/index.js +23 -5
  13. package/plugins/dsh/package.json +1 -1
  14. package/plugins/dsh/skills/delphi-review/AGENTS.md +2 -2
  15. package/plugins/dsh/skills/sprint-flow/AGENTS.md +2 -2
  16. package/plugins/dsh/skills/test-specification-alignment/AGENTS.md +2 -2
  17. package/plugins/dsh/src/index.test.ts +18 -1
  18. package/plugins/dsh/src/index.ts +24 -5
  19. package/plugins/opencode/index.ts +8 -4
  20. package/plugins/opencode/package.json +1 -1
  21. package/plugins/opencode/semver.test.ts +64 -0
  22. package/plugins/opencode/skills/delphi-review/AGENTS.md +2 -2
  23. package/plugins/opencode/skills/sprint-flow/AGENTS.md +2 -2
  24. package/plugins/opencode/skills/test-specification-alignment/AGENTS.md +2 -2
  25. package/plugins/qoder/plugin.json +1 -1
  26. package/plugins/qoder/skills/delphi-review/AGENTS.md +2 -2
  27. package/plugins/qoder/skills/sprint-flow/AGENTS.md +2 -2
  28. package/plugins/qoder/skills/test-specification-alignment/AGENTS.md +2 -2
  29. package/principles/AGENTS.md +2 -2
  30. package/skills/delphi-review/AGENTS.md +2 -2
  31. package/skills/sprint-flow/AGENTS.md +2 -2
  32. package/skills/test-specification-alignment/AGENTS.md +2 -2
package/hooks/pre-commit CHANGED
@@ -100,8 +100,16 @@ fi
100
100
  # and a plausible wrong number is harder to notice than a visible gap.
101
101
  #
102
102
  # Resolve from the repo's own declaration, in the order the tools themselves
103
- # use: pyproject [tool.coverage.report] fail_under, then .coveragerc. A repo
104
- # declaring neither keeps the historical 80.
103
+ # use: pyproject [tool.coverage.report] fail_under, then .coveragerc, then the
104
+ # JS/TS runner's own threshold block. A repo declaring none keeps the historical 80.
105
+ #
106
+ # #605: the JS/TS branch below enforces THIS number, but the resolver only ever
107
+ # read Python declarations -- so a vitest project declaring `thresholds.lines: 60`
108
+ # was silently judged against 80. The result was the worst possible split:
109
+ # `npx vitest run` goes green, the commit is then rejected, and the two numbers
110
+ # are read from different files with nothing pointing at the disagreement.
111
+ # Same defect shape as #554/#609: a declaration exists, an enforcement exists,
112
+ # and nothing connected them.
105
113
  _resolve_coverage_threshold() {
106
114
  _rct_value=""
107
115
  _rct_src="default"
@@ -117,13 +125,36 @@ _resolve_coverage_threshold() {
117
125
  _rct_value=$(grep -oE '^[[:space:]]*fail_under[[:space:]]*=[[:space:]]*[0-9]+' .coveragerc 2>/dev/null | grep -oE '[0-9]+' | head -1 || true)
118
126
  [ -n "$_rct_value" ] && _rct_src=".coveragerc fail_under"
119
127
  fi
128
+ # #605: JS/TS. Read the runner's own threshold rather than assuming 80, so the
129
+ # hook enforces the number the project actually declares. `lines` specifically:
130
+ # it is the metric the JS branch below compares, and it is the one vitest docs
131
+ # lead with. Only the FLAT shape is read -- the nested `thresholds: { global }`
132
+ # form is vitest 2.x and was proven silently dropped by the 1.6.x config parser
133
+ # (#561), so honouring it here would enforce a number that never takes effect.
134
+ if [ -z "$_rct_value" ]; then
135
+ for _rct_cfg in vitest.config.ts vitest.config.js vitest.config.mts; do
136
+ [ -f "$_rct_cfg" ] || continue
137
+ _rct_value=$(sed -n '/thresholds[[:space:]]*:[[:space:]]*{/,/}/p' "$_rct_cfg" 2>/dev/null \
138
+ | grep -oE 'lines[[:space:]]*:[[:space:]]*[0-9]+' | grep -oE '[0-9]+' | head -1 || true)
139
+ if [ -n "$_rct_value" ]; then
140
+ _rct_src="$_rct_cfg coverage.thresholds.lines"
141
+ break
142
+ fi
143
+ done
144
+ fi
120
145
  case "$_rct_value" in
121
- ''|*[!0-9]*) _rct_value="80"; _rct_src="default (no fail_under declared)" ;;
146
+ ''|*[!0-9]*) _rct_value="80"; _rct_src="default (no coverage threshold declared)" ;;
122
147
  esac
123
148
  COVERAGE_THRESHOLD_SOURCE="$_rct_src"
124
149
  printf '%s' "$_rct_value"
125
150
  }
126
- COVERAGE_THRESHOLD="$(_resolve_coverage_threshold)"
151
+ # NOT `COVERAGE_THRESHOLD="$(_resolve_coverage_threshold)"`: command substitution
152
+ # runs in a subshell, so `COVERAGE_THRESHOLD_SOURCE` set inside the function was
153
+ # discarded and the audit record's `threshold_source` field was empty on every
154
+ # single commit. A field that is always blank is worse than a missing one -- it
155
+ # looks populated and says nothing. Call the function in this shell instead.
156
+ _resolve_coverage_threshold >/dev/null
157
+ COVERAGE_THRESHOLD="$_rct_value"
127
158
 
128
159
  # Look up one tier's directory variable by name without eval-ing arbitrary input:
129
160
  # the base names are a closed set baked into the generated file.
@@ -2,6 +2,6 @@
2
2
  "verdict": "PASS",
3
3
  "license_type": "MIT",
4
4
  "sha256": "0e7ac423bf2c6e223b7c5b156f8cf72da49d748e56a1641402c31f22ad07dbb5",
5
- "checked_at": "2026-10-30T21:19:13.693Z",
5
+ "checked_at": "2026-10-30T22:02:11.313Z",
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:** c5c05324
4
+ **Commit:** 3978d361
5
5
  **Branch:** main
6
- **Version:** 0.35.11.0
6
+ **Version:** 0.35.13.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:** c5c05324
4
+ **Commit:** 3978d361
5
5
  **Branch:** main
6
- **Version:** 0.35.11.0
6
+ **Version:** 0.35.13.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.11",
3
+ "version": "0.35.13",
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.11",
3
+ "version": "0.35.13",
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:** c5c05324
4
+ **Commit:** 3978d361
5
5
  **Branch:** main
6
- **Version:** 0.35.11.0
6
+ **Version:** 0.35.13.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
3
  **Generated:** 2026-10-30
4
- **Commit:** c5c05324
4
+ **Commit:** 3978d361
5
5
  **Branch:** main
6
- **Version:** 0.35.11.0
6
+ **Version:** 0.35.13.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:** c5c05324
4
+ **Commit:** 3978d361
5
5
  **Branch:** main
6
- **Version:** 0.35.11.0
6
+ **Version:** 0.35.13.0
7
7
 
8
8
  ## OVERVIEW
9
9
  Test-Specification Alignment Engine — two-stage validation ensuring tests accurately reflect requirements and design specs.
@@ -1,5 +1,26 @@
1
1
  import z from "@deepseek-ai/schemastery";
2
2
  import type { Context } from "@deepseek-ai/cordis";
3
+ /**
4
+ * Renders a tool's string result as plain text.
5
+ *
6
+ * Extracted because five tools had written this identical closure inline, which
7
+ * left five separate uncovered functions in the coverage report for one line of
8
+ * behaviour (#562). One definition can be tested once and reads as what it is: a
9
+ * shared output convention, not five coincidences.
10
+ *
11
+ * Generic in the argument type on purpose. `defineTool` types `render` as
12
+ * `(args: <this tool's parameters>, value: string) => ContentBlock[]`, so a
13
+ * `(_args: unknown, ...)` implementation is NOT assignable to it -- `unknown` is
14
+ * the wrong direction for a parameter. Declaring it as `A` lets every call site
15
+ * infer its own argument shape while the return type stays a plain text block.
16
+ *
17
+ * This mismatch was caught by `tsc` in the publish job, not by the test suite:
18
+ * vitest transpiles without type-checking. A green test run is not a green build.
19
+ */
20
+ export declare const renderText: <A>(_args: A, value: string) => {
21
+ type: "text";
22
+ text: string;
23
+ }[];
3
24
  export declare const name = "tool-xp-gate";
4
25
  export declare const inject: string[];
5
26
  /**
@@ -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,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;AAsZlD;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,MAAM;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;aA4BjB,CAAA;AAEF,wBAAgB,KAAK,CAAC,GAAG,EAAE,OAAO,EAAE,MAAM,GAAE,OAAY,GAAG,IAAI,CAgC9D"}
@@ -15,6 +15,24 @@ import { delphiGuardDecision, isCheckablePath, isEditTool } from "./hooks.js";
15
15
  import { PHASE_STATUSES, SPRINT_SUBCOMMANDS, buildSprintCommand } from "./sprint.js";
16
16
  import { createSkillProvider } from "./skill-provider.js";
17
17
  import { createCredentialResolver } from "./credentials.js";
18
+ /**
19
+ * Renders a tool's string result as plain text.
20
+ *
21
+ * Extracted because five tools had written this identical closure inline, which
22
+ * left five separate uncovered functions in the coverage report for one line of
23
+ * behaviour (#562). One definition can be tested once and reads as what it is: a
24
+ * shared output convention, not five coincidences.
25
+ *
26
+ * Generic in the argument type on purpose. `defineTool` types `render` as
27
+ * `(args: <this tool's parameters>, value: string) => ContentBlock[]`, so a
28
+ * `(_args: unknown, ...)` implementation is NOT assignable to it -- `unknown` is
29
+ * the wrong direction for a parameter. Declaring it as `A` lets every call site
30
+ * infer its own argument shape while the return type stays a plain text block.
31
+ *
32
+ * This mismatch was caught by `tsc` in the publish job, not by the test suite:
33
+ * vitest transpiles without type-checking. A green test run is not a green build.
34
+ */
35
+ export const renderText = (_args, value) => [{ type: "text", text: value }];
18
36
  export const name = "tool-xp-gate";
19
37
  // "skills" is required: the bundled skill provider registers during apply(), and
20
38
  // without it ctx.skills may not be mounted yet when this plugin starts.
@@ -68,7 +86,7 @@ function gateCheckTool(shell, dialect) {
68
86
  },
69
87
  output: {
70
88
  schema: { type: "string" },
71
- render: (_args, value) => [{ type: "text", text: value }],
89
+ render: renderText,
72
90
  },
73
91
  timeoutMs: DEFAULT_TIMEOUT_MS,
74
92
  async execute(args, exec) {
@@ -92,7 +110,7 @@ function gatePrinciplesTool(shell, dialect) {
92
110
  },
93
111
  output: {
94
112
  schema: { type: "string" },
95
- render: (_args, value) => [{ type: "text", text: value }],
113
+ render: renderText,
96
114
  },
97
115
  timeoutMs: DEFAULT_TIMEOUT_MS,
98
116
  async execute(args, exec) {
@@ -115,7 +133,7 @@ function gateArchTool(shell, dialect) {
115
133
  },
116
134
  output: {
117
135
  schema: { type: "string" },
118
- render: (_args, value) => [{ type: "text", text: value }],
136
+ render: renderText,
119
137
  },
120
138
  timeoutMs: DEFAULT_TIMEOUT_MS,
121
139
  async execute(args, exec) {
@@ -297,7 +315,7 @@ function delphiReviewTool(resolveApiKey, settings) {
297
315
  },
298
316
  output: {
299
317
  schema: { type: "string" },
300
- render: (_args, value) => [{ type: "text", text: value }],
318
+ render: renderText,
301
319
  },
302
320
  timeoutMs: DELPHI_TIMEOUT_MS,
303
321
  async execute(args, exec) {
@@ -348,7 +366,7 @@ function sprintTool(shell, dialect) {
348
366
  },
349
367
  output: {
350
368
  schema: { type: "string" },
351
- render: (_args, value) => [{ type: "text", text: value }],
369
+ render: renderText,
352
370
  },
353
371
  timeoutMs: DEFAULT_TIMEOUT_MS,
354
372
  async execute(args, exec) {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@boyingliu01/dsh-plugin-xp-gate",
3
- "version": "0.35.11",
3
+ "version": "0.35.13",
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:** c5c05324
4
+ **Commit:** 3978d361
5
5
  **Branch:** main
6
- **Version:** 0.35.11.0
6
+ **Version:** 0.35.13.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
3
  **Generated:** 2026-10-30
4
- **Commit:** c5c05324
4
+ **Commit:** 3978d361
5
5
  **Branch:** main
6
- **Version:** 0.35.11.0
6
+ **Version:** 0.35.13.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:** c5c05324
4
+ **Commit:** 3978d361
5
5
  **Branch:** main
6
- **Version:** 0.35.11.0
6
+ **Version:** 0.35.13.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 } from "./index.js"
5
+ import { apply, inject, renderText } from "./index.js"
6
6
 
7
7
  type ProviderFactory = (control: SkillProviderControl) => SkillProvider
8
8
 
@@ -169,4 +169,21 @@ describe("apply", () => {
169
169
  await tool.execute({ config: "custom.yaml" }, { signal: new AbortController().signal })
170
170
  expect(requests[1]?.command).toContain("xp-gate arch --use-config 'custom.yaml'")
171
171
  })
172
+
173
+ /**
174
+ * @test REQ-562
175
+ * @intent 5 个工具曾各自内联一份完全相同的 render 闭包,在覆盖率报告里表现为 5 个
176
+ * 未覆盖函数,而实际只有一行行为(#562)。提取为共享常量后只需测一次,
177
+ * 也让"共享的输出约定"这件事在代码里显形,而不是 5 个巧合。
178
+ * @covers AC-562-04
179
+ */
180
+ it("renderText wraps a tool result as a single text part", () => {
181
+ expect(renderText(undefined, "hello")).toEqual([{ type: "text", text: "hello" }])
182
+ })
183
+
184
+ it("renderText keeps empty output as an empty text part, not a dropped one", () => {
185
+ // A tool that legitimately produced nothing must still render one part; returning
186
+ // [] would look to the host like the tool never answered.
187
+ expect(renderText({ path: "x" }, "")).toEqual([{ type: "text", text: "" }])
188
+ })
172
189
  })
@@ -32,6 +32,25 @@ import { createSkillProvider } from "./skill-provider.js"
32
32
  import type { ApiKeyResolver } from "./credentials.js"
33
33
  import { createCredentialResolver } from "./credentials.js"
34
34
 
35
+ /**
36
+ * Renders a tool's string result as plain text.
37
+ *
38
+ * Extracted because five tools had written this identical closure inline, which
39
+ * left five separate uncovered functions in the coverage report for one line of
40
+ * behaviour (#562). One definition can be tested once and reads as what it is: a
41
+ * shared output convention, not five coincidences.
42
+ *
43
+ * Generic in the argument type on purpose. `defineTool` types `render` as
44
+ * `(args: <this tool's parameters>, value: string) => ContentBlock[]`, so a
45
+ * `(_args: unknown, ...)` implementation is NOT assignable to it -- `unknown` is
46
+ * the wrong direction for a parameter. Declaring it as `A` lets every call site
47
+ * infer its own argument shape while the return type stays a plain text block.
48
+ *
49
+ * This mismatch was caught by `tsc` in the publish job, not by the test suite:
50
+ * vitest transpiles without type-checking. A green test run is not a green build.
51
+ */
52
+ export const renderText = <A>(_args: A, value: string) => [{ type: "text" as const, text: value }]
53
+
35
54
  export const name = "tool-xp-gate"
36
55
  // "skills" is required: the bundled skill provider registers during apply(), and
37
56
  // without it ctx.skills may not be mounted yet when this plugin starts.
@@ -92,7 +111,7 @@ function gateCheckTool(shell: Context["shell"], dialect: ShellDialect) {
92
111
  },
93
112
  output: {
94
113
  schema: { type: "string" },
95
- render: (_args, value) => [{ type: "text", text: value }],
114
+ render: renderText,
96
115
  },
97
116
  timeoutMs: DEFAULT_TIMEOUT_MS,
98
117
  async execute(args, exec) {
@@ -118,7 +137,7 @@ function gatePrinciplesTool(shell: Context["shell"], dialect: ShellDialect) {
118
137
  },
119
138
  output: {
120
139
  schema: { type: "string" },
121
- render: (_args, value) => [{ type: "text", text: value }],
140
+ render: renderText,
122
141
  },
123
142
  timeoutMs: DEFAULT_TIMEOUT_MS,
124
143
  async execute(args, exec) {
@@ -144,7 +163,7 @@ function gateArchTool(shell: Context["shell"], dialect: ShellDialect) {
144
163
  },
145
164
  output: {
146
165
  schema: { type: "string" },
147
- render: (_args, value) => [{ type: "text", text: value }],
166
+ render: renderText,
148
167
  },
149
168
  timeoutMs: DEFAULT_TIMEOUT_MS,
150
169
  async execute(args, exec) {
@@ -352,7 +371,7 @@ function delphiReviewTool(resolveApiKey: ApiKeyResolver, settings: DelphiSetting
352
371
  },
353
372
  output: {
354
373
  schema: { type: "string" },
355
- render: (_args, value) => [{ type: "text", text: value }],
374
+ render: renderText,
356
375
  },
357
376
  timeoutMs: DELPHI_TIMEOUT_MS,
358
377
  async execute(args, exec) {
@@ -405,7 +424,7 @@ function sprintTool(shell: Context["shell"], dialect: ShellDialect) {
405
424
  },
406
425
  output: {
407
426
  schema: { type: "string" },
408
- render: (_args, value) => [{ type: "text", text: value }],
427
+ render: renderText,
409
428
  },
410
429
  timeoutMs: DEFAULT_TIMEOUT_MS,
411
430
  async execute(args, exec) {
@@ -1,6 +1,6 @@
1
1
  import { tool } from "@opencode-ai/plugin"
2
2
  import { z } from "zod"
3
- import { exec, execSync, spawn } from "node:child_process"
3
+ import { exec, execFileSync, execSync, spawn } from "node:child_process"
4
4
  import { promisify } from "node:util"
5
5
  import { readFileSync, writeFileSync, existsSync, mkdirSync, rmSync } from "node:fs"
6
6
  import { join } from "node:path"
@@ -345,7 +345,7 @@ function generateSessionTitle(dbPath: string, sessionId: string): string {
345
345
  ORDER BY m.time_created DESC
346
346
  LIMIT 10
347
347
  `
348
- const raw = execSync(`sqlite3 "${dbPath}" "${sql}"`, {
348
+ const raw = execFileSync("sqlite3", [dbPath, sql], {
349
349
  encoding: "utf8",
350
350
  timeout: 5000,
351
351
  })
@@ -492,7 +492,11 @@ function writeSessionModel(dbPath: string, sessionId: string, modelJson: string)
492
492
  const escapedModel = modelJson.replace(/'/g, "''")
493
493
  const sql = `UPDATE session SET model = '${escapedModel}', time_updated = ${Date.now()} WHERE id = '${escapedId}'`
494
494
  try {
495
- execSync(`sqlite3 "${dbPath}" "${sql}"`, { timeout: SQLITE_TIMEOUT_MS })
495
+ // execFileSync, NOT execSync: `dbPath` was interpolated into a shell string with
496
+ // only double quotes for protection, so a path containing `"` or `$()` broke out
497
+ // into shell (semgrep security-audit flagged it as a command-injection sink).
498
+ // Passing the arguments as an array removes the shell from the picture entirely.
499
+ execFileSync("sqlite3", [dbPath, sql], { timeout: SQLITE_TIMEOUT_MS })
496
500
  } catch (err) {
497
501
  // Contract is to propagate: the caller renders the failure as
498
502
  // "Session reload failed: <message>". Wrapped so the IO sits inside
@@ -616,7 +620,7 @@ function renameSession(sessionId: string, newTitle?: string): string {
616
620
  const escapedId = sessionId.replace(/'/g, "''")
617
621
  const sql = `UPDATE session SET title = '${escapedTitle}', time_updated = ${Date.now()} WHERE id = '${escapedId}'`
618
622
  try {
619
- execSync(`sqlite3 "${dbPath}" "${sql}"`, { timeout: SQLITE_TIMEOUT_MS })
623
+ execFileSync("sqlite3", [dbPath, sql], { timeout: SQLITE_TIMEOUT_MS })
620
624
  return `Session renamed to: "${trimmed}"`
621
625
  } catch (err) {
622
626
  const msg = err instanceof Error ? err.message : String(err)
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@boyingliu01/opencode-plugin",
3
- "version": "0.35.11",
3
+ "version": "0.35.13",
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",
@@ -0,0 +1,64 @@
1
+ /**
2
+ * @test REQ-562 插件版本比较必须正确,否则升级提示会撒谎
3
+ * @intent `plugins/opencode/index.ts` 有 4 个 export,函数覆盖率 50%,且**没有任何
4
+ * 测试文件**。它不是可选的样板:`semverLt` 决定"要不要提示用户升级",
5
+ * 在 3 处被调用(第 143/249/289 行)。
6
+ *
7
+ * 一个错的 `semverLt` 不会崩溃,只会让提示永远说"已是最新"或永远提示升级——
8
+ * 两种都是"看起来在工作"。这类函数正是最该被钉住的:纯、无副作用、
9
+ * 影响用户可见的行为。
10
+ *
11
+ * 这里只测导出的纯函数,不碰网络和文件系统——测试不该依赖 npm registry
12
+ * 是否在线(那会让 CI 在断网时红,而问题不在代码里)。
13
+ * @covers AC-562-03
14
+ */
15
+ 'use strict';
16
+
17
+ const path = require('node:path');
18
+ const { fileURLToPath } = require('node:url');
19
+
20
+ const PLUGIN_ENTRY = path.resolve(path.dirname(fileURLToPath(import.meta.url)), 'index.ts');
21
+
22
+ describe('opencode 插件版本比较 (#562)', () => {
23
+ let mod;
24
+
25
+ beforeAll(async () => {
26
+ // The plugin is TypeScript with an npm dependency on @opencode-ai/plugin, which
27
+ // is only present in that workspace. Import through tsx so the test itself stays
28
+ // runnable from the root suite.
29
+ mod = await import(PLUGIN_ENTRY);
30
+ });
31
+
32
+ it('AC-562-03: 暴露了纯函数测试钩子', () => {
33
+ expect(mod.__test).toBeDefined();
34
+ expect(typeof mod.__test.semverLt).toBe('function');
35
+ });
36
+
37
+ it('AC-562-03: 主版本更高时判定为更小', () => {
38
+ expect(mod.__test.semverLt('0.35.10', '0.36.0')).toBe(true);
39
+ expect(mod.__test.semverLt('0.36.0', '0.35.10')).toBe(false);
40
+ });
41
+
42
+ it('AC-562-03: patch 与 minor 的位数差异不被误判', () => {
43
+ // This is the case a naive string comparison gets wrong: "0.9.0" < "0.10.0"
44
+ // lexicographically is 9 < 1 == false. Numeric segment compare must win.
45
+ expect(mod.__test.semverLt('0.9.0', '0.10.0')).toBe(true);
46
+ expect(mod.__test.semverLt('0.10.0', '0.9.0')).toBe(false);
47
+ });
48
+
49
+ it('AC-562-03: 前导 v 与缺失段被正确处理', () => {
50
+ expect(mod.__test.semverLt('v0.35.10', '0.35.11')).toBe(true);
51
+ // "0.35" is treated as 0.35.0, so it is LESS than 0.35.1 -- solid? No: it is
52
+ // equal to 0.35.0, and 0.35 < 0.35.1. Asserting the documented behaviour keeps
53
+ // a future refactor from silently changing missing-segment semantics.
54
+ expect(mod.__test.semverLt('0.35', '0.35.1')).toBe(true);
55
+ expect(mod.__test.semverLt('0.35.1', '0.35')).toBe(false);
56
+ });
57
+
58
+ it('AC-562-03: 相等版本不判定为更小', () => {
59
+ // `upToDate = !semverLt(...)` -- if equal versions compared as "less", every
60
+ // run would tell the user an update is available when there is none.
61
+ expect(mod.__test.semverLt('0.35.11', '0.35.11')).toBe(false);
62
+ expect(mod.__test.semverLt('v0.35.11', '0.35.11')).toBe(false);
63
+ });
64
+ });
@@ -1,9 +1,9 @@
1
1
  # SKILLS/DELPHI-REVIEW KNOWLEDGE BASE
2
2
 
3
3
  **Generated:** 2026-10-30
4
- **Commit:** c5c05324
4
+ **Commit:** 3978d361
5
5
  **Branch:** main
6
- **Version:** 0.35.11.0
6
+ **Version:** 0.35.13.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
3
  **Generated:** 2026-10-30
4
- **Commit:** c5c05324
4
+ **Commit:** 3978d361
5
5
  **Branch:** main
6
- **Version:** 0.35.11.0
6
+ **Version:** 0.35.13.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:** c5c05324
4
+ **Commit:** 3978d361
5
5
  **Branch:** main
6
- **Version:** 0.35.11.0
6
+ **Version:** 0.35.13.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.11",
3
+ "version": "0.35.13",
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:** c5c05324
4
+ **Commit:** 3978d361
5
5
  **Branch:** main
6
- **Version:** 0.35.11.0
6
+ **Version:** 0.35.13.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
3
  **Generated:** 2026-10-30
4
- **Commit:** c5c05324
4
+ **Commit:** 3978d361
5
5
  **Branch:** main
6
- **Version:** 0.35.11.0
6
+ **Version:** 0.35.13.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:** c5c05324
4
+ **Commit:** 3978d361
5
5
  **Branch:** main
6
- **Version:** 0.35.11.0
6
+ **Version:** 0.35.13.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:** c5c05324
4
+ **Commit:** 3978d361
5
5
  **Branch:** main
6
- **Version:** 0.35.11.0
6
+ **Version:** 0.35.13.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:** c5c05324
4
+ **Commit:** 3978d361
5
5
  **Branch:** main
6
- **Version:** 0.35.11.0
6
+ **Version:** 0.35.13.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
3
  **Generated:** 2026-10-30
4
- **Commit:** c5c05324
4
+ **Commit:** 3978d361
5
5
  **Branch:** main
6
- **Version:** 0.35.11.0
6
+ **Version:** 0.35.13.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:** c5c05324
4
+ **Commit:** 3978d361
5
5
  **Branch:** main
6
- **Version:** 0.35.11.0
6
+ **Version:** 0.35.13.0
7
7
 
8
8
  ## OVERVIEW
9
9
  Test-Specification Alignment Engine — two-stage validation ensuring tests accurately reflect requirements and design specs.