@boyingliu01/xp-gate 0.31.4 → 0.32.1
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 +65 -16
- package/hooks/gate-13-compiler-strictness.sh +1031 -0
- package/hooks/gate-3.sh +65 -16
- package/hooks/lib/coverage-total.sh +18 -2
- package/hooks/lib/java-test-selection.sh +251 -0
- package/hooks/lib/jscpd-run.sh +101 -0
- package/hooks/lib/validate-code-walkthrough.cjs +186 -29
- package/hooks/pre-commit +259 -57
- 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,95 @@
|
|
|
1
|
+
import { TOOL_ABORTED } from "@deepseek-ai/dsh-tools";
|
|
2
|
+
import { HarnessError } from "@deepseek-ai/dsh-llm";
|
|
3
|
+
/**
|
|
4
|
+
* Classify a completed shell run.
|
|
5
|
+
*
|
|
6
|
+
* The order below is load-bearing and must not be rearranged:
|
|
7
|
+
*
|
|
8
|
+
* 1. timeout / truncation -> INDETERMINATE (a killed or clipped run yields no verdict)
|
|
9
|
+
* 2. exit 0 with no stdout -> INDETERMINATE (the command succeeded but said nothing)
|
|
10
|
+
* 3. exit 0 with stdout -> PASS
|
|
11
|
+
* 4. otherwise -> GATE FAILED (nonzero with output) or INDETERMINATE (silent)
|
|
12
|
+
*
|
|
13
|
+
* Step 1 must precede step 3: a timed-out process can still report exit 0 from
|
|
14
|
+
* the state it held when the deadline killed it, so testing `exitCode === 0`
|
|
15
|
+
* first would render a timeout as a pass. That is the exact defect this project
|
|
16
|
+
* fixed in Gate 8 and Gate 10, and the plugin must not reintroduce it.
|
|
17
|
+
*/
|
|
18
|
+
export function classifyGateRun(result) {
|
|
19
|
+
const stdout = result.stdout.text.trim();
|
|
20
|
+
if (result.timedOut)
|
|
21
|
+
return "INDETERMINATE";
|
|
22
|
+
if (result.stdout.truncated)
|
|
23
|
+
return "INDETERMINATE";
|
|
24
|
+
if (result.exitCode === 0)
|
|
25
|
+
return stdout.length > 0 ? "PASS" : "INDETERMINATE";
|
|
26
|
+
// Nonzero: a verdict exists only when the run produced something to show.
|
|
27
|
+
const stderr = result.stderr.text.trim();
|
|
28
|
+
return stdout.length > 0 || stderr.length > 0 ? "GATE FAILED" : "INDETERMINATE";
|
|
29
|
+
}
|
|
30
|
+
/** Detailed diagnostics appended after the verdict line, when present. */
|
|
31
|
+
function renderDetails(result) {
|
|
32
|
+
const lines = [];
|
|
33
|
+
const stdout = result.stdout.text.trim();
|
|
34
|
+
if (stdout.length > 0)
|
|
35
|
+
lines.push(stdout);
|
|
36
|
+
const stderr = result.stderr.text.trim();
|
|
37
|
+
if (stderr.length > 0)
|
|
38
|
+
lines.push(`[stderr] ${stderr}`);
|
|
39
|
+
if (result.timedOut)
|
|
40
|
+
lines.push(`[timed out after ${result.timeoutMs}ms]`);
|
|
41
|
+
if (result.exitCode !== null && result.exitCode !== 0)
|
|
42
|
+
lines.push(`[exit code: ${result.exitCode}]`);
|
|
43
|
+
if (result.signal !== null)
|
|
44
|
+
lines.push(`[signal: ${result.signal}]`);
|
|
45
|
+
if (result.sandbox?.denied === true) {
|
|
46
|
+
lines.push(`[sandbox: file access denied under ${result.sandbox.mode} mode]`);
|
|
47
|
+
}
|
|
48
|
+
if (result.stdout.truncated) {
|
|
49
|
+
const spill = result.stdout.spillPath !== undefined ? `, full output: ${result.stdout.spillPath}` : "";
|
|
50
|
+
lines.push(`[stdout truncated${spill}]`);
|
|
51
|
+
}
|
|
52
|
+
if (result.stderr.truncated) {
|
|
53
|
+
const spill = result.stderr.spillPath !== undefined ? `, full output: ${result.stderr.spillPath}` : "";
|
|
54
|
+
lines.push(`[stderr truncated${spill}]`);
|
|
55
|
+
}
|
|
56
|
+
return lines;
|
|
57
|
+
}
|
|
58
|
+
/** Install hint appended whenever no verdict could be reached. */
|
|
59
|
+
const INDETERMINATE_HINT = "The gate produced no usable verdict. Check that the xp-gate CLI is installed " +
|
|
60
|
+
"and runnable (npm i -g @boyingliu01/xp-gate), then retry.";
|
|
61
|
+
/**
|
|
62
|
+
* Render a completed shell run as model-facing text.
|
|
63
|
+
*
|
|
64
|
+
* Every run resolves to exactly one of three verdicts. A run that could not
|
|
65
|
+
* establish a verdict says INDETERMINATE and states explicitly that it is not a
|
|
66
|
+
* pass, because an empty output read as a green check is worse than no output.
|
|
67
|
+
*/
|
|
68
|
+
export function renderGateResult(result) {
|
|
69
|
+
const verdict = classifyGateRun(result);
|
|
70
|
+
const lines = renderDetails(result);
|
|
71
|
+
if (verdict === "PASS") {
|
|
72
|
+
return `[XP-Gate] PASS\n${lines.join("\n")}`;
|
|
73
|
+
}
|
|
74
|
+
if (verdict === "GATE FAILED") {
|
|
75
|
+
return `[XP-Gate] GATE FAILED\n${lines.join("\n")}`;
|
|
76
|
+
}
|
|
77
|
+
return [`[XP-Gate] INDETERMINATE — gate produced no output; NOT a pass`, ...lines, INDETERMINATE_HINT].join("\n");
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Run an xp-gate command through the harness shell seam. Nonzero exits, timeouts,
|
|
81
|
+
* and sandbox denials resolve (and render) rather than reject; only cancellation
|
|
82
|
+
* (the caller's AbortSignal fired first) throws an AbortError with code ABORTED.
|
|
83
|
+
*/
|
|
84
|
+
export async function runXpGate(shell, signal, command, workdir, timeoutMs) {
|
|
85
|
+
const spec = shell.resolve({ command, workdir, timeoutMs, signal });
|
|
86
|
+
const execution = await shell.execute(spec);
|
|
87
|
+
const result = await execution.result();
|
|
88
|
+
if (result.aborted) {
|
|
89
|
+
const error = new HarnessError("tool call aborted", TOOL_ABORTED);
|
|
90
|
+
error.name = "AbortError";
|
|
91
|
+
throw error;
|
|
92
|
+
}
|
|
93
|
+
return renderGateResult(result);
|
|
94
|
+
}
|
|
95
|
+
//# sourceMappingURL=gate-runner.js.map
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Minimal git context for evidence.
|
|
3
|
+
*
|
|
4
|
+
* @test REQ-DSH-012 delphi-review can produce Gate MW evidence
|
|
5
|
+
* @intent Read HEAD and the current branch name, which the walkthrough evidence must
|
|
6
|
+
* name exactly, without adding a git library dependency.
|
|
7
|
+
* @covers AC-DSH-012-02
|
|
8
|
+
*
|
|
9
|
+
* Spawned git, not a parsed .git directory: worktrees and detached HEAD have
|
|
10
|
+
* layouts that are easy to misread, and getting either wrong produces evidence the
|
|
11
|
+
* validator rejects at push time with no hint about why.
|
|
12
|
+
*/
|
|
13
|
+
/** The two facts the validator compares against. */
|
|
14
|
+
export interface GitContext {
|
|
15
|
+
readonly head: string;
|
|
16
|
+
readonly branch: string;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Read HEAD and the branch.
|
|
20
|
+
*
|
|
21
|
+
* The validator compares `branch` to the name the push reports; a detached HEAD
|
|
22
|
+
* reports "HEAD", which is what git itself would say, so it is passed through
|
|
23
|
+
* rather than guessed at.
|
|
24
|
+
*/
|
|
25
|
+
export declare function readGitContext(cwd: string): GitContext;
|
|
26
|
+
//# sourceMappingURL=git-context.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"git-context.d.ts","sourceRoot":"","sources":["../src/git-context.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAIH,oDAAoD;AACpD,MAAM,WAAW,UAAU;IACzB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;CACxB;AAED;;;;;;GAMG;AACH,wBAAgB,cAAc,CAAC,GAAG,EAAE,MAAM,GAAG,UAAU,CAStD"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Minimal git context for evidence.
|
|
3
|
+
*
|
|
4
|
+
* @test REQ-DSH-012 delphi-review can produce Gate MW evidence
|
|
5
|
+
* @intent Read HEAD and the current branch name, which the walkthrough evidence must
|
|
6
|
+
* name exactly, without adding a git library dependency.
|
|
7
|
+
* @covers AC-DSH-012-02
|
|
8
|
+
*
|
|
9
|
+
* Spawned git, not a parsed .git directory: worktrees and detached HEAD have
|
|
10
|
+
* layouts that are easy to misread, and getting either wrong produces evidence the
|
|
11
|
+
* validator rejects at push time with no hint about why.
|
|
12
|
+
*/
|
|
13
|
+
import { execFileSync } from "node:child_process";
|
|
14
|
+
/**
|
|
15
|
+
* Read HEAD and the branch.
|
|
16
|
+
*
|
|
17
|
+
* The validator compares `branch` to the name the push reports; a detached HEAD
|
|
18
|
+
* reports "HEAD", which is what git itself would say, so it is passed through
|
|
19
|
+
* rather than guessed at.
|
|
20
|
+
*/
|
|
21
|
+
export function readGitContext(cwd) {
|
|
22
|
+
const run = (args) => execFileSync("git", args, { cwd, encoding: "utf8", stdio: ["ignore", "pipe", "pipe"] }).trim();
|
|
23
|
+
try {
|
|
24
|
+
return { head: run(["rev-parse", "HEAD"]), branch: run(["rev-parse", "--abbrev-ref", "HEAD"]) };
|
|
25
|
+
}
|
|
26
|
+
catch (error) {
|
|
27
|
+
throw new Error(`cannot read git context in ${cwd}: ${error.message}`);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
//# sourceMappingURL=git-context.js.map
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DSH-native hooks: the delphi-review guard and the save-time principles check.
|
|
3
|
+
*
|
|
4
|
+
* @test REQ-DSH-015 sprint-phase writes are gated the way the IDE plugin gates them
|
|
5
|
+
* @intent Port the Claude Code guard and save-check to DSH's own pre/post-execute
|
|
6
|
+
* seams, keeping the degrade rules that made them safe there.
|
|
7
|
+
* @covers AC-DSH-015-01
|
|
8
|
+
*
|
|
9
|
+
* Degrade semantics are copied from plugins/claude-code/bin/delphi-review-guard.sh,
|
|
10
|
+
* which has been in use long enough to have settled them:
|
|
11
|
+
*
|
|
12
|
+
* no .sprint-state -> ALLOW (not a sprint project; gating it would punish every
|
|
13
|
+
* ordinary repo that installed the plugin)
|
|
14
|
+
* state, no verdict -> DENY (sprint is open, review not done)
|
|
15
|
+
* verdict != APPROVED-> DENY (reason names the actual verdict)
|
|
16
|
+
* APPROVED -> ALLOW
|
|
17
|
+
*
|
|
18
|
+
* The guard only ever inspects state. It must not throw: a throw inside pre-execute
|
|
19
|
+
* aborts the tool call, which is a worse failure than letting an edit through.
|
|
20
|
+
*/
|
|
21
|
+
/** Denial the guard returns; shape matches DSH's PreToolDecision deny variant. */
|
|
22
|
+
export interface GuardDenial {
|
|
23
|
+
readonly kind: "deny";
|
|
24
|
+
readonly reason: string;
|
|
25
|
+
}
|
|
26
|
+
/** Does this tool modify files? */
|
|
27
|
+
export declare function isEditTool(name: string): boolean;
|
|
28
|
+
/**
|
|
29
|
+
* Should a save-time check run for this path?
|
|
30
|
+
*
|
|
31
|
+
* Documents and data files are excluded on purpose: they are a large share of the
|
|
32
|
+
* commits in repositories using xp-gate, and the engine has nothing to say about
|
|
33
|
+
* them. Skipping them removes most of the hook's cost.
|
|
34
|
+
*/
|
|
35
|
+
export declare function isCheckablePath(path: string): boolean;
|
|
36
|
+
/**
|
|
37
|
+
* Decide whether an edit may proceed.
|
|
38
|
+
*
|
|
39
|
+
* Returns undefined to mean "allow" so the caller can `if (denial) return denial`.
|
|
40
|
+
*/
|
|
41
|
+
export declare function delphiGuardDecision(root: string, toolName: string): GuardDenial | undefined;
|
|
42
|
+
//# sourceMappingURL=hooks.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hooks.d.ts","sourceRoot":"","sources":["../src/hooks.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAYH,kFAAkF;AAClF,MAAM,WAAW,WAAW;IAC1B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;CACxB;AAsBD,mCAAmC;AACnC,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAEhD;AAED;;;;;;GAMG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAKrD;AAgBD;;;;GAIG;AACH,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,WAAW,GAAG,SAAS,CA8B3F"}
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DSH-native hooks: the delphi-review guard and the save-time principles check.
|
|
3
|
+
*
|
|
4
|
+
* @test REQ-DSH-015 sprint-phase writes are gated the way the IDE plugin gates them
|
|
5
|
+
* @intent Port the Claude Code guard and save-check to DSH's own pre/post-execute
|
|
6
|
+
* seams, keeping the degrade rules that made them safe there.
|
|
7
|
+
* @covers AC-DSH-015-01
|
|
8
|
+
*
|
|
9
|
+
* Degrade semantics are copied from plugins/claude-code/bin/delphi-review-guard.sh,
|
|
10
|
+
* which has been in use long enough to have settled them:
|
|
11
|
+
*
|
|
12
|
+
* no .sprint-state -> ALLOW (not a sprint project; gating it would punish every
|
|
13
|
+
* ordinary repo that installed the plugin)
|
|
14
|
+
* state, no verdict -> DENY (sprint is open, review not done)
|
|
15
|
+
* verdict != APPROVED-> DENY (reason names the actual verdict)
|
|
16
|
+
* APPROVED -> ALLOW
|
|
17
|
+
*
|
|
18
|
+
* The guard only ever inspects state. It must not throw: a throw inside pre-execute
|
|
19
|
+
* aborts the tool call, which is a worse failure than letting an edit through.
|
|
20
|
+
*/
|
|
21
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
22
|
+
import { join } from "node:path";
|
|
23
|
+
/** Tools that modify files. Read-only tools are never gated. */
|
|
24
|
+
const EDIT_TOOLS = new Set(["write", "edit", "str_replace_editor"]);
|
|
25
|
+
/** Where Sprint Flow records the review verdict. */
|
|
26
|
+
const SPRINT_STATE_DIR = ".sprint-state";
|
|
27
|
+
const VERDICT_FILE = "delphi-reviewed.json";
|
|
28
|
+
/** The file extensions the principles engine actually covers. */
|
|
29
|
+
const CHECKABLE_EXTENSIONS = new Set([
|
|
30
|
+
".ts",
|
|
31
|
+
".tsx",
|
|
32
|
+
".js",
|
|
33
|
+
".jsx",
|
|
34
|
+
".py",
|
|
35
|
+
".go",
|
|
36
|
+
".java",
|
|
37
|
+
".kt",
|
|
38
|
+
".c",
|
|
39
|
+
".cpp",
|
|
40
|
+
".h",
|
|
41
|
+
".swift",
|
|
42
|
+
".m",
|
|
43
|
+
".dart",
|
|
44
|
+
".sh",
|
|
45
|
+
".ps1",
|
|
46
|
+
]);
|
|
47
|
+
/** Does this tool modify files? */
|
|
48
|
+
export function isEditTool(name) {
|
|
49
|
+
return EDIT_TOOLS.has(name);
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Should a save-time check run for this path?
|
|
53
|
+
*
|
|
54
|
+
* Documents and data files are excluded on purpose: they are a large share of the
|
|
55
|
+
* commits in repositories using xp-gate, and the engine has nothing to say about
|
|
56
|
+
* them. Skipping them removes most of the hook's cost.
|
|
57
|
+
*/
|
|
58
|
+
export function isCheckablePath(path) {
|
|
59
|
+
const lowered = path.toLowerCase();
|
|
60
|
+
const dot = lowered.lastIndexOf(".");
|
|
61
|
+
if (dot < 0)
|
|
62
|
+
return false;
|
|
63
|
+
return CHECKABLE_EXTENSIONS.has(lowered.slice(dot));
|
|
64
|
+
}
|
|
65
|
+
/** Read the recorded verdict, or a reason it cannot be read. */
|
|
66
|
+
function readVerdict(root) {
|
|
67
|
+
const file = join(root, SPRINT_STATE_DIR, VERDICT_FILE);
|
|
68
|
+
try {
|
|
69
|
+
const parsed = JSON.parse(readFileSync(file, "utf8"));
|
|
70
|
+
const verdict = typeof parsed.verdict === "string" ? parsed.verdict : "";
|
|
71
|
+
return { verdict };
|
|
72
|
+
}
|
|
73
|
+
catch (error) {
|
|
74
|
+
// Corrupt JSON is a real state (a half-written file, a merge conflict). Treat it
|
|
75
|
+
// as "not reviewed" and say why, rather than throwing into the tool pipeline.
|
|
76
|
+
return { unreadable: error.message };
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Decide whether an edit may proceed.
|
|
81
|
+
*
|
|
82
|
+
* Returns undefined to mean "allow" so the caller can `if (denial) return denial`.
|
|
83
|
+
*/
|
|
84
|
+
export function delphiGuardDecision(root, toolName) {
|
|
85
|
+
if (!isEditTool(toolName))
|
|
86
|
+
return undefined;
|
|
87
|
+
if (!existsSync(join(root, SPRINT_STATE_DIR)))
|
|
88
|
+
return undefined;
|
|
89
|
+
const verdictFile = join(root, SPRINT_STATE_DIR, VERDICT_FILE);
|
|
90
|
+
if (!existsSync(verdictFile)) {
|
|
91
|
+
return {
|
|
92
|
+
kind: "deny",
|
|
93
|
+
reason: "delphi-review has not run for this sprint. Complete the Phase 2 delphi-review " +
|
|
94
|
+
"before modifying code, then retry.",
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
const { verdict, unreadable } = readVerdict(root);
|
|
98
|
+
if (unreadable !== undefined) {
|
|
99
|
+
return {
|
|
100
|
+
kind: "deny",
|
|
101
|
+
reason: `${VERDICT_FILE} is not readable as JSON (${unreadable}). Re-run delphi-review.`,
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
if (verdict !== "APPROVED") {
|
|
105
|
+
return {
|
|
106
|
+
kind: "deny",
|
|
107
|
+
reason: `delphi-review verdict is "${verdict || "(missing)"}", not APPROVED. ` +
|
|
108
|
+
"Address the findings and re-run delphi-review, then retry.",
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
return undefined;
|
|
112
|
+
}
|
|
113
|
+
//# sourceMappingURL=hooks.js.map
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import z from "@deepseek-ai/schemastery";
|
|
2
|
+
import type { Context } from "@deepseek-ai/cordis";
|
|
3
|
+
export declare const name = "tool-xp-gate";
|
|
4
|
+
export declare const inject: string[];
|
|
5
|
+
/**
|
|
6
|
+
* Runtime schema for the keys above.
|
|
7
|
+
*
|
|
8
|
+
* Exported as a schema, not just a type: DSH validates against it and surfaces the
|
|
9
|
+
* keys in the profile schema, so a user can discover and set them. A bare interface
|
|
10
|
+
* is invisible to the harness.
|
|
11
|
+
*/
|
|
12
|
+
export declare const Config: z<Schemastery.ObjectS<NoInfer<{
|
|
13
|
+
principlesCheck: z<"auto" | "off", "auto" | "off", "defined">;
|
|
14
|
+
delphiGuard: z<"auto" | "off", "auto" | "off", "defined">;
|
|
15
|
+
}>>, Schemastery.ObjectT<NoInfer<{
|
|
16
|
+
principlesCheck: z<"auto" | "off", "auto" | "off", "defined">;
|
|
17
|
+
delphiGuard: z<"auto" | "off", "auto" | "off", "defined">;
|
|
18
|
+
}>>, "plain">;
|
|
19
|
+
export declare function apply(ctx: Context, config?: unknown): void;
|
|
20
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +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;AAgBlD,eAAO,MAAM,IAAI,iBAAiB,CAAA;AAGlC,eAAO,MAAM,MAAM,UAA+B,CAAA;AA0TlD;;;;;;GAMG;AACH,eAAO,MAAM,MAAM;;;;;;aAajB,CAAA;AAEF,wBAAgB,KAAK,CAAC,GAAG,EAAE,OAAO,EAAE,MAAM,GAAE,OAAY,GAAG,IAAI,CA6B9D"}
|