@boyingliu01/xp-gate 0.35.10 → 0.35.12
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/hooks/pre-commit +131 -5
- package/lib/__tests__/coverage-scope.test.js +140 -0
- package/lib/__tests__/slo.test.js +89 -0
- package/lib/mattpocock-license-verdict.json +1 -1
- package/lib/retro.js +84 -0
- package/lib/slo.js +150 -0
- package/mock-policy/AGENTS.md +2 -2
- package/mutation/AGENTS.md +2 -2
- package/package.json +1 -1
- package/plugins/claude-code/.claude-plugin/plugin.json +1 -1
- package/plugins/claude-code/skills/delphi-review/AGENTS.md +2 -2
- package/plugins/claude-code/skills/sprint-flow/AGENTS.md +2 -2
- package/plugins/claude-code/skills/test-specification-alignment/AGENTS.md +2 -2
- package/plugins/dsh/lib/index.d.ts +21 -0
- package/plugins/dsh/lib/index.d.ts.map +1 -1
- package/plugins/dsh/lib/index.js +23 -5
- package/plugins/dsh/package.json +1 -1
- package/plugins/dsh/skills/delphi-review/AGENTS.md +2 -2
- package/plugins/dsh/skills/sprint-flow/AGENTS.md +2 -2
- package/plugins/dsh/skills/test-specification-alignment/AGENTS.md +2 -2
- package/plugins/dsh/src/index.test.ts +18 -1
- package/plugins/dsh/src/index.ts +24 -5
- package/plugins/opencode/index.ts +8 -4
- package/plugins/opencode/package.json +1 -1
- package/plugins/opencode/semver.test.ts +64 -0
- package/plugins/opencode/skills/delphi-review/AGENTS.md +2 -2
- package/plugins/opencode/skills/sprint-flow/AGENTS.md +2 -2
- package/plugins/opencode/skills/test-specification-alignment/AGENTS.md +2 -2
- package/plugins/qoder/plugin.json +1 -1
- package/plugins/qoder/skills/delphi-review/AGENTS.md +2 -2
- package/plugins/qoder/skills/sprint-flow/AGENTS.md +2 -2
- package/plugins/qoder/skills/test-specification-alignment/AGENTS.md +2 -2
- package/principles/AGENTS.md +2 -2
- package/skills/delphi-review/AGENTS.md +2 -2
- package/skills/sprint-flow/AGENTS.md +2 -2
- package/skills/test-specification-alignment/AGENTS.md +2 -2
package/lib/slo.js
ADDED
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @test REQ-609 SLO 指标必须区分"达标"与"没有数据"
|
|
3
|
+
* @intent 指南 §15 要求首构 ≥90%、终构 ≥98%、首测 ≥85%、Flaky ≤0.5%、
|
|
4
|
+
* CI 无效触发 ≤20%、文件丢失 = 0。
|
|
5
|
+
*
|
|
6
|
+
* 这个模块的**全部价值**在于三态判定,而不是六行数字:
|
|
7
|
+
*
|
|
8
|
+
* PASS 有数据,且在目标内
|
|
9
|
+
* MISS 有数据,但在目标外 <- 唯一需要行动的
|
|
10
|
+
* UNKNOWN 数据不足,无从判断 <- 不是通过,也不是失败
|
|
11
|
+
*
|
|
12
|
+
* 为什么 UNKNOWN 必须独立存在:把"没有数据"折叠进 PASS 就是 #554 那一类
|
|
13
|
+
* 缺陷(门禁没跑却报通过);折叠进 MISS 则会让每一个新仓库一开始就六项
|
|
14
|
+
* 全红,于是所有人都学会忽略这个面板——面板一旦被忽略,它就和不存在一样。
|
|
15
|
+
*
|
|
16
|
+
* "文件丢失 = 0"尤其危险:它的目标值是 0,而没有数据时最自然的算法
|
|
17
|
+
* 恰好也产出 0。所以这一项的 UNKNOWN 判定必须先于数值比较,而不是
|
|
18
|
+
* 比较完之后再补一句注释。
|
|
19
|
+
*
|
|
20
|
+
* 每个 SLO 都带 `source`:没有指明数据来源的指标无法被核实,
|
|
21
|
+
* 也就无法被信任。
|
|
22
|
+
*/
|
|
23
|
+
'use strict';
|
|
24
|
+
|
|
25
|
+
/** 六项 SLO 及其目标。方向决定比较运算符,避免手写每个分支。 */
|
|
26
|
+
const SLO_DEFINITIONS = [
|
|
27
|
+
{
|
|
28
|
+
id: 'first_build_pass',
|
|
29
|
+
label: 'First-build pass rate',
|
|
30
|
+
target: 90,
|
|
31
|
+
unit: '%',
|
|
32
|
+
direction: 'min',
|
|
33
|
+
source: '.xp-gate/audit.jsonl + pre-commit reports',
|
|
34
|
+
describe: 'first attempt of each gate run passes without a retry',
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
id: 'final_build_pass',
|
|
38
|
+
label: 'Final-build pass rate',
|
|
39
|
+
target: 98,
|
|
40
|
+
unit: '%',
|
|
41
|
+
direction: 'min',
|
|
42
|
+
source: '.xp-gate/audit.jsonl (last attempt per gate per commit)',
|
|
43
|
+
describe: 'the attempt that actually lands passes',
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
id: 'first_test_pass',
|
|
47
|
+
label: 'First-test pass rate',
|
|
48
|
+
target: 85,
|
|
49
|
+
unit: '%',
|
|
50
|
+
direction: 'min',
|
|
51
|
+
source: '.xp-gate/audit.jsonl (Gate 5 first attempt)',
|
|
52
|
+
describe: 'test suite passes on its first run in a commit',
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
id: 'flaky_rate',
|
|
56
|
+
label: 'Flaky rate',
|
|
57
|
+
target: 0.5,
|
|
58
|
+
unit: '%',
|
|
59
|
+
direction: 'max',
|
|
60
|
+
source: '.xp-gate/audit.jsonl re-run records',
|
|
61
|
+
describe: 'a test that failed then passed on re-run without a code change',
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
id: 'ci_wasted_trigger',
|
|
65
|
+
label: 'CI wasted trigger rate',
|
|
66
|
+
target: 20,
|
|
67
|
+
unit: '%',
|
|
68
|
+
direction: 'max',
|
|
69
|
+
source: '.github workflow runs vs gate journal',
|
|
70
|
+
describe: 'a CI run triggered by a commit that the gates had already rejected',
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
id: 'file_loss',
|
|
74
|
+
label: 'File loss events',
|
|
75
|
+
target: 0,
|
|
76
|
+
unit: '',
|
|
77
|
+
direction: 'max',
|
|
78
|
+
source: 'vitest-worktree-guard + git status snapshots',
|
|
79
|
+
describe: 'tracked files disappearing during a run',
|
|
80
|
+
},
|
|
81
|
+
];
|
|
82
|
+
|
|
83
|
+
const STATES = { PASS: 'PASS', MISS: 'MISS', UNKNOWN: 'UNKNOWN' };
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Evaluate one SLO sample.
|
|
87
|
+
*
|
|
88
|
+
* `value === null` / `undefined` means "no measurement", which is a different
|
|
89
|
+
* claim from `value === 0`. Keeping them distinct is the whole point of this module.
|
|
90
|
+
*/
|
|
91
|
+
function evaluateSlo(id, value, opts = {}) {
|
|
92
|
+
const def = SLO_DEFINITIONS.find((d) => d.id === id);
|
|
93
|
+
if (!def) throw new Error(`unknown SLO id: ${id}`);
|
|
94
|
+
|
|
95
|
+
if (value === null || value === undefined || Number.isNaN(value)) {
|
|
96
|
+
return {
|
|
97
|
+
...def,
|
|
98
|
+
value: null,
|
|
99
|
+
state: STATES.UNKNOWN,
|
|
100
|
+
reason: opts.reason || 'no measurement available in the reporting window',
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// File loss is the trap: its target is 0, and a naive max-direction comparison
|
|
105
|
+
// (`0 <= 0`) reports PASS for a window that observed nothing. Requiring an
|
|
106
|
+
// explicit sample count makes "we looked and saw none" different from "we did
|
|
107
|
+
// not look".
|
|
108
|
+
const samples = opts.samples;
|
|
109
|
+
if (def.id === 'file_loss' && (samples === undefined || samples === null || samples <= 0)) {
|
|
110
|
+
return {
|
|
111
|
+
...def,
|
|
112
|
+
value: null,
|
|
113
|
+
state: STATES.UNKNOWN,
|
|
114
|
+
reason: 'no guard runs in the window -- absence of guards is not absence of loss',
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const pass = def.direction === 'min' ? value >= def.target : value <= def.target;
|
|
119
|
+
return {
|
|
120
|
+
...def,
|
|
121
|
+
value,
|
|
122
|
+
sampleCount: samples,
|
|
123
|
+
state: pass ? STATES.PASS : STATES.MISS,
|
|
124
|
+
reason: pass ? '' : `${value}${def.unit} vs target ${def.direction === 'min' ? '>=' : '<='} ${def.target}${def.unit}`,
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/** Evaluate a whole batch of measurements (`{ id: value | {value,samples} }`). */
|
|
129
|
+
function evaluateAll(measurements = {}) {
|
|
130
|
+
return SLO_DEFINITIONS.map((def) => {
|
|
131
|
+
const raw = measurements[def.id];
|
|
132
|
+
if (raw && typeof raw === 'object') return evaluateSlo(def.id, raw.value, raw);
|
|
133
|
+
return evaluateSlo(def.id, raw);
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/** Count rows per state -- what a dashboard badge and a retro line both need. */
|
|
138
|
+
function summariseStates(rows) {
|
|
139
|
+
const out = { PASS: 0, MISS: 0, UNKNOWN: 0, total: rows.length };
|
|
140
|
+
for (const r of rows) out[r.state] += 1;
|
|
141
|
+
return out;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
module.exports = {
|
|
145
|
+
SLO_DEFINITIONS,
|
|
146
|
+
STATES,
|
|
147
|
+
evaluateSlo,
|
|
148
|
+
evaluateAll,
|
|
149
|
+
summariseStates,
|
|
150
|
+
};
|
package/mock-policy/AGENTS.md
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
# SRC/MOCK-POLICY KNOWLEDGE BASE
|
|
2
2
|
|
|
3
3
|
**Generated:** 2026-10-30
|
|
4
|
-
**Commit:**
|
|
4
|
+
**Commit:** a92243d7
|
|
5
5
|
**Branch:** main
|
|
6
|
-
**Version:** 0.35.
|
|
6
|
+
**Version:** 0.35.12.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.
|
package/mutation/AGENTS.md
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
# SRC/MUTATION KNOWLEDGE BASE
|
|
2
2
|
|
|
3
3
|
**Generated:** 2026-10-30
|
|
4
|
-
**Commit:**
|
|
4
|
+
**Commit:** a92243d7
|
|
5
5
|
**Branch:** main
|
|
6
|
-
**Version:** 0.35.
|
|
6
|
+
**Version:** 0.35.12.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": "xp-gate",
|
|
3
|
-
"version": "0.35.
|
|
3
|
+
"version": "0.35.12",
|
|
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:**
|
|
4
|
+
**Commit:** a92243d7
|
|
5
5
|
**Branch:** main
|
|
6
|
-
**Version:** 0.35.
|
|
6
|
+
**Version:** 0.35.12.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:**
|
|
4
|
+
**Commit:** a92243d7
|
|
5
5
|
**Branch:** main
|
|
6
|
-
**Version:** 0.35.
|
|
6
|
+
**Version:** 0.35.12.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:**
|
|
4
|
+
**Commit:** a92243d7
|
|
5
5
|
**Branch:** main
|
|
6
|
-
**Version:** 0.35.
|
|
6
|
+
**Version:** 0.35.12.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"}
|
package/plugins/dsh/lib/index.js
CHANGED
|
@@ -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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
369
|
+
render: renderText,
|
|
352
370
|
},
|
|
353
371
|
timeoutMs: DEFAULT_TIMEOUT_MS,
|
|
354
372
|
async execute(args, exec) {
|
package/plugins/dsh/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@boyingliu01/dsh-plugin-xp-gate",
|
|
3
|
-
"version": "0.35.
|
|
3
|
+
"version": "0.35.12",
|
|
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:**
|
|
4
|
+
**Commit:** a92243d7
|
|
5
5
|
**Branch:** main
|
|
6
|
-
**Version:** 0.35.
|
|
6
|
+
**Version:** 0.35.12.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:**
|
|
4
|
+
**Commit:** a92243d7
|
|
5
5
|
**Branch:** main
|
|
6
|
-
**Version:** 0.35.
|
|
6
|
+
**Version:** 0.35.12.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:**
|
|
4
|
+
**Commit:** a92243d7
|
|
5
5
|
**Branch:** main
|
|
6
|
-
**Version:** 0.35.
|
|
6
|
+
**Version:** 0.35.12.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
|
})
|
package/plugins/dsh/src/index.ts
CHANGED
|
@@ -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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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 =
|
|
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
|
|
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
|
-
|
|
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)
|
|
@@ -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:**
|
|
4
|
+
**Commit:** a92243d7
|
|
5
5
|
**Branch:** main
|
|
6
|
-
**Version:** 0.35.
|
|
6
|
+
**Version:** 0.35.12.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:**
|
|
4
|
+
**Commit:** a92243d7
|
|
5
5
|
**Branch:** main
|
|
6
|
-
**Version:** 0.35.
|
|
6
|
+
**Version:** 0.35.12.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:**
|
|
4
|
+
**Commit:** a92243d7
|
|
5
5
|
**Branch:** main
|
|
6
|
-
**Version:** 0.35.
|
|
6
|
+
**Version:** 0.35.12.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.
|
|
3
|
+
"version": "0.35.12",
|
|
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": {
|