@boyingliu01/xp-gate 0.35.2 → 0.35.4
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/gate-4.sh +33 -4
- package/lib/__tests__/flaky.test.js +128 -0
- package/lib/doctor.js +56 -0
- package/lib/flaky.js +208 -0
- package/lib/gate-audit.ts +15 -0
- package/lib/mattpocock-license-verdict.json +1 -1
- package/lib/retro.js +26 -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/command.d.ts +1 -1
- package/plugins/dsh/lib/command.d.ts.map +1 -1
- package/plugins/dsh/lib/command.js +9 -3
- package/plugins/dsh/lib/dialect.d.ts +22 -2
- package/plugins/dsh/lib/dialect.d.ts.map +1 -1
- package/plugins/dsh/lib/dialect.js +37 -2
- package/plugins/dsh/lib/gate-runner.d.ts.map +1 -1
- package/plugins/dsh/lib/gate-runner.js +22 -0
- package/plugins/dsh/lib/index.d.ts.map +1 -1
- package/plugins/dsh/lib/index.js +7 -4
- 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/command.ts +9 -3
- package/plugins/dsh/src/dialect.ts +45 -2
- package/plugins/dsh/src/exit-code.test.ts +83 -0
- package/plugins/dsh/src/gate-runner.ts +23 -0
- package/plugins/dsh/src/index.ts +7 -4
- package/plugins/opencode/package.json +1 -1
- 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
|
@@ -17,8 +17,28 @@ export declare function shq(value: string): string;
|
|
|
17
17
|
/**
|
|
18
18
|
* Pick the dialect for the current platform.
|
|
19
19
|
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
20
|
+
* @deprecated Platform is NOT the right signal (#669). It encodes the assumption
|
|
21
|
+
* "win32 => PowerShell executor", which held when this was written but is now
|
|
22
|
+
* false: Windows can mount a Git Bash executor, and a PowerShell command string
|
|
23
|
+
* handed to bash fails to parse. Use {@link detectDialectForShell} wherever a
|
|
24
|
+
* shell is available; this remains only as the last-resort fallback.
|
|
22
25
|
*/
|
|
23
26
|
export declare function detectDialect(platform?: NodeJS.Platform): ShellDialect;
|
|
27
|
+
/** Structural view of a shell executor — only the fields that reveal its dialect. */
|
|
28
|
+
export interface DialectBearingShell {
|
|
29
|
+
/** Explicit dialect/kind announcement, when the executor offers one. */
|
|
30
|
+
shellDialect?: unknown;
|
|
31
|
+
dialect?: unknown;
|
|
32
|
+
/** Absolute path to the resolved bash binary — the Git Bash executor's tell. */
|
|
33
|
+
bashPath?: unknown;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Pick the dialect from the executor that will actually run the command (#669).
|
|
37
|
+
*
|
|
38
|
+
* Detection is structural — an explicit announcement, or a resolved `bashPath` —
|
|
39
|
+
* never a class name, so this stays decoupled from any particular executor
|
|
40
|
+
* package. When nothing is recognisable it falls back to the platform default,
|
|
41
|
+
* which equals the old behaviour and is therefore never worse.
|
|
42
|
+
*/
|
|
43
|
+
export declare function detectDialectForShell(shell?: unknown, platform?: NodeJS.Platform): ShellDialect;
|
|
24
44
|
//# sourceMappingURL=dialect.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dialect.d.ts","sourceRoot":"","sources":["../src/dialect.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,MAAM,MAAM,YAAY,GAAG,OAAO,GAAG,YAAY,CAAA;AAEjD,iDAAiD;AACjD,wBAAgB,UAAU,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,YAAY,GAAG,MAAM,CASvE;AAED;;;GAGG;AACH,wBAAgB,GAAG,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAEzC;AAED
|
|
1
|
+
{"version":3,"file":"dialect.d.ts","sourceRoot":"","sources":["../src/dialect.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,MAAM,MAAM,YAAY,GAAG,OAAO,GAAG,YAAY,CAAA;AAEjD,iDAAiD;AACjD,wBAAgB,UAAU,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,YAAY,GAAG,MAAM,CASvE;AAED;;;GAGG;AACH,wBAAgB,GAAG,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAEzC;AAED;;;;;;;;GAQG;AACH,wBAAgB,aAAa,CAAC,QAAQ,GAAE,MAAM,CAAC,QAA2B,GAAG,YAAY,CAExF;AAED,qFAAqF;AACrF,MAAM,WAAW,mBAAmB;IAClC,wEAAwE;IACxE,YAAY,CAAC,EAAE,OAAO,CAAA;IACtB,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB,gFAAgF;IAChF,QAAQ,CAAC,EAAE,OAAO,CAAA;CACnB;AAYD;;;;;;;GAOG;AACH,wBAAgB,qBAAqB,CACnC,KAAK,CAAC,EAAE,OAAO,EACf,QAAQ,GAAE,MAAM,CAAC,QAA2B,GAC3C,YAAY,CAQd"}
|
|
@@ -27,10 +27,45 @@ export function shq(value) {
|
|
|
27
27
|
/**
|
|
28
28
|
* Pick the dialect for the current platform.
|
|
29
29
|
*
|
|
30
|
-
*
|
|
31
|
-
*
|
|
30
|
+
* @deprecated Platform is NOT the right signal (#669). It encodes the assumption
|
|
31
|
+
* "win32 => PowerShell executor", which held when this was written but is now
|
|
32
|
+
* false: Windows can mount a Git Bash executor, and a PowerShell command string
|
|
33
|
+
* handed to bash fails to parse. Use {@link detectDialectForShell} wherever a
|
|
34
|
+
* shell is available; this remains only as the last-resort fallback.
|
|
32
35
|
*/
|
|
33
36
|
export function detectDialect(platform = process.platform) {
|
|
34
37
|
return platform === "win32" ? "powershell" : "posix";
|
|
35
38
|
}
|
|
39
|
+
function announceDialect(shell) {
|
|
40
|
+
for (const raw of [shell.shellDialect, shell.dialect]) {
|
|
41
|
+
if (typeof raw !== "string")
|
|
42
|
+
continue;
|
|
43
|
+
const lower = raw.toLowerCase();
|
|
44
|
+
if (lower.includes("bash") || lower.includes("posix"))
|
|
45
|
+
return "posix";
|
|
46
|
+
if (lower.includes("pwsh") || lower.includes("powershell"))
|
|
47
|
+
return "powershell";
|
|
48
|
+
}
|
|
49
|
+
return null;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Pick the dialect from the executor that will actually run the command (#669).
|
|
53
|
+
*
|
|
54
|
+
* Detection is structural — an explicit announcement, or a resolved `bashPath` —
|
|
55
|
+
* never a class name, so this stays decoupled from any particular executor
|
|
56
|
+
* package. When nothing is recognisable it falls back to the platform default,
|
|
57
|
+
* which equals the old behaviour and is therefore never worse.
|
|
58
|
+
*/
|
|
59
|
+
export function detectDialectForShell(shell, platform = process.platform) {
|
|
60
|
+
const fallback = detectDialect(platform);
|
|
61
|
+
if (shell === null || typeof shell !== "object")
|
|
62
|
+
return fallback;
|
|
63
|
+
const view = shell;
|
|
64
|
+
const announced = announceDialect(view);
|
|
65
|
+
if (announced !== null)
|
|
66
|
+
return announced;
|
|
67
|
+
if (typeof view.bashPath === "string" && view.bashPath.length > 0)
|
|
68
|
+
return "posix";
|
|
69
|
+
return fallback;
|
|
70
|
+
}
|
|
36
71
|
//# sourceMappingURL=dialect.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"gate-runner.d.ts","sourceRoot":"","sources":["../src/gate-runner.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,gBAAgB,EAAE,aAAa,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAA;AAE7F;;;;;;;;GAQG;AACH,MAAM,WAAW,WAAW;IAC1B,OAAO,CAAC,OAAO,EAAE,gBAAgB,GAAG,aAAa,CAAA;IACjD,OAAO,CAAC,IAAI,EAAE,aAAa,GAAG,OAAO,CAAC;QAAE,MAAM,IAAI,OAAO,CAAC,cAAc,CAAC,CAAA;KAAE,CAAC,CAAA;CAC7E;AAED,kFAAkF;AAClF,MAAM,MAAM,WAAW,GAAG,MAAM,GAAG,aAAa,GAAG,eAAe,CAAA;AAElE;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,eAAe,CAAC,MAAM,EAAE,cAAc,GAAG,WAAW,
|
|
1
|
+
{"version":3,"file":"gate-runner.d.ts","sourceRoot":"","sources":["../src/gate-runner.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,gBAAgB,EAAE,aAAa,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAA;AAE7F;;;;;;;;GAQG;AACH,MAAM,WAAW,WAAW;IAC1B,OAAO,CAAC,OAAO,EAAE,gBAAgB,GAAG,aAAa,CAAA;IACjD,OAAO,CAAC,IAAI,EAAE,aAAa,GAAG,OAAO,CAAC;QAAE,MAAM,IAAI,OAAO,CAAC,cAAc,CAAC,CAAA;KAAE,CAAC,CAAA;CAC7E;AAED,kFAAkF;AAClF,MAAM,MAAM,WAAW,GAAG,MAAM,GAAG,aAAa,GAAG,eAAe,CAAA;AAElE;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,eAAe,CAAC,MAAM,EAAE,cAAc,GAAG,WAAW,CAuBnE;AAyCD;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,cAAc,GAAG,MAAM,CAa/D;AAED;;;;GAIG;AACH,wBAAsB,SAAS,CAC7B,KAAK,EAAE,WAAW,EAClB,MAAM,EAAE,WAAW,EACnB,OAAO,EAAE,MAAM,EACf,OAAO,EAAE,MAAM,EACf,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,MAAM,CAAC,CAUjB"}
|
|
@@ -21,12 +21,34 @@ export function classifyGateRun(result) {
|
|
|
21
21
|
return "INDETERMINATE";
|
|
22
22
|
if (result.stdout.truncated)
|
|
23
23
|
return "INDETERMINATE";
|
|
24
|
+
// Cross-check the reported exit code against the marker our own command
|
|
25
|
+
// suffix wrote into stdout (#669).
|
|
26
|
+
//
|
|
27
|
+
// The exit code travels a fragile chain: shell -> harness -> classify. The
|
|
28
|
+
// marker is the same fact recorded twice by independent paths, so a
|
|
29
|
+
// disagreement means one of them was mangled, and a run whose outcome cannot
|
|
30
|
+
// be established must not be reported as a pass. This is the backstop for the
|
|
31
|
+
// case where the exit code is lost outside our control -- the suffix fix makes
|
|
32
|
+
// the two agree, but only an explicit check proves they do.
|
|
33
|
+
const marker = parseExitMarker(stdout);
|
|
34
|
+
if (marker !== null && result.exitCode !== null && marker !== result.exitCode)
|
|
35
|
+
return "INDETERMINATE";
|
|
24
36
|
if (result.exitCode === 0)
|
|
25
37
|
return stdout.length > 0 ? "PASS" : "INDETERMINATE";
|
|
26
38
|
// Nonzero: a verdict exists only when the run produced something to show.
|
|
27
39
|
const stderr = result.stderr.text.trim();
|
|
28
40
|
return stdout.length > 0 || stderr.length > 0 ? "GATE FAILED" : "INDETERMINATE";
|
|
29
41
|
}
|
|
42
|
+
/** Read the `[XP-Gate] exit:<n>` marker the command suffix appends, if present. */
|
|
43
|
+
function parseExitMarker(stdout) {
|
|
44
|
+
const matches = [...stdout.matchAll(/\[XP-Gate\] exit:(-?\d+)/g)];
|
|
45
|
+
if (matches.length === 0)
|
|
46
|
+
return null;
|
|
47
|
+
// Take the LAST marker: a run whose output happens to contain an earlier one
|
|
48
|
+
// must be judged by the status our suffix wrote at the end.
|
|
49
|
+
const value = Number.parseInt(matches[matches.length - 1][1], 10);
|
|
50
|
+
return Number.isNaN(value) ? null : value;
|
|
51
|
+
}
|
|
30
52
|
/** Detailed diagnostics appended after the verdict line, when present. */
|
|
31
53
|
function renderDetails(result) {
|
|
32
54
|
const lines = [];
|
|
@@ -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,
|
|
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"}
|
package/plugins/dsh/lib/index.js
CHANGED
|
@@ -3,7 +3,7 @@ import { join } from "node:path";
|
|
|
3
3
|
import { fileURLToPath } from "node:url";
|
|
4
4
|
import { defineTool } from "@deepseek-ai/dsh-tools";
|
|
5
5
|
import z from "@deepseek-ai/schemastery";
|
|
6
|
-
import { GATE_WHITELIST, buildCommand,
|
|
6
|
+
import { GATE_WHITELIST, buildCommand, detectDialectForShell, resolveTarget } from "./command.js";
|
|
7
7
|
import { runXpGate } from "./gate-runner.js";
|
|
8
8
|
import { DelphiSetupError, expandEnvDetailed, listCredentialReferences, loadDelphiConfig, toDelphiOptions, } from "./delphi-config.js";
|
|
9
9
|
import { DelphiConfigError } from "./delphi.js";
|
|
@@ -409,10 +409,13 @@ export const Config = z.object({
|
|
|
409
409
|
});
|
|
410
410
|
export function apply(ctx, config = {}) {
|
|
411
411
|
const cfg = config;
|
|
412
|
-
// DSH runs a POSIX shell off-Windows and PowerShell on Windows; generating the
|
|
413
|
-
// wrong dialect makes every tool fail to parse rather than degrade gracefully.
|
|
414
|
-
const dialect = detectDialect();
|
|
415
412
|
const shell = ctx.shell;
|
|
413
|
+
// The EXECUTOR decides the dialect, not the platform (#669). "win32 implies
|
|
414
|
+
// PowerShell" stopped being true once Windows could mount a Git Bash executor:
|
|
415
|
+
// a PowerShell command string handed to bash exits 127 on a line that does not
|
|
416
|
+
// parse, so every gate was reported as failed. Falls back to the platform
|
|
417
|
+
// default when the executor does not reveal itself.
|
|
418
|
+
const dialect = detectDialectForShell(shell);
|
|
416
419
|
// ctx.credentials is optional: outside DSH there is no store, and the resolver
|
|
417
420
|
// then reads the process environment directly. Inside DSH this is what makes
|
|
418
421
|
// delphi-review work on a machine whose key lives in the credential store rather
|
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.4",
|
|
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:** aefe7f40
|
|
5
5
|
**Branch:** main
|
|
6
|
-
**Version:** 0.35.
|
|
6
|
+
**Version:** 0.35.4.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:** aefe7f40
|
|
5
5
|
**Branch:** main
|
|
6
|
-
**Version:** 0.35.
|
|
6
|
+
**Version:** 0.35.4.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:** aefe7f40
|
|
5
5
|
**Branch:** main
|
|
6
|
-
**Version:** 0.35.
|
|
6
|
+
**Version:** 0.35.4.0
|
|
7
7
|
|
|
8
8
|
## OVERVIEW
|
|
9
9
|
Test-Specification Alignment Engine — two-stage validation ensuring tests accurately reflect requirements and design specs.
|
|
@@ -3,7 +3,7 @@ import { type ShellDialect, shellQuote } from "./dialect.js"
|
|
|
3
3
|
|
|
4
4
|
// Re-exported so existing importers of this module keep working; the dialect
|
|
5
5
|
// primitives themselves live in ./dialect.ts.
|
|
6
|
-
export { type ShellDialect, detectDialect, shellQuote, shq } from "./dialect.js"
|
|
6
|
+
export { type ShellDialect, detectDialect, detectDialectForShell, shellQuote, shq } from "./dialect.js"
|
|
7
7
|
|
|
8
8
|
/**
|
|
9
9
|
* Allowlist of xp-gate gate aliases that are safe to invoke standalone
|
|
@@ -88,12 +88,18 @@ export function buildCommand(options: BuildCommandOptions): string {
|
|
|
88
88
|
// Merge stderr into stdout so a failure that only writes to stderr still
|
|
89
89
|
// reaches the renderer, then echo the status so the exit code survives the
|
|
90
90
|
// pipeline.
|
|
91
|
-
|
|
91
|
+
// Capture the status BEFORE printing it, then `exit` with it. Without the
|
|
92
|
+
// explicit `exit`, the compound command's status is printf's -- always 0 --
|
|
93
|
+
// so classifyGateRun() would read a failed gate as PASS (#669).
|
|
94
|
+
return `${inner} 2>&1; __rc=$?; printf '\\n[XP-Gate] exit:%s\\n' "$__rc"; exit $__rc`
|
|
92
95
|
}
|
|
93
96
|
// PowerShell: merge streams and report $LASTEXITCODE explicitly. The
|
|
94
97
|
// .cmd/.ps1 shims npm writes under %APPDATA%\npm are visible here but not to a
|
|
95
98
|
// Git-Bash PATH lookup, which is why Windows uses this dialect.
|
|
96
|
-
|
|
99
|
+
//
|
|
100
|
+
// Same defect as the POSIX branch (#669): Write-Output resets the status, so
|
|
101
|
+
// the failing gate's code must be captured and re-exited explicitly.
|
|
102
|
+
return `${inner} 2>&1; $__rc=$LASTEXITCODE; Write-Output "[XP-Gate] exit:$__rc"; exit $__rc`
|
|
97
103
|
}
|
|
98
104
|
|
|
99
105
|
/** Resolve a model-supplied path against the session cwd; absolutes pass through. */
|
|
@@ -32,9 +32,52 @@ export function shq(value: string): string {
|
|
|
32
32
|
/**
|
|
33
33
|
* Pick the dialect for the current platform.
|
|
34
34
|
*
|
|
35
|
-
*
|
|
36
|
-
*
|
|
35
|
+
* @deprecated Platform is NOT the right signal (#669). It encodes the assumption
|
|
36
|
+
* "win32 => PowerShell executor", which held when this was written but is now
|
|
37
|
+
* false: Windows can mount a Git Bash executor, and a PowerShell command string
|
|
38
|
+
* handed to bash fails to parse. Use {@link detectDialectForShell} wherever a
|
|
39
|
+
* shell is available; this remains only as the last-resort fallback.
|
|
37
40
|
*/
|
|
38
41
|
export function detectDialect(platform: NodeJS.Platform = process.platform): ShellDialect {
|
|
39
42
|
return platform === "win32" ? "powershell" : "posix"
|
|
40
43
|
}
|
|
44
|
+
|
|
45
|
+
/** Structural view of a shell executor — only the fields that reveal its dialect. */
|
|
46
|
+
export interface DialectBearingShell {
|
|
47
|
+
/** Explicit dialect/kind announcement, when the executor offers one. */
|
|
48
|
+
shellDialect?: unknown
|
|
49
|
+
dialect?: unknown
|
|
50
|
+
/** Absolute path to the resolved bash binary — the Git Bash executor's tell. */
|
|
51
|
+
bashPath?: unknown
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function announceDialect(shell: DialectBearingShell): ShellDialect | null {
|
|
55
|
+
for (const raw of [shell.shellDialect, shell.dialect]) {
|
|
56
|
+
if (typeof raw !== "string") continue
|
|
57
|
+
const lower = raw.toLowerCase()
|
|
58
|
+
if (lower.includes("bash") || lower.includes("posix")) return "posix"
|
|
59
|
+
if (lower.includes("pwsh") || lower.includes("powershell")) return "powershell"
|
|
60
|
+
}
|
|
61
|
+
return null
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Pick the dialect from the executor that will actually run the command (#669).
|
|
66
|
+
*
|
|
67
|
+
* Detection is structural — an explicit announcement, or a resolved `bashPath` —
|
|
68
|
+
* never a class name, so this stays decoupled from any particular executor
|
|
69
|
+
* package. When nothing is recognisable it falls back to the platform default,
|
|
70
|
+
* which equals the old behaviour and is therefore never worse.
|
|
71
|
+
*/
|
|
72
|
+
export function detectDialectForShell(
|
|
73
|
+
shell?: unknown,
|
|
74
|
+
platform: NodeJS.Platform = process.platform,
|
|
75
|
+
): ShellDialect {
|
|
76
|
+
const fallback = detectDialect(platform)
|
|
77
|
+
if (shell === null || typeof shell !== "object") return fallback
|
|
78
|
+
const view = shell as DialectBearingShell
|
|
79
|
+
const announced = announceDialect(view)
|
|
80
|
+
if (announced !== null) return announced
|
|
81
|
+
if (typeof view.bashPath === "string" && view.bashPath.length > 0) return "posix"
|
|
82
|
+
return fallback
|
|
83
|
+
}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @test REQ-669 Gate 判定不得因命令串自身缺陷而失真
|
|
3
|
+
* @intent 这两个缺陷都不是"慢"或"报错",而是**给出错误的结论**——尤其是假绿:
|
|
4
|
+
*
|
|
5
|
+
* 1. 方言按 `process.platform` 猜。它假设 "win32 ⇒ PowerShell",这个前提在
|
|
6
|
+
* #442 时代成立;但 Windows 现在可以挂 Git Bash executor
|
|
7
|
+
* (`@very12345/dsh-bash-windows`)。于是生成 PowerShell 命令串丢进 bash,
|
|
8
|
+
* `Write-Output` 不存在 → 退出码 127 → 通过的 gate 报 GATE FAILED。
|
|
9
|
+
*
|
|
10
|
+
* 2. 退出码靠尾部文本标记传递,但**标记本身吃掉了退出码**。两个分支都用
|
|
11
|
+
* `printf`/`Write-Output` 收尾且都不 `exit`,复合命令的退出码等于最后一条
|
|
12
|
+
* 命令的退出码 → 恒为 0。而 `classifyGateRun()` 只读 `result.exitCode`,
|
|
13
|
+
* 从不看 stdout 里的 `exit:N`。于是**失败的 gate 被判 PASS**。
|
|
14
|
+
*
|
|
15
|
+
* 缺陷二更危险:缺陷一至少会让人怀疑,缺陷二会让人相信。
|
|
16
|
+
*
|
|
17
|
+
* 所以这里不靠字符串匹配断言——那证明不了退出码真的传出去了。真正的证明是
|
|
18
|
+
* 起一个真实 shell 跑一遍,断言进程退出码等于内层命令的真实退出码。
|
|
19
|
+
* @covers AC-669-01, AC-669-02, AC-669-03, AC-669-04
|
|
20
|
+
*/
|
|
21
|
+
import { execFileSync } from "node:child_process"
|
|
22
|
+
import { buildCommand, detectDialectForShell } from "./command.js"
|
|
23
|
+
import { classifyGateRun } from "./gate-runner.js"
|
|
24
|
+
|
|
25
|
+
/** 用一个必然退出的内层命令替换真实 xp-gate,只考察后缀对退出码的传递。 */
|
|
26
|
+
function runInRealShell(command: string, shell: "bash" | "bash"): number {
|
|
27
|
+
try {
|
|
28
|
+
execFileSync(shell, ["-c", command], { stdio: "pipe", timeout: 20_000 })
|
|
29
|
+
return 0
|
|
30
|
+
} catch (error) {
|
|
31
|
+
const code = (error as { status?: number | null }).status
|
|
32
|
+
// `status` is null when the process was killed by a signal; surface that as
|
|
33
|
+
// a distinct value so a hang can never be mistaken for a clean exit.
|
|
34
|
+
return typeof code === "number" ? code : -1
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
describe("#669 命令串的退出码必须真实传递", () => {
|
|
39
|
+
it("AC-669-01: POSIX 后缀把内层退出码原样带出来(不是恒为 0)", () => {
|
|
40
|
+
// 用 `sh -c 'exit 7'` 冒充失败的 gate。修复前:文本标记说 7,但进程退出码是 0,
|
|
41
|
+
// 于是 classifyGateRun 看到 exitCode===0 + 有输出 → PASS。假绿。
|
|
42
|
+
const suffix = buildCommand({ subcommand: "principles", dialect: "posix" })
|
|
43
|
+
.replace(/^xp-gate principles/, "sh -c 'exit 7'")
|
|
44
|
+
expect(runInRealShell(suffix, "bash")).toBe(7)
|
|
45
|
+
})
|
|
46
|
+
|
|
47
|
+
it("AC-669-02: POSIX 后缀在成功时仍返回 0(不能为了传失败码而让成功变红)", () => {
|
|
48
|
+
const suffix = buildCommand({ subcommand: "principles", dialect: "posix" })
|
|
49
|
+
.replace(/^xp-gate principles/, "sh -c 'exit 0'")
|
|
50
|
+
expect(runInRealShell(suffix, "bash")).toBe(0)
|
|
51
|
+
})
|
|
52
|
+
|
|
53
|
+
it("AC-669-03: 方言由 executor 决定,而不是 process.platform", () => {
|
|
54
|
+
// Git Bash executor 在 win32 上暴露 bashPath;按平台猜会得到 powershell。
|
|
55
|
+
expect(detectDialectForShell({ bashPath: "C:\\Program Files\\Git\\bin\\bash.exe" }, "win32")).toBe("posix")
|
|
56
|
+
// 显式声明优先于一切推断。
|
|
57
|
+
expect(detectDialectForShell({ shellDialect: "bash" }, "win32")).toBe("posix")
|
|
58
|
+
expect(detectDialectForShell({ dialect: "pwsh" }, "linux")).toBe("powershell")
|
|
59
|
+
// 认不出来时回落到原平台默认 —— 等效旧行为,不会比现在更差。
|
|
60
|
+
expect(detectDialectForShell({}, "win32")).toBe("powershell")
|
|
61
|
+
expect(detectDialectForShell(null, "linux")).toBe("posix")
|
|
62
|
+
// PowerShell executor 带 bashPath 的假想组合:显式声明优先。
|
|
63
|
+
expect(detectDialectForShell({ shellDialect: "powershell", bashPath: "x" }, "win32")).toBe("powershell")
|
|
64
|
+
})
|
|
65
|
+
|
|
66
|
+
it("AC-669-04: classifyGateRun 对退出码与文本标记不一致的 run 报 INDETERMINATE", () => {
|
|
67
|
+
// 即使后缀修好,退出码仍要穿过 shell → harness → 分类器这条脆弱链路。
|
|
68
|
+
// 两者不一致时不能静默采信其中之一。
|
|
69
|
+
const base = {
|
|
70
|
+
timedOut: false,
|
|
71
|
+
timeoutMs: 1000,
|
|
72
|
+
stdout: { text: "some output\n[XP-Gate] exit:7\n", truncated: false },
|
|
73
|
+
stderr: { text: "", truncated: false },
|
|
74
|
+
}
|
|
75
|
+
// exitCode 说成功、标记说失败 → 冲突,不可判定。
|
|
76
|
+
expect(classifyGateRun({ ...base, exitCode: 0 })).toBe("INDETERMINATE")
|
|
77
|
+
// 两者都说失败 → 正常判 GATE FAILED。
|
|
78
|
+
expect(classifyGateRun({ ...base, exitCode: 7 })).toBe("GATE FAILED")
|
|
79
|
+
// 两者都说成功 → PASS。
|
|
80
|
+
const ok = { ...base, stdout: { text: "ok\n[XP-Gate] exit:0\n", truncated: false } }
|
|
81
|
+
expect(classifyGateRun({ ...ok, exitCode: 0 })).toBe("PASS")
|
|
82
|
+
})
|
|
83
|
+
})
|
|
@@ -39,6 +39,19 @@ export function classifyGateRun(result: ShellRunResult): GateVerdict {
|
|
|
39
39
|
|
|
40
40
|
if (result.timedOut) return "INDETERMINATE"
|
|
41
41
|
if (result.stdout.truncated) return "INDETERMINATE"
|
|
42
|
+
|
|
43
|
+
// Cross-check the reported exit code against the marker our own command
|
|
44
|
+
// suffix wrote into stdout (#669).
|
|
45
|
+
//
|
|
46
|
+
// The exit code travels a fragile chain: shell -> harness -> classify. The
|
|
47
|
+
// marker is the same fact recorded twice by independent paths, so a
|
|
48
|
+
// disagreement means one of them was mangled, and a run whose outcome cannot
|
|
49
|
+
// be established must not be reported as a pass. This is the backstop for the
|
|
50
|
+
// case where the exit code is lost outside our control -- the suffix fix makes
|
|
51
|
+
// the two agree, but only an explicit check proves they do.
|
|
52
|
+
const marker = parseExitMarker(stdout)
|
|
53
|
+
if (marker !== null && result.exitCode !== null && marker !== result.exitCode) return "INDETERMINATE"
|
|
54
|
+
|
|
42
55
|
if (result.exitCode === 0) return stdout.length > 0 ? "PASS" : "INDETERMINATE"
|
|
43
56
|
|
|
44
57
|
// Nonzero: a verdict exists only when the run produced something to show.
|
|
@@ -46,6 +59,16 @@ export function classifyGateRun(result: ShellRunResult): GateVerdict {
|
|
|
46
59
|
return stdout.length > 0 || stderr.length > 0 ? "GATE FAILED" : "INDETERMINATE"
|
|
47
60
|
}
|
|
48
61
|
|
|
62
|
+
/** Read the `[XP-Gate] exit:<n>` marker the command suffix appends, if present. */
|
|
63
|
+
function parseExitMarker(stdout: string): number | null {
|
|
64
|
+
const matches = [...stdout.matchAll(/\[XP-Gate\] exit:(-?\d+)/g)]
|
|
65
|
+
if (matches.length === 0) return null
|
|
66
|
+
// Take the LAST marker: a run whose output happens to contain an earlier one
|
|
67
|
+
// must be judged by the status our suffix wrote at the end.
|
|
68
|
+
const value = Number.parseInt(matches[matches.length - 1][1], 10)
|
|
69
|
+
return Number.isNaN(value) ? null : value
|
|
70
|
+
}
|
|
71
|
+
|
|
49
72
|
/** Detailed diagnostics appended after the verdict line, when present. */
|
|
50
73
|
function renderDetails(result: ShellRunResult): string[] {
|
|
51
74
|
const lines: string[] = []
|
package/plugins/dsh/src/index.ts
CHANGED
|
@@ -8,7 +8,7 @@ import type { PostToolDecision, ToolExecution, ToolExecutionResult } from "@deep
|
|
|
8
8
|
import z from "@deepseek-ai/schemastery"
|
|
9
9
|
import type { Context } from "@deepseek-ai/cordis"
|
|
10
10
|
import type { ShellDialect } from "./command.js"
|
|
11
|
-
import { GATE_WHITELIST, buildCommand,
|
|
11
|
+
import { GATE_WHITELIST, buildCommand, detectDialectForShell, resolveTarget } from "./command.js"
|
|
12
12
|
import { runXpGate } from "./gate-runner.js"
|
|
13
13
|
import type { ResolvedDelphiConfig } from "./delphi-config.js"
|
|
14
14
|
import {
|
|
@@ -485,10 +485,13 @@ export const Config = z.object({
|
|
|
485
485
|
|
|
486
486
|
export function apply(ctx: Context, config: unknown = {}): void {
|
|
487
487
|
const cfg = config as Partial<XpGatePluginConfig>
|
|
488
|
-
// DSH runs a POSIX shell off-Windows and PowerShell on Windows; generating the
|
|
489
|
-
// wrong dialect makes every tool fail to parse rather than degrade gracefully.
|
|
490
|
-
const dialect = detectDialect()
|
|
491
488
|
const shell = ctx.shell
|
|
489
|
+
// The EXECUTOR decides the dialect, not the platform (#669). "win32 implies
|
|
490
|
+
// PowerShell" stopped being true once Windows could mount a Git Bash executor:
|
|
491
|
+
// a PowerShell command string handed to bash exits 127 on a line that does not
|
|
492
|
+
// parse, so every gate was reported as failed. Falls back to the platform
|
|
493
|
+
// default when the executor does not reveal itself.
|
|
494
|
+
const dialect = detectDialectForShell(shell)
|
|
492
495
|
// ctx.credentials is optional: outside DSH there is no store, and the resolver
|
|
493
496
|
// then reads the process environment directly. Inside DSH this is what makes
|
|
494
497
|
// delphi-review work on a machine whose key lives in the credential store rather
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
# SKILLS/DELPHI-REVIEW KNOWLEDGE BASE
|
|
2
2
|
|
|
3
3
|
**Generated:** 2026-10-30
|
|
4
|
-
**Commit:**
|
|
4
|
+
**Commit:** aefe7f40
|
|
5
5
|
**Branch:** main
|
|
6
|
-
**Version:** 0.35.
|
|
6
|
+
**Version:** 0.35.4.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:** aefe7f40
|
|
5
5
|
**Branch:** main
|
|
6
|
-
**Version:** 0.35.
|
|
6
|
+
**Version:** 0.35.4.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:** aefe7f40
|
|
5
5
|
**Branch:** main
|
|
6
|
-
**Version:** 0.35.
|
|
6
|
+
**Version:** 0.35.4.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.4",
|
|
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:**
|
|
4
|
+
**Commit:** aefe7f40
|
|
5
5
|
**Branch:** main
|
|
6
|
-
**Version:** 0.35.
|
|
6
|
+
**Version:** 0.35.4.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:** aefe7f40
|
|
5
5
|
**Branch:** main
|
|
6
|
-
**Version:** 0.35.
|
|
6
|
+
**Version:** 0.35.4.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:** aefe7f40
|
|
5
5
|
**Branch:** main
|
|
6
|
-
**Version:** 0.35.
|
|
6
|
+
**Version:** 0.35.4.0
|
|
7
7
|
|
|
8
8
|
## OVERVIEW
|
|
9
9
|
Test-Specification Alignment Engine — two-stage validation ensuring tests accurately reflect requirements and design specs.
|
package/principles/AGENTS.md
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
# PRINCIPLES CHECKER MODULE
|
|
2
2
|
|
|
3
3
|
**Generated:** 2026-10-30
|
|
4
|
-
**Commit:**
|
|
4
|
+
**Commit:** aefe7f40
|
|
5
5
|
**Branch:** main
|
|
6
|
-
**Version:** 0.35.
|
|
6
|
+
**Version:** 0.35.4.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:**
|
|
4
|
+
**Commit:** aefe7f40
|
|
5
5
|
**Branch:** main
|
|
6
|
-
**Version:** 0.35.
|
|
6
|
+
**Version:** 0.35.4.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:** aefe7f40
|
|
5
5
|
**Branch:** main
|
|
6
|
-
**Version:** 0.35.
|
|
6
|
+
**Version:** 0.35.4.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:** aefe7f40
|
|
5
5
|
**Branch:** main
|
|
6
|
-
**Version:** 0.35.
|
|
6
|
+
**Version:** 0.35.4.0
|
|
7
7
|
|
|
8
8
|
## OVERVIEW
|
|
9
9
|
Test-Specification Alignment Engine — two-stage validation ensuring tests accurately reflect requirements and design specs.
|