@boyingliu01/xp-gate 0.31.4 → 0.32.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/adapters/java.sh +256 -4
- package/adapters/powershell.sh +45 -6
- package/bin/xp-gate.js +8 -2
- package/build-integrity/__tests__/gate-10.test.ts +88 -1
- package/build-integrity/gate-10.ts +51 -16
- package/dsh.bundle.patch.yml +15 -0
- package/gate-13-compiler-strictness.sh +1031 -0
- package/gate-3.sh +48 -13
- package/hooks/gate-13-compiler-strictness.sh +1031 -0
- package/hooks/gate-3.sh +48 -13
- package/hooks/lib/coverage-total.sh +18 -2
- package/hooks/lib/java-test-selection.sh +251 -0
- package/hooks/lib/jscpd-run.sh +9 -0
- package/hooks/lib/validate-code-walkthrough.cjs +186 -29
- package/hooks/pre-commit +149 -2
- package/lib/__tests__/detect-deps-dsh-profile.test.js +106 -0
- package/lib/__tests__/doctor-tui-skill-freshness.test.js +140 -0
- package/lib/__tests__/gate-runner.test.js +29 -2
- package/lib/__tests__/issue-586-principles-cmdline.test.js +35 -0
- package/lib/__tests__/issue-618-gate-not-run.test.js +100 -0
- package/lib/__tests__/shared-utils.test.js +21 -0
- package/lib/check.js +6 -2
- package/lib/detect-deps.js +42 -10
- package/lib/doctor-tui.js +111 -21
- package/lib/gate-runner.js +111 -37
- package/lib/mattpocock-license-verdict.json +1 -1
- package/lib/principles.js +48 -6
- package/lib/shared-utils.js +8 -0
- package/mock-policy/AGENTS.md +3 -3
- package/mock-policy/gate-m3.ts +57 -23
- package/mutation/AGENTS.md +3 -3
- package/mutation/__tests__/pitest-gradle-availability.test.ts +74 -0
- package/mutation/__tests__/test-filter-parity.test.ts +5 -2
- package/mutation/gate-m.ts +30 -24
- package/mutation/runners/mutmut-runner.ts +80 -30
- package/mutation/runners/pitest-runner.ts +60 -42
- package/package.json +14 -2
- package/plugins/claude-code/.claude-plugin/plugin.json +1 -1
- package/plugins/claude-code/skills/delphi-review/AGENTS.md +3 -3
- package/plugins/claude-code/skills/sprint-flow/AGENTS.md +3 -3
- package/plugins/claude-code/skills/test-specification-alignment/AGENTS.md +3 -3
- package/plugins/dsh/lib/command.d.ts +38 -0
- package/plugins/dsh/lib/command.d.ts.map +1 -0
- package/plugins/dsh/lib/command.js +87 -0
- package/plugins/dsh/lib/credentials.d.ts +39 -0
- package/plugins/dsh/lib/credentials.d.ts.map +1 -0
- package/plugins/dsh/lib/credentials.js +57 -0
- package/plugins/dsh/lib/delphi-config.d.ts +129 -0
- package/plugins/dsh/lib/delphi-config.d.ts.map +1 -0
- package/plugins/dsh/lib/delphi-config.js +369 -0
- package/plugins/dsh/lib/delphi-run.d.ts +24 -0
- package/plugins/dsh/lib/delphi-run.d.ts.map +1 -0
- package/plugins/dsh/lib/delphi-run.js +113 -0
- package/plugins/dsh/lib/delphi.d.ts +94 -0
- package/plugins/dsh/lib/delphi.d.ts.map +1 -0
- package/plugins/dsh/lib/delphi.js +207 -0
- package/plugins/dsh/lib/dialect.d.ts +24 -0
- package/plugins/dsh/lib/dialect.d.ts.map +1 -0
- package/plugins/dsh/lib/dialect.js +36 -0
- package/plugins/dsh/lib/evidence.d.ts +70 -0
- package/plugins/dsh/lib/evidence.d.ts.map +1 -0
- package/plugins/dsh/lib/evidence.js +92 -0
- package/plugins/dsh/lib/gate-runner.d.ts +49 -0
- package/plugins/dsh/lib/gate-runner.d.ts.map +1 -0
- package/plugins/dsh/lib/gate-runner.js +95 -0
- package/plugins/dsh/lib/git-context.d.ts +26 -0
- package/plugins/dsh/lib/git-context.d.ts.map +1 -0
- package/plugins/dsh/lib/git-context.js +30 -0
- package/plugins/dsh/lib/hooks.d.ts +42 -0
- package/plugins/dsh/lib/hooks.d.ts.map +1 -0
- package/plugins/dsh/lib/hooks.js +113 -0
- package/plugins/dsh/lib/index.d.ts +20 -0
- package/plugins/dsh/lib/index.d.ts.map +1 -0
- package/plugins/dsh/lib/index.js +430 -0
- package/plugins/dsh/lib/skill-provider.d.ts +15 -0
- package/plugins/dsh/lib/skill-provider.d.ts.map +1 -0
- package/plugins/dsh/lib/skill-provider.js +205 -0
- package/plugins/dsh/lib/sprint.d.ts +60 -0
- package/plugins/dsh/lib/sprint.d.ts.map +1 -0
- package/plugins/dsh/lib/sprint.js +92 -0
- package/plugins/dsh/package.json +14 -8
- package/plugins/dsh/scripts/verify-skill-provider.mjs +113 -0
- package/plugins/dsh/skills/delphi-review/AGENTS.md +3 -3
- package/plugins/dsh/skills/sprint-flow/AGENTS.md +3 -3
- package/plugins/dsh/skills/test-specification-alignment/AGENTS.md +3 -3
- package/plugins/dsh/src/command-single-form.test.ts +67 -0
- package/plugins/dsh/src/command.test.ts +21 -17
- package/plugins/dsh/src/command.ts +19 -21
- package/plugins/dsh/src/compat.test.ts +200 -0
- package/plugins/dsh/src/contract.test.ts +7 -1
- package/plugins/dsh/src/credentials.test.ts +93 -0
- package/plugins/dsh/src/credentials.ts +71 -0
- package/plugins/dsh/src/delphi-cancellation.test.ts +71 -0
- package/plugins/dsh/src/delphi-config-apikey-override.test.ts +94 -0
- package/plugins/dsh/src/delphi-config-credential-refs.test.ts +75 -0
- package/plugins/dsh/src/delphi-config.ts +146 -53
- package/plugins/dsh/src/delphi-run.ts +27 -4
- package/plugins/dsh/src/delphi.ts +133 -65
- package/plugins/dsh/src/evidence.test.ts +74 -0
- package/plugins/dsh/src/evidence.ts +141 -0
- package/plugins/dsh/src/gate-runner.test.ts +14 -6
- package/plugins/dsh/src/gate-runner.ts +68 -5
- package/plugins/dsh/src/git-context.test.ts +39 -0
- package/plugins/dsh/src/git-context.ts +38 -0
- package/plugins/dsh/src/hooks.test.ts +87 -0
- package/plugins/dsh/src/hooks.ts +126 -0
- package/plugins/dsh/src/index.test.ts +63 -6
- package/plugins/dsh/src/index.ts +340 -13
- package/plugins/dsh/src/skill-provider.test.ts +194 -0
- package/plugins/dsh/src/skill-provider.ts +231 -0
- package/plugins/dsh/src/sprint.test.ts +64 -0
- package/plugins/dsh/src/sprint.ts +122 -0
- package/plugins/dsh/src/tri-state.test.ts +123 -0
- package/plugins/opencode/index.ts +192 -58
- package/plugins/opencode/package.json +1 -1
- package/plugins/opencode/skills/delphi-review/AGENTS.md +3 -3
- package/plugins/opencode/skills/sprint-flow/AGENTS.md +3 -3
- package/plugins/opencode/skills/test-specification-alignment/AGENTS.md +3 -3
- package/plugins/qoder/plugin.json +1 -1
- package/plugins/qoder/skills/delphi-review/AGENTS.md +3 -3
- package/plugins/qoder/skills/sprint-flow/AGENTS.md +3 -3
- package/plugins/qoder/skills/test-specification-alignment/AGENTS.md +3 -3
- package/principles/AGENTS.md +3 -3
- package/principles/__tests__/analyze-read-failure.test.ts +70 -0
- package/principles/__tests__/baseline-prune.test.ts +61 -0
- package/principles/__tests__/cli-files-separators.test.ts +69 -0
- package/principles/__tests__/config-merge-warnings.test.ts +148 -0
- package/principles/adapters/java.ts +22 -3
- package/principles/adapters/typescript.ts +70 -29
- package/principles/analyzer.ts +36 -3
- package/principles/baseline-prune.ts +19 -7
- package/principles/config.ts +56 -35
- package/principles/index.ts +122 -40
- package/principles/rules/clean-code/deep-nesting.ts +36 -22
- package/principles/rules/clean-code/unused-imports.ts +30 -19
- package/principles/rules/solid/dip.ts +30 -20
- package/principles/rules/solid/lsp.ts +17 -2
- package/principles/rules/solid/ocp.ts +33 -23
- package/principles/rules/solid/srp.ts +65 -23
- package/scripts/delphi-external-review.cjs +25 -2
- package/scripts/sync-package-content.js +28 -1
- package/skills/delphi-review/AGENTS.md +3 -3
- package/skills/sprint-flow/AGENTS.md +3 -3
- package/skills/test-specification-alignment/AGENTS.md +3 -3
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Sprint-flow subcommands the model may run.
|
|
3
|
+
*
|
|
4
|
+
* @test REQ-DSH-013 sprint-flow subcommands are callable from the model
|
|
5
|
+
* @intent Expose the sprint state commands the sprint-flow skill instructs the model
|
|
6
|
+
* to run, without opening a passthrough to the whole CLI.
|
|
7
|
+
* @covers AC-DSH-013-01
|
|
8
|
+
*
|
|
9
|
+
* The skill references these commands by name, so before this module existed a model
|
|
10
|
+
* following the skill could read the instruction and had no way to carry it out.
|
|
11
|
+
*
|
|
12
|
+
* The whitelist is the security boundary, not a convenience: a passthrough tool would
|
|
13
|
+
* turn one tool call into arbitrary CLI invocation, which is a far larger surface than
|
|
14
|
+
* the four gate tools it sits beside. Enumerating both the subcommand and the
|
|
15
|
+
* phase-transition status keeps every user-influenced token inside a known set.
|
|
16
|
+
*/
|
|
17
|
+
import { type ShellDialect } from "./dialect.js";
|
|
18
|
+
/**
|
|
19
|
+
* Subcommands in scope. Deliberately excludes anything destructive or
|
|
20
|
+
* environment-mutating (`uninstall`, `init`, `migrate`, `setup-global`) even though
|
|
21
|
+
* they are the same CLI.
|
|
22
|
+
*/
|
|
23
|
+
export declare const SPRINT_SUBCOMMANDS: readonly ["sprint-status", "phase-transition", "retro", "ui-review", "sprint-init", "sprint-audit", "next-sprint"];
|
|
24
|
+
export type SprintSubcommand = (typeof SPRINT_SUBCOMMANDS)[number];
|
|
25
|
+
/** The only status values `phase-transition` documents. */
|
|
26
|
+
export declare const PHASE_STATUSES: readonly ["in_progress", "completed", "skipped", "failed", "paused"];
|
|
27
|
+
export type PhaseStatus = (typeof PHASE_STATUSES)[number];
|
|
28
|
+
/** Options a caller may pass. Every field is either enumerated or quoted. */
|
|
29
|
+
export interface SprintCommandOptions {
|
|
30
|
+
/** phase-transition phase number, 1-6. */
|
|
31
|
+
readonly phase?: string;
|
|
32
|
+
/** phase-transition status. */
|
|
33
|
+
readonly status?: string;
|
|
34
|
+
/** Project directory, quoted as a single argument. */
|
|
35
|
+
readonly dir?: string;
|
|
36
|
+
/** Emit JSON where the subcommand supports it. */
|
|
37
|
+
readonly json?: boolean;
|
|
38
|
+
/** `retro`: look back N days. */
|
|
39
|
+
readonly days?: number;
|
|
40
|
+
/** `retro`: cover every sprint. */
|
|
41
|
+
readonly all?: boolean;
|
|
42
|
+
}
|
|
43
|
+
/** Type guard for the subcommand whitelist. */
|
|
44
|
+
export declare function isSprintSubcommand(value: string): boolean;
|
|
45
|
+
/**
|
|
46
|
+
* Validate a subcommand, throwing rather than returning a sentinel.
|
|
47
|
+
*
|
|
48
|
+
* A caller that ignores a boolean would fall through to "run nothing" or, worse, to a
|
|
49
|
+
* default subcommand; throwing makes the rejection impossible to overlook.
|
|
50
|
+
*/
|
|
51
|
+
export declare function assertSprintSubcommand(value: string): asserts value is SprintSubcommand;
|
|
52
|
+
/**
|
|
53
|
+
* Build the command line for a whitelisted subcommand.
|
|
54
|
+
*
|
|
55
|
+
* User-influenced values reach the shell in exactly two ways: enumerated membership
|
|
56
|
+
* (subcommand, phase, status) or shell quoting (paths). Nothing is interpolated raw,
|
|
57
|
+
* which is what keeps this from becoming the injection surface a passthrough would be.
|
|
58
|
+
*/
|
|
59
|
+
export declare function buildSprintCommand(subcommand: string, options?: SprintCommandOptions, dialect?: ShellDialect): string;
|
|
60
|
+
//# sourceMappingURL=sprint.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sprint.d.ts","sourceRoot":"","sources":["../src/sprint.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,OAAO,EAAE,KAAK,YAAY,EAAc,MAAM,cAAc,CAAA;AAE5D;;;;GAIG;AACH,eAAO,MAAM,kBAAkB,oHAQrB,CAAA;AAEV,MAAM,MAAM,gBAAgB,GAAG,CAAC,OAAO,kBAAkB,CAAC,CAAC,MAAM,CAAC,CAAA;AAElE,2DAA2D;AAC3D,eAAO,MAAM,cAAc,sEAAuE,CAAA;AAElG,MAAM,MAAM,WAAW,GAAG,CAAC,OAAO,cAAc,CAAC,CAAC,MAAM,CAAC,CAAA;AAEzD,6EAA6E;AAC7E,MAAM,WAAW,oBAAoB;IACnC,0CAA0C;IAC1C,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAA;IACvB,+BAA+B;IAC/B,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAA;IACxB,sDAAsD;IACtD,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,CAAA;IACrB,kDAAkD;IAClD,QAAQ,CAAC,IAAI,CAAC,EAAE,OAAO,CAAA;IACvB,iCAAiC;IACjC,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAA;IACtB,mCAAmC;IACnC,QAAQ,CAAC,GAAG,CAAC,EAAE,OAAO,CAAA;CACvB;AAKD,+CAA+C;AAC/C,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAEzD;AAED;;;;;GAKG;AACH,wBAAgB,sBAAsB,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,KAAK,IAAI,gBAAgB,CAMvF;AA2BD;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAChC,UAAU,EAAE,MAAM,EAClB,OAAO,GAAE,oBAAyB,EAClC,OAAO,GAAE,YAAsB,GAC9B,MAAM,CAMR"}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Sprint-flow subcommands the model may run.
|
|
3
|
+
*
|
|
4
|
+
* @test REQ-DSH-013 sprint-flow subcommands are callable from the model
|
|
5
|
+
* @intent Expose the sprint state commands the sprint-flow skill instructs the model
|
|
6
|
+
* to run, without opening a passthrough to the whole CLI.
|
|
7
|
+
* @covers AC-DSH-013-01
|
|
8
|
+
*
|
|
9
|
+
* The skill references these commands by name, so before this module existed a model
|
|
10
|
+
* following the skill could read the instruction and had no way to carry it out.
|
|
11
|
+
*
|
|
12
|
+
* The whitelist is the security boundary, not a convenience: a passthrough tool would
|
|
13
|
+
* turn one tool call into arbitrary CLI invocation, which is a far larger surface than
|
|
14
|
+
* the four gate tools it sits beside. Enumerating both the subcommand and the
|
|
15
|
+
* phase-transition status keeps every user-influenced token inside a known set.
|
|
16
|
+
*/
|
|
17
|
+
import { shellQuote } from "./dialect.js";
|
|
18
|
+
/**
|
|
19
|
+
* Subcommands in scope. Deliberately excludes anything destructive or
|
|
20
|
+
* environment-mutating (`uninstall`, `init`, `migrate`, `setup-global`) even though
|
|
21
|
+
* they are the same CLI.
|
|
22
|
+
*/
|
|
23
|
+
export const SPRINT_SUBCOMMANDS = [
|
|
24
|
+
"sprint-status",
|
|
25
|
+
"phase-transition",
|
|
26
|
+
"retro",
|
|
27
|
+
"ui-review",
|
|
28
|
+
"sprint-init",
|
|
29
|
+
"sprint-audit",
|
|
30
|
+
"next-sprint",
|
|
31
|
+
];
|
|
32
|
+
/** The only status values `phase-transition` documents. */
|
|
33
|
+
export const PHASE_STATUSES = ["in_progress", "completed", "skipped", "failed", "paused"];
|
|
34
|
+
/** Phase numbers the CLI accepts. */
|
|
35
|
+
const PHASE_NUMBERS = ["1", "2", "3", "4", "5", "6"];
|
|
36
|
+
/** Type guard for the subcommand whitelist. */
|
|
37
|
+
export function isSprintSubcommand(value) {
|
|
38
|
+
return SPRINT_SUBCOMMANDS.includes(value);
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Validate a subcommand, throwing rather than returning a sentinel.
|
|
42
|
+
*
|
|
43
|
+
* A caller that ignores a boolean would fall through to "run nothing" or, worse, to a
|
|
44
|
+
* default subcommand; throwing makes the rejection impossible to overlook.
|
|
45
|
+
*/
|
|
46
|
+
export function assertSprintSubcommand(value) {
|
|
47
|
+
if (!isSprintSubcommand(value)) {
|
|
48
|
+
throw new Error(`Unknown sprint subcommand "${value}". Allowed: ${SPRINT_SUBCOMMANDS.join(", ")}`);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
/** Validate the two positional arguments phase-transition requires. */
|
|
52
|
+
function phaseTokens(options) {
|
|
53
|
+
const phase = options.phase ?? "";
|
|
54
|
+
if (!PHASE_NUMBERS.includes(phase)) {
|
|
55
|
+
throw new Error(`phase must be one of ${PHASE_NUMBERS.join(", ")}; got "${phase}"`);
|
|
56
|
+
}
|
|
57
|
+
const status = options.status ?? "";
|
|
58
|
+
if (!PHASE_STATUSES.includes(status)) {
|
|
59
|
+
throw new Error(`status must be one of ${PHASE_STATUSES.join(", ")}; got "${status}"`);
|
|
60
|
+
}
|
|
61
|
+
return [phase, status];
|
|
62
|
+
}
|
|
63
|
+
/** Append the flags shared by several subcommands. */
|
|
64
|
+
function sharedFlags(options, dialect) {
|
|
65
|
+
const flags = [];
|
|
66
|
+
if (options.json)
|
|
67
|
+
flags.push("--json");
|
|
68
|
+
if (options.all)
|
|
69
|
+
flags.push("--all");
|
|
70
|
+
if (options.days !== undefined && Number.isInteger(options.days) && options.days > 0) {
|
|
71
|
+
flags.push("--days", String(options.days));
|
|
72
|
+
}
|
|
73
|
+
if (options.dir !== undefined)
|
|
74
|
+
flags.push("--dir", shellQuote(options.dir, dialect));
|
|
75
|
+
return flags;
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Build the command line for a whitelisted subcommand.
|
|
79
|
+
*
|
|
80
|
+
* User-influenced values reach the shell in exactly two ways: enumerated membership
|
|
81
|
+
* (subcommand, phase, status) or shell quoting (paths). Nothing is interpolated raw,
|
|
82
|
+
* which is what keeps this from becoming the injection surface a passthrough would be.
|
|
83
|
+
*/
|
|
84
|
+
export function buildSprintCommand(subcommand, options = {}, dialect = "posix") {
|
|
85
|
+
assertSprintSubcommand(subcommand);
|
|
86
|
+
const tokens = ["xp-gate", subcommand];
|
|
87
|
+
if (subcommand === "phase-transition")
|
|
88
|
+
tokens.push(...phaseTokens(options));
|
|
89
|
+
tokens.push(...sharedFlags(options, dialect));
|
|
90
|
+
return tokens.join(" ");
|
|
91
|
+
}
|
|
92
|
+
//# sourceMappingURL=sprint.js.map
|
package/plugins/dsh/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@boyingliu01/dsh-plugin-xp-gate",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.32.0",
|
|
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",
|
|
@@ -42,15 +42,21 @@
|
|
|
42
42
|
},
|
|
43
43
|
"peerDependencies": {
|
|
44
44
|
"@deepseek-ai/cordis": "^4.0.1",
|
|
45
|
-
"@deepseek-ai/dsh-
|
|
46
|
-
"@deepseek-ai/dsh-
|
|
47
|
-
"@deepseek-ai/dsh-
|
|
45
|
+
"@deepseek-ai/dsh-credentials": ">=0.2.0-rc.2 <0.3.0-0",
|
|
46
|
+
"@deepseek-ai/dsh-llm": ">=0.2.0-rc.2 <0.3.0-0",
|
|
47
|
+
"@deepseek-ai/dsh-shell": ">=0.2.0-rc.2 <0.3.0-0",
|
|
48
|
+
"@deepseek-ai/dsh-skill": ">=0.2.0-rc.2 <0.3.0-0",
|
|
49
|
+
"@deepseek-ai/dsh-tools": ">=0.2.0-rc.2 <0.3.0-0",
|
|
50
|
+
"@deepseek-ai/schemastery": "^3.18.5"
|
|
48
51
|
},
|
|
49
52
|
"devDependencies": {
|
|
50
|
-
"@deepseek-ai/cordis": "4.0.
|
|
51
|
-
"@deepseek-ai/dsh-
|
|
52
|
-
"@deepseek-ai/dsh-
|
|
53
|
-
"@deepseek-ai/dsh-
|
|
53
|
+
"@deepseek-ai/cordis": "4.0.4",
|
|
54
|
+
"@deepseek-ai/dsh-credentials": "0.2.0-rc.2",
|
|
55
|
+
"@deepseek-ai/dsh-llm": "0.2.0-rc.2",
|
|
56
|
+
"@deepseek-ai/dsh-shell": "0.2.0-rc.2",
|
|
57
|
+
"@deepseek-ai/dsh-skill": "0.2.0-rc.2",
|
|
58
|
+
"@deepseek-ai/dsh-tools": "0.2.0-rc.2",
|
|
59
|
+
"@deepseek-ai/schemastery": "3.18.5",
|
|
54
60
|
"@types/node": "^25.6.0",
|
|
55
61
|
"typescript": "^5.9.3",
|
|
56
62
|
"vitest": "^1.6.1",
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @test REQ-DSH-006
|
|
3
|
+
* @intent INV-2 end-to-end: drive DSH's REAL SkillRegistry (not a fake), mount the
|
|
4
|
+
* xp-gate plugin through its own apply(), and assert the registry surfaces
|
|
5
|
+
* all 12 bundled skills with non-empty bodies and resolvable resource bases.
|
|
6
|
+
* @covers AC-DSH-006-04
|
|
7
|
+
*
|
|
8
|
+
* Why this exists: every unit test asserts the provider's internal contract. None
|
|
9
|
+
* prove the registry actually receives and ranks the skills. v1 shipped 65 files
|
|
10
|
+
* and passed its own tests while the model saw nothing, so this script uses
|
|
11
|
+
* @deepseek-ai/dsh-skill's SkillRegistry itself as the consumer.
|
|
12
|
+
*
|
|
13
|
+
* Usage: node scripts/verify-skill-provider.mjs <pluginDir>
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import { pathToFileURL } from "node:url"
|
|
17
|
+
import { join } from "node:path"
|
|
18
|
+
|
|
19
|
+
const EXPECTED = 12
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Build a real Cordis Context and mount the DSH SkillRegistry service on it.
|
|
23
|
+
*
|
|
24
|
+
* SkillRegistry extends cordis' Service, whose constructor reflects into the
|
|
25
|
+
* owning context — a plain object literal cannot host it. Cordis also rejects
|
|
26
|
+
* assigning undeclared properties, so the tools/shell seams are installed by a
|
|
27
|
+
* small plugin that declares them the way a real DSH service would.
|
|
28
|
+
*/
|
|
29
|
+
async function makeContext(dshRoot, SkillRegistry) {
|
|
30
|
+
const cordis = await import(
|
|
31
|
+
pathToFileURL(join(dshRoot, "@deepseek-ai", "cordis", "lib", "index.js")).href
|
|
32
|
+
)
|
|
33
|
+
const ctx = new cordis.Context()
|
|
34
|
+
const tools = []
|
|
35
|
+
|
|
36
|
+
const seams = {
|
|
37
|
+
name: "verify-seams",
|
|
38
|
+
apply(scope) {
|
|
39
|
+
scope.provide("tools", { register: (tool) => tools.push(tool) })
|
|
40
|
+
scope.provide("shell", {
|
|
41
|
+
resolve: (request) => ({ ...request }),
|
|
42
|
+
execute: () => Promise.resolve({ result: () => Promise.resolve({}) }),
|
|
43
|
+
})
|
|
44
|
+
},
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
ctx.plugin(seams)
|
|
48
|
+
// SkillRegistry is a Service; mounting it provides ctx.skills.
|
|
49
|
+
await ctx.plugin(SkillRegistry)
|
|
50
|
+
return { ctx, tools }
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export async function verifySkillProvider(pluginDir, dshRoot) {
|
|
54
|
+
const skillModule = await import(
|
|
55
|
+
pathToFileURL(join(dshRoot, "@deepseek-ai", "dsh-skill", "lib", "index.js")).href
|
|
56
|
+
)
|
|
57
|
+
const { SkillRegistry } = skillModule
|
|
58
|
+
|
|
59
|
+
const plugin = await import(pathToFileURL(join(pluginDir, "lib", "index.js")).href)
|
|
60
|
+
const { ctx, tools } = await makeContext(dshRoot, SkillRegistry)
|
|
61
|
+
|
|
62
|
+
const failures = []
|
|
63
|
+
if (!Array.isArray(plugin.inject) || !plugin.inject.includes("skills")) {
|
|
64
|
+
failures.push(`inject does not declare skills: ${JSON.stringify(plugin.inject)}`)
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
plugin.apply(ctx)
|
|
68
|
+
|
|
69
|
+
// Registry-level view: exactly what a model-facing consumer would read.
|
|
70
|
+
const summaries = await ctx.skills.list({ cwd: pluginDir })
|
|
71
|
+
const ours = summaries.filter((s) => s.provider === "xp-gate")
|
|
72
|
+
if (ours.length !== EXPECTED) {
|
|
73
|
+
failures.push(`registry lists ${ours.length} xp-gate skills, expected ${EXPECTED}`)
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const loaded = []
|
|
77
|
+
for (const summary of ours) {
|
|
78
|
+
const definition = await ctx.skills.get(summary.name, { cwd: pluginDir })
|
|
79
|
+
if (!definition) {
|
|
80
|
+
failures.push(`${summary.name}: not loadable through the registry`)
|
|
81
|
+
continue
|
|
82
|
+
}
|
|
83
|
+
if (!definition.content || definition.content.trim().length === 0) {
|
|
84
|
+
failures.push(`${summary.name}: empty body`)
|
|
85
|
+
continue
|
|
86
|
+
}
|
|
87
|
+
if (definition.resourceBase?.kind !== "directory") {
|
|
88
|
+
failures.push(`${summary.name}: resourceBase is ${JSON.stringify(definition.resourceBase)}`)
|
|
89
|
+
}
|
|
90
|
+
loaded.push({ name: summary.name, bytes: definition.content.length })
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
return {
|
|
94
|
+
ok: failures.length === 0,
|
|
95
|
+
reason: failures.join("; "),
|
|
96
|
+
tools: tools.map((t) => t?.name).filter(Boolean),
|
|
97
|
+
count: ours.length,
|
|
98
|
+
loaded,
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const isMain = process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href
|
|
103
|
+
if (isMain) {
|
|
104
|
+
const pluginDir = process.argv[2]
|
|
105
|
+
const dshRoot = process.argv[3] ?? join(pluginDir, "node_modules")
|
|
106
|
+
if (!pluginDir) {
|
|
107
|
+
console.error("usage: node scripts/verify-skill-provider.mjs <pluginDir> [dshNodeModulesRoot]")
|
|
108
|
+
process.exit(2)
|
|
109
|
+
}
|
|
110
|
+
const result = await verifySkillProvider(pluginDir, dshRoot)
|
|
111
|
+
console.log(JSON.stringify(result, null, 2))
|
|
112
|
+
process.exit(result.ok ? 0 : 1)
|
|
113
|
+
}
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
# SKILLS/DELPHI-REVIEW KNOWLEDGE BASE
|
|
2
2
|
|
|
3
|
-
**Generated:** 2026-10-
|
|
4
|
-
**Commit:**
|
|
3
|
+
**Generated:** 2026-10-28
|
|
4
|
+
**Commit:** 0f58c300
|
|
5
5
|
**Branch:** main
|
|
6
|
-
**Version:** 0.
|
|
6
|
+
**Version:** 0.32.0.0
|
|
7
7
|
|
|
8
8
|
## OVERVIEW
|
|
9
9
|
Delphi Consensus Review — multi-round anonymous expert review (≥90% threshold, exactly 3 experts with distinct executable model IDs). Supports design, requirements, and code-walkthrough modes.
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
# SKILLS/SPRINT-FLOW KNOWLEDGE BASE
|
|
2
2
|
|
|
3
|
-
**Generated:** 2026-10-
|
|
4
|
-
**Commit:**
|
|
3
|
+
**Generated:** 2026-10-28
|
|
4
|
+
**Commit:** 0f58c300
|
|
5
5
|
**Branch:** main
|
|
6
|
-
**Version:** 0.
|
|
6
|
+
**Version:** 0.32.0.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-
|
|
4
|
-
**Commit:**
|
|
3
|
+
**Generated:** 2026-10-28
|
|
4
|
+
**Commit:** 0f58c300
|
|
5
5
|
**Branch:** main
|
|
6
|
-
**Version:** 0.
|
|
6
|
+
**Version:** 0.32.0.0
|
|
7
7
|
|
|
8
8
|
## OVERVIEW
|
|
9
9
|
Test-Specification Alignment Engine — two-stage validation ensuring tests accurately reflect requirements and design specs.
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @test REQ-DSH-011 single-form command construction
|
|
3
|
+
* @intent Verify buildCommand emits one unconditional xp-gate invocation per dialect,
|
|
4
|
+
* appends an explicit exit-code echo, and no longer branches on whether the
|
|
5
|
+
* CLI resolves — so "installed but broken" can never look like a pass.
|
|
6
|
+
* @covers AC-DSH-011-07 AC-DSH-011-08
|
|
7
|
+
*
|
|
8
|
+
* Why: the old form probed with `command -v` / `Get-Command` and printed a hint on
|
|
9
|
+
* the else branch while leaving the exit code at 0. A CLI that resolved but then
|
|
10
|
+
* failed produced empty stdout with exit 0, which the model read as green.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { describe, it, expect } from "vitest"
|
|
14
|
+
import { buildCommand, GATE_WHITELIST, isGateAllowed } from "./command.js"
|
|
15
|
+
|
|
16
|
+
describe("buildCommand single form", () => {
|
|
17
|
+
it("never branches on CLI presence in POSIX form", () => {
|
|
18
|
+
const command = buildCommand({ subcommand: "check", target: "src", dialect: "posix" })
|
|
19
|
+
expect(command).not.toContain("command -v")
|
|
20
|
+
expect(command).not.toContain("if ")
|
|
21
|
+
expect(command.trimStart().startsWith("xp-gate")).toBe(true)
|
|
22
|
+
})
|
|
23
|
+
|
|
24
|
+
it("never branches on CLI presence in PowerShell form", () => {
|
|
25
|
+
const command = buildCommand({ subcommand: "check", target: "src", dialect: "powershell" })
|
|
26
|
+
expect(command).not.toContain("Get-Command")
|
|
27
|
+
expect(command.trimStart().startsWith("xp-gate")).toBe(true)
|
|
28
|
+
})
|
|
29
|
+
|
|
30
|
+
it("echoes the exit status so a missing CLI becomes a nonzero exit", () => {
|
|
31
|
+
const posix = buildCommand({ subcommand: "principles", dialect: "posix" })
|
|
32
|
+
expect(posix).toContain("exit:")
|
|
33
|
+
expect(posix).toContain("2>&1")
|
|
34
|
+
|
|
35
|
+
const powershell = buildCommand({ subcommand: "principles", dialect: "powershell" })
|
|
36
|
+
expect(powershell).toContain("LASTEXITCODE")
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
it("keeps quoting model-controlled arguments in both dialects", () => {
|
|
40
|
+
const posix = buildCommand({ subcommand: "check", target: "a b; rm -rf /", dialect: "posix" })
|
|
41
|
+
expect(posix).toContain("'a b; rm -rf /'")
|
|
42
|
+
|
|
43
|
+
const powershell = buildCommand({
|
|
44
|
+
subcommand: "check",
|
|
45
|
+
target: "a b; rm -rf /",
|
|
46
|
+
dialect: "powershell",
|
|
47
|
+
})
|
|
48
|
+
expect(powershell).toContain("'a b; rm -rf /'")
|
|
49
|
+
})
|
|
50
|
+
|
|
51
|
+
it("filters gates through the allowlist before they reach the shell", () => {
|
|
52
|
+
const command = buildCommand({
|
|
53
|
+
subcommand: "check",
|
|
54
|
+
target: "src",
|
|
55
|
+
gates: ["principles", "not-a-gate", "arch"],
|
|
56
|
+
dialect: "posix",
|
|
57
|
+
})
|
|
58
|
+
expect(command).toContain("principles,arch")
|
|
59
|
+
expect(command).not.toContain("not-a-gate")
|
|
60
|
+
})
|
|
61
|
+
|
|
62
|
+
it("knows which gate ids are allowed", () => {
|
|
63
|
+
expect(isGateAllowed("principles")).toBe(true)
|
|
64
|
+
expect(isGateAllowed("nope")).toBe(false)
|
|
65
|
+
expect(GATE_WHITELIST.length).toBeGreaterThan(0)
|
|
66
|
+
})
|
|
67
|
+
})
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { describe, it, expect } from "vitest"
|
|
2
2
|
import { resolve } from "node:path"
|
|
3
3
|
import {
|
|
4
|
-
FALLBACK_MESSAGE,
|
|
5
4
|
GATE_WHITELIST,
|
|
6
5
|
buildCommand,
|
|
7
6
|
isGateAllowed,
|
|
@@ -44,11 +43,12 @@ describe("isGateAllowed (enum whitelist)", () => {
|
|
|
44
43
|
})
|
|
45
44
|
|
|
46
45
|
describe("buildCommand", () => {
|
|
47
|
-
it("builds
|
|
46
|
+
it("builds an unconditional gate-check command with target + gates", () => {
|
|
48
47
|
const cmd = buildCommand({ subcommand: "check", target: "src", gates: ["principles", "arch"] })
|
|
49
|
-
|
|
48
|
+
// No presence probe: the command runs regardless and reports its own status.
|
|
50
49
|
expect(cmd).toContain("xp-gate check 'src' --gates 'principles,arch'")
|
|
51
|
-
expect(cmd).toContain(
|
|
50
|
+
expect(cmd).toContain("[XP-Gate] exit:")
|
|
51
|
+
expect(cmd).not.toContain("command -v")
|
|
52
52
|
})
|
|
53
53
|
|
|
54
54
|
it("drops non-whitelisted gates before building the command", () => {
|
|
@@ -86,13 +86,16 @@ describe("buildCommand", () => {
|
|
|
86
86
|
|
|
87
87
|
/**
|
|
88
88
|
* @test REQ-DSH-005
|
|
89
|
-
* @intent 验证 xp-gate CLI
|
|
89
|
+
* @intent 验证 xp-gate CLI 缺失时不再走 else 分支伪装成功:命令无条件执行并回显退出码,
|
|
90
|
+
* 由渲染层把 command-not-found 判为 INDETERMINATE 并附安装提示。
|
|
91
|
+
* 优雅降级保留(不抛错、不崩),但不再静默返回 exit 0。
|
|
90
92
|
* @covers AC-DSH-005-01
|
|
91
93
|
*/
|
|
92
|
-
it("
|
|
94
|
+
it("degrades through the exit code instead of a fake success branch", () => {
|
|
93
95
|
const cmd = buildCommand({ subcommand: "check", target: "src" })
|
|
94
|
-
|
|
95
|
-
expect(cmd).toContain(
|
|
96
|
+
// No else branch: absence becomes a real nonzero exit the renderer can see.
|
|
97
|
+
expect(cmd).not.toContain("else printf")
|
|
98
|
+
expect(cmd).toContain("[XP-Gate] exit:")
|
|
96
99
|
})
|
|
97
100
|
})
|
|
98
101
|
|
|
@@ -122,32 +125,32 @@ describe("GATE_WHITELIST", () => {
|
|
|
122
125
|
|
|
123
126
|
/**
|
|
124
127
|
* @test REQ-DSH-010
|
|
125
|
-
* @intent 验证 PowerShell 方言下生成的命令使用
|
|
126
|
-
*
|
|
128
|
+
* @intent 验证 PowerShell 方言下生成的命令使用 Write-Output/$LASTEXITCODE 而非 POSIX 的
|
|
129
|
+
* printf/$?,避免 DSH 在 win32(pwsh executor)下因语法错误导致三个工具全部不可用
|
|
127
130
|
* @covers AC-DSH-010-01
|
|
128
131
|
*/
|
|
129
132
|
describe("PowerShell dialect (regression: DSH win32 uses the pwsh executor)", () => {
|
|
130
|
-
it("
|
|
133
|
+
it("reports the exit code with $LASTEXITCODE instead of POSIX $?", () => {
|
|
131
134
|
const cmd = buildCommand({ subcommand: "principles", target: "src/x.ts", dialect: "powershell" })
|
|
132
|
-
expect(cmd).toContain("
|
|
135
|
+
expect(cmd).toContain("$LASTEXITCODE")
|
|
133
136
|
expect(cmd).not.toContain("command -v")
|
|
137
|
+
expect(cmd).not.toContain("Get-Command")
|
|
134
138
|
})
|
|
135
139
|
|
|
136
|
-
it("uses Write-Output instead of printf for the
|
|
140
|
+
it("uses Write-Output instead of printf for the exit report", () => {
|
|
137
141
|
const cmd = buildCommand({ subcommand: "check", target: "src", dialect: "powershell" })
|
|
138
142
|
expect(cmd).toContain("Write-Output")
|
|
139
143
|
expect(cmd).not.toContain("printf")
|
|
140
|
-
expect(cmd).toContain(FALLBACK_MESSAGE)
|
|
141
144
|
})
|
|
142
145
|
|
|
143
146
|
it("never emits POSIX shell syntax that PowerShell cannot parse", () => {
|
|
144
147
|
const cmd = buildCommand({ subcommand: "check", target: "src", gates: ["principles"], dialect: "powershell" })
|
|
145
148
|
// `/dev/null` and `; then … ; fi` are the constructs that produced
|
|
146
|
-
// "if语句中的'if'后缺少\"(\"" under the pwsh executor.
|
|
149
|
+
// "if语句中的'if'后缺少\"(\"" under the pwsh executor. `2>&1` IS valid in
|
|
150
|
+
// PowerShell and is used deliberately to merge stderr into the report.
|
|
147
151
|
expect(cmd).not.toContain("/dev/null")
|
|
148
152
|
expect(cmd).not.toContain("then ")
|
|
149
153
|
expect(cmd).not.toContain("; fi")
|
|
150
|
-
expect(cmd).not.toContain("2>&1")
|
|
151
154
|
})
|
|
152
155
|
|
|
153
156
|
it("preserves the xp-gate invocation and gate subset", () => {
|
|
@@ -173,6 +176,7 @@ describe("PowerShell dialect (regression: DSH win32 uses the pwsh executor)", ()
|
|
|
173
176
|
|
|
174
177
|
it("defaults to the POSIX dialect when none is given (backwards compatible)", () => {
|
|
175
178
|
const cmd = buildCommand({ subcommand: "arch" })
|
|
176
|
-
expect(cmd).toContain("
|
|
179
|
+
expect(cmd).toContain("printf")
|
|
180
|
+
expect(cmd).not.toContain("Write-Output")
|
|
177
181
|
})
|
|
178
182
|
})
|
|
@@ -5,10 +5,6 @@ import { type ShellDialect, shellQuote } from "./dialect.js"
|
|
|
5
5
|
// primitives themselves live in ./dialect.ts.
|
|
6
6
|
export { type ShellDialect, detectDialect, shellQuote, shq } from "./dialect.js"
|
|
7
7
|
|
|
8
|
-
/** Shown when the global `xp-gate` CLI is not on PATH. */
|
|
9
|
-
export const FALLBACK_MESSAGE =
|
|
10
|
-
"[XP-Gate] xp-gate CLI not installed. Install it with: npm install -g @boyingliu01/xp-gate"
|
|
11
|
-
|
|
12
8
|
/**
|
|
13
9
|
* Allowlist of xp-gate gate aliases that are safe to invoke standalone
|
|
14
10
|
* (non-preCommitOnly gates + common aliases). Anything outside this set is
|
|
@@ -73,29 +69,31 @@ function buildInner(options: BuildCommandOptions, dialect: ShellDialect): string
|
|
|
73
69
|
}
|
|
74
70
|
|
|
75
71
|
/**
|
|
76
|
-
* Build a single
|
|
77
|
-
*
|
|
78
|
-
*
|
|
72
|
+
* Build a single unconditional shell command.
|
|
73
|
+
*
|
|
74
|
+
* There is deliberately no presence probe. An earlier version wrapped the call
|
|
75
|
+
* in `if command -v xp-gate …; then …; else print hint; fi`, which left the exit
|
|
76
|
+
* code at 0 on the else branch — so a CLI that resolved but then failed produced
|
|
77
|
+
* empty stdout with exit 0, and the model read that as a pass.
|
|
78
|
+
*
|
|
79
|
+
* Running unconditionally turns "not installed" into an ordinary command-not-found
|
|
80
|
+
* (nonzero exit, empty output), which renderGateResult classifies as
|
|
81
|
+
* INDETERMINATE and pairs with an install hint. It also removes the branch that
|
|
82
|
+
* could not distinguish "absent" from "present but broken".
|
|
79
83
|
*/
|
|
80
84
|
export function buildCommand(options: BuildCommandOptions): string {
|
|
81
85
|
const dialect = options.dialect ?? "posix"
|
|
82
86
|
const inner = buildInner(options, dialect)
|
|
83
87
|
if (dialect === "posix") {
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
+
// Merge stderr into stdout so a failure that only writes to stderr still
|
|
89
|
+
// reaches the renderer, then echo the status so the exit code survives the
|
|
90
|
+
// pipeline.
|
|
91
|
+
return `${inner} 2>&1; printf '\\n[XP-Gate] exit:%s\\n' "$?"`
|
|
88
92
|
}
|
|
89
|
-
// PowerShell:
|
|
90
|
-
// %APPDATA%\npm
|
|
91
|
-
//
|
|
92
|
-
|
|
93
|
-
// The hint branch leaves $LASTEXITCODE untouched at 0, matching the POSIX
|
|
94
|
-
// branch's `printf` behaviour.
|
|
95
|
-
return `if (Get-Command xp-gate -ErrorAction SilentlyContinue) { ${inner} } else { Write-Output ${shellQuote(
|
|
96
|
-
FALLBACK_MESSAGE,
|
|
97
|
-
dialect,
|
|
98
|
-
)} }`
|
|
93
|
+
// PowerShell: merge streams and report $LASTEXITCODE explicitly. The
|
|
94
|
+
// .cmd/.ps1 shims npm writes under %APPDATA%\npm are visible here but not to a
|
|
95
|
+
// Git-Bash PATH lookup, which is why Windows uses this dialect.
|
|
96
|
+
return `${inner} 2>&1; Write-Output "[XP-Gate] exit:$LASTEXITCODE"`
|
|
99
97
|
}
|
|
100
98
|
|
|
101
99
|
/** Resolve a model-supplied path against the session cwd; absolutes pass through. */
|