@boyingliu01/xp-gate 0.27.2 → 0.27.3
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-7.sh +22 -4
- package/adapters/java.sh +68 -6
- package/gate-7.sh +22 -4
- package/hooks/gate-7.sh +22 -4
- package/lib/mattpocock-license-verdict.json +1 -1
- package/mock-policy/AGENTS.md +3 -3
- package/mutation/AGENTS.md +3 -3
- package/package.json +1 -1
- 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/grill-with-docs/SKILL.md +1 -2
- package/plugins/claude-code/skills/grilling/SKILL.md +22 -5
- 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/delphi-config.d.ts +1 -1
- package/plugins/dsh/lib/delphi-config.d.ts.map +1 -1
- package/plugins/dsh/lib/delphi-config.js +47 -19
- package/plugins/dsh/node_modules/.vite/vitest/results.json +1 -0
- package/plugins/dsh/package.json +1 -1
- package/plugins/dsh/skills/delphi-review/AGENTS.md +3 -3
- package/plugins/dsh/skills/grill-with-docs/SKILL.md +1 -2
- package/plugins/dsh/skills/grilling/SKILL.md +22 -5
- 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/delphi-config-global-fallback.test.ts +87 -0
- package/plugins/dsh/src/delphi-config.ts +67 -28
- package/plugins/opencode/package.json +1 -1
- package/plugins/opencode/skills/delphi-review/AGENTS.md +3 -3
- package/plugins/opencode/skills/grill-with-docs/SKILL.md +1 -2
- package/plugins/opencode/skills/grilling/SKILL.md +22 -5
- 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/scripts/delphi-external-review.cjs +26 -0
- package/skills/delphi-review/AGENTS.md +3 -3
- package/skills/grill-with-docs/SKILL.md +1 -2
- package/skills/grilling/SKILL.md +22 -5
- package/skills/sprint-flow/AGENTS.md +3 -3
- package/skills/test-specification-alignment/AGENTS.md +3 -3
package/adapters/gate-7.sh
CHANGED
|
@@ -14,10 +14,28 @@ if [ -z "$IAC_FILES" ]; then
|
|
|
14
14
|
echo "✅ PASSED - No IaC files detected in changes."
|
|
15
15
|
GATE_7_STATUS="PASS"
|
|
16
16
|
else
|
|
17
|
-
# Run IaC adapter
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
17
|
+
# Run IaC adapter. Resolution follows the documented 3-tier convention
|
|
18
|
+
# (adapter-common.sh): global ~/.config/xp-gate/adapters -> project
|
|
19
|
+
# githooks/adapters -> script dir. The old cwd-only test silently SKIPped
|
|
20
|
+
# IaC security in every consumer repo without an in-repo githooks/ dir,
|
|
21
|
+
# even with the adapter installed globally (e2e sweep 2026-10-17; pinned
|
|
22
|
+
# by __tests__/gate-7-adapter-resolution.test.bats).
|
|
23
|
+
IAC_ADAPTER=""
|
|
24
|
+
for _candidate in \
|
|
25
|
+
"$HOME/.config/xp-gate/adapters/iac.sh" \
|
|
26
|
+
"githooks/adapters/iac.sh" \
|
|
27
|
+
"${SCRIPT_DIR:-}/adapters/iac.sh"; do
|
|
28
|
+
if [ -n "$_candidate" ] && [ -f "$_candidate" ]; then
|
|
29
|
+
IAC_ADAPTER="$_candidate"
|
|
30
|
+
break
|
|
31
|
+
fi
|
|
32
|
+
done
|
|
33
|
+
if [ -n "$IAC_ADAPTER" ]; then
|
|
34
|
+
# Audit trail: which tier won (r14 walkthrough MAJ-02 — a silent tier-order
|
|
35
|
+
# regression must be visible in the gate output, not only in test diffs).
|
|
36
|
+
echo " IaC adapter resolved from: $IAC_ADAPTER"
|
|
37
|
+
# shellcheck source=/dev/null
|
|
38
|
+
source "$IAC_ADAPTER"
|
|
21
39
|
|
|
22
40
|
# Run static analysis for IaC files
|
|
23
41
|
IAC_OUTPUT=$(run_static_analysis "$IAC_FILES" 2>&1)
|
package/adapters/java.sh
CHANGED
|
@@ -111,13 +111,23 @@ wc_java_json_violation_count() {
|
|
|
111
111
|
local json_file="$1"
|
|
112
112
|
[ -s "$json_file" ] || return 1
|
|
113
113
|
|
|
114
|
-
#
|
|
115
|
-
#
|
|
116
|
-
|
|
117
|
-
|
|
114
|
+
# Two shapes are accepted, each closed by its own bracket:
|
|
115
|
+
# - bare array `[ ... ]` — the originally pinned contract ([] = clean);
|
|
116
|
+
# - object wrapper `{ ..., "issues": [ ... ] }` — what wc-java-lint 0.4.x
|
|
117
|
+
# actually emits ({summary, issues}). Rejecting it fail-closed every
|
|
118
|
+
# real Java commit on well-formed JSON (e2e sweep 2026-10-17; pinned by
|
|
119
|
+
# __tests__/issue-507-wc-java-object-json.test.bats). A truncated
|
|
120
|
+
# response still fails its closing-bracket test -> BLOCK.
|
|
121
|
+
local head1 tail shape
|
|
122
|
+
head1=$(head -c 1 "$json_file")
|
|
118
123
|
tail=$(tr -d '[:space:]' < "$json_file" | tail -c 1)
|
|
119
|
-
|
|
124
|
+
case "$head1" in
|
|
125
|
+
"[") shape=array; [ "$tail" = "]" ] || return 1 ;;
|
|
126
|
+
"{") shape=object; [ "$tail" = "}" ] || return 1 ;;
|
|
127
|
+
*) return 1 ;;
|
|
128
|
+
esac
|
|
120
129
|
|
|
130
|
+
local structural obj_count file_count rule_count sev_count
|
|
121
131
|
# Strip string VALUE literals (`: "..."` -> `: ""`). Keys must survive
|
|
122
132
|
# (they are the tokens being counted); values must vanish (violation
|
|
123
133
|
# messages routinely contain braces or the words file/rule/severity --
|
|
@@ -131,8 +141,60 @@ wc_java_json_violation_count() {
|
|
|
131
141
|
# variant either fails to strip values (count desync -> BLOCK, annoying but
|
|
132
142
|
# safe) or, worst case, errors to empty counts and disarms the lying-tool
|
|
133
143
|
# BLOCK below (fail-open). Delphi round-2 A-MAJOR-2.
|
|
134
|
-
local structural obj_count file_count rule_count sev_count
|
|
135
144
|
structural=$(sed -E 's/:([[:space:]]*)"([^"\\]|\\.)*"/:\1""/g' "$json_file" 2>/dev/null)
|
|
145
|
+
|
|
146
|
+
if [ "$shape" = "object" ]; then
|
|
147
|
+
# Object wrapper ({summary, issues}). Parse the ISSUES ARRAY exactly with
|
|
148
|
+
# brace-depth tracking (issues are JSON objects, so a record ends exactly
|
|
149
|
+
# where its nesting returns to the array level). For each record: all
|
|
150
|
+
# three keys must be present exactly once. A key outside any record —
|
|
151
|
+
# e.g. split triples in sibling objects, the lying-tool shape of r14
|
|
152
|
+
# CI-02 — makes the per-record key totals disagree with the global
|
|
153
|
+
# stripped counts -> BLOCK. Values were stripped first so braces inside
|
|
154
|
+
# messages cannot skew the depth.
|
|
155
|
+
file_count=$(grep -o '"file"' <<< "$structural" | wc -l | tr -d '[:space:]')
|
|
156
|
+
rule_count=$(grep -o '"rule"' <<< "$structural" | wc -l | tr -d '[:space:]')
|
|
157
|
+
sev_count=$(grep -o '"severity"' <<< "$structural" | wc -l | tr -d '[:space:]')
|
|
158
|
+
[ "$file_count" -eq "$rule_count" ] || return 1
|
|
159
|
+
[ "$file_count" -eq "$sev_count" ] || return 1
|
|
160
|
+
# Everything from the "issues" member onward (the tool emits it last; if
|
|
161
|
+
# a future format moves it, keys before it fall outside record parsing ->
|
|
162
|
+
# sum mismatch -> BLOCK, fail closed). Flatten to one line first: real
|
|
163
|
+
# output is pretty-printed across lines.
|
|
164
|
+
flattened=$(tr '\n' ' ' <<< "$structural")
|
|
165
|
+
tail_from_issues=$(sed -n 's/.*"issues"[[:space:]]*:[[:space:]]*\[/\n/p' <<< "$flattened")
|
|
166
|
+
[ -n "$tail_from_issues" ] || return 1
|
|
167
|
+
records=$(awk '
|
|
168
|
+
{ rest = $0
|
|
169
|
+
depth = 0; rec = ""
|
|
170
|
+
for (i = 1; i <= length(rest); i++) {
|
|
171
|
+
c = substr(rest, i, 1)
|
|
172
|
+
if (depth == 0 && c == "]") break
|
|
173
|
+
if (c == "{") { depth++; if (depth == 1) { rec = ""; continue } }
|
|
174
|
+
if (c == "}") { depth--; if (depth == 0) { print rec; continue } }
|
|
175
|
+
if (depth >= 1) rec = rec c
|
|
176
|
+
}
|
|
177
|
+
}' <<< "$tail_from_issues")
|
|
178
|
+
rec_count=0
|
|
179
|
+
rf=0; rr=0; rs=0
|
|
180
|
+
while IFS= read -r _rec; do
|
|
181
|
+
[ -n "$_rec" ] || continue
|
|
182
|
+
cf=$(grep -o '"file"' <<< "$_rec" | wc -l | tr -d '[:space:]')
|
|
183
|
+
cr=$(grep -o '"rule"' <<< "$_rec" | wc -l | tr -d '[:space:]')
|
|
184
|
+
cs=$(grep -o '"severity"' <<< "$_rec" | wc -l | tr -d '[:space:]')
|
|
185
|
+
[ "$cf" -eq 1 ] || return 1
|
|
186
|
+
[ "$cr" -eq 1 ] || return 1
|
|
187
|
+
[ "$cs" -eq 1 ] || return 1
|
|
188
|
+
rf=$((rf + cf)); rr=$((rr + cr)); rs=$((rs + cs))
|
|
189
|
+
rec_count=$((rec_count + 1))
|
|
190
|
+
done <<< "$records"
|
|
191
|
+
[ "$rf" -eq "$file_count" ] || return 1
|
|
192
|
+
[ "$rr" -eq "$rule_count" ] || return 1
|
|
193
|
+
[ "$rs" -eq "$sev_count" ] || return 1
|
|
194
|
+
echo "$rec_count"
|
|
195
|
+
return 0
|
|
196
|
+
fi
|
|
197
|
+
|
|
136
198
|
obj_count=$(grep -o '{' <<< "$structural" | wc -l | tr -d '[:space:]')
|
|
137
199
|
file_count=$(grep -o '"file"' <<< "$structural" | wc -l | tr -d '[:space:]')
|
|
138
200
|
rule_count=$(grep -o '"rule"' <<< "$structural" | wc -l | tr -d '[:space:]')
|
package/gate-7.sh
CHANGED
|
@@ -14,10 +14,28 @@ if [ -z "$IAC_FILES" ]; then
|
|
|
14
14
|
echo "✅ PASSED - No IaC files detected in changes."
|
|
15
15
|
GATE_7_STATUS="PASS"
|
|
16
16
|
else
|
|
17
|
-
# Run IaC adapter
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
17
|
+
# Run IaC adapter. Resolution follows the documented 3-tier convention
|
|
18
|
+
# (adapter-common.sh): global ~/.config/xp-gate/adapters -> project
|
|
19
|
+
# githooks/adapters -> script dir. The old cwd-only test silently SKIPped
|
|
20
|
+
# IaC security in every consumer repo without an in-repo githooks/ dir,
|
|
21
|
+
# even with the adapter installed globally (e2e sweep 2026-10-17; pinned
|
|
22
|
+
# by __tests__/gate-7-adapter-resolution.test.bats).
|
|
23
|
+
IAC_ADAPTER=""
|
|
24
|
+
for _candidate in \
|
|
25
|
+
"$HOME/.config/xp-gate/adapters/iac.sh" \
|
|
26
|
+
"githooks/adapters/iac.sh" \
|
|
27
|
+
"${SCRIPT_DIR:-}/adapters/iac.sh"; do
|
|
28
|
+
if [ -n "$_candidate" ] && [ -f "$_candidate" ]; then
|
|
29
|
+
IAC_ADAPTER="$_candidate"
|
|
30
|
+
break
|
|
31
|
+
fi
|
|
32
|
+
done
|
|
33
|
+
if [ -n "$IAC_ADAPTER" ]; then
|
|
34
|
+
# Audit trail: which tier won (r14 walkthrough MAJ-02 — a silent tier-order
|
|
35
|
+
# regression must be visible in the gate output, not only in test diffs).
|
|
36
|
+
echo " IaC adapter resolved from: $IAC_ADAPTER"
|
|
37
|
+
# shellcheck source=/dev/null
|
|
38
|
+
source "$IAC_ADAPTER"
|
|
21
39
|
|
|
22
40
|
# Run static analysis for IaC files
|
|
23
41
|
IAC_OUTPUT=$(run_static_analysis "$IAC_FILES" 2>&1)
|
package/hooks/gate-7.sh
CHANGED
|
@@ -14,10 +14,28 @@ if [ -z "$IAC_FILES" ]; then
|
|
|
14
14
|
echo "✅ PASSED - No IaC files detected in changes."
|
|
15
15
|
GATE_7_STATUS="PASS"
|
|
16
16
|
else
|
|
17
|
-
# Run IaC adapter
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
17
|
+
# Run IaC adapter. Resolution follows the documented 3-tier convention
|
|
18
|
+
# (adapter-common.sh): global ~/.config/xp-gate/adapters -> project
|
|
19
|
+
# githooks/adapters -> script dir. The old cwd-only test silently SKIPped
|
|
20
|
+
# IaC security in every consumer repo without an in-repo githooks/ dir,
|
|
21
|
+
# even with the adapter installed globally (e2e sweep 2026-10-17; pinned
|
|
22
|
+
# by __tests__/gate-7-adapter-resolution.test.bats).
|
|
23
|
+
IAC_ADAPTER=""
|
|
24
|
+
for _candidate in \
|
|
25
|
+
"$HOME/.config/xp-gate/adapters/iac.sh" \
|
|
26
|
+
"githooks/adapters/iac.sh" \
|
|
27
|
+
"${SCRIPT_DIR:-}/adapters/iac.sh"; do
|
|
28
|
+
if [ -n "$_candidate" ] && [ -f "$_candidate" ]; then
|
|
29
|
+
IAC_ADAPTER="$_candidate"
|
|
30
|
+
break
|
|
31
|
+
fi
|
|
32
|
+
done
|
|
33
|
+
if [ -n "$IAC_ADAPTER" ]; then
|
|
34
|
+
# Audit trail: which tier won (r14 walkthrough MAJ-02 — a silent tier-order
|
|
35
|
+
# regression must be visible in the gate output, not only in test diffs).
|
|
36
|
+
echo " IaC adapter resolved from: $IAC_ADAPTER"
|
|
37
|
+
# shellcheck source=/dev/null
|
|
38
|
+
source "$IAC_ADAPTER"
|
|
21
39
|
|
|
22
40
|
# Run static analysis for IaC files
|
|
23
41
|
IAC_OUTPUT=$(run_static_analysis "$IAC_FILES" 2>&1)
|
package/mock-policy/AGENTS.md
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
# SRC/MOCK-POLICY KNOWLEDGE BASE
|
|
2
2
|
|
|
3
|
-
**Generated:** 2026-10-
|
|
4
|
-
**Commit:**
|
|
3
|
+
**Generated:** 2026-10-17
|
|
4
|
+
**Commit:** b207e882
|
|
5
5
|
**Branch:** main
|
|
6
|
-
**Version:** 0.27.
|
|
6
|
+
**Version:** 0.27.3.0
|
|
7
7
|
|
|
8
8
|
## OVERVIEW
|
|
9
9
|
Mock layering policy enforcement — Gate M3 of pre-push hook. Ensures integration tests use real implementations for internal dependencies, mock external dependencies, and annotate pending mocks with removal plans. Combines project scope scanning, mock decision engine, and per-file validation into a single pipeline.
|
package/mutation/AGENTS.md
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
# SRC/MUTATION KNOWLEDGE BASE
|
|
2
2
|
|
|
3
|
-
**Generated:** 2026-10-
|
|
4
|
-
**Commit:**
|
|
3
|
+
**Generated:** 2026-10-17
|
|
4
|
+
**Commit:** b207e882
|
|
5
5
|
**Branch:** main
|
|
6
|
-
**Version:** 0.27.
|
|
6
|
+
**Version:** 0.27.3.0
|
|
7
7
|
|
|
8
8
|
## OVERVIEW
|
|
9
9
|
**Gate M** (incremental mutation testing) + **Gate M2** helpers (test-layer detection used by `src/mock-policy/`). Pre-push quality gate. TypeScript-only; uses Stryker.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "xp-gate",
|
|
3
|
-
"version": "0.27.
|
|
3
|
+
"version": "0.27.3",
|
|
4
4
|
"displayName": "XP-Gate",
|
|
5
5
|
"description": "Extreme Programming quality gates + AI workflow skills for Claude Code. Includes 12 pre-commit gates (Gate 0-11), 8 pre-push gates, Sprint Flow (6 phases), and Delphi multi-expert review (>=90% consensus).",
|
|
6
6
|
"author": {
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
# SKILLS/DELPHI-REVIEW KNOWLEDGE BASE
|
|
2
2
|
|
|
3
|
-
**Generated:** 2026-10-
|
|
4
|
-
**Commit:**
|
|
3
|
+
**Generated:** 2026-10-17
|
|
4
|
+
**Commit:** b207e882
|
|
5
5
|
**Branch:** main
|
|
6
|
-
**Version:** 0.27.
|
|
6
|
+
**Version:** 0.27.3.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,8 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: grill-with-docs
|
|
3
|
-
version: 1.0.0
|
|
4
3
|
description: A relentless interview to sharpen a plan or design, which also creates docs (ADR's and glossary) as we go.
|
|
5
4
|
disable-model-invocation: true
|
|
6
5
|
---
|
|
7
6
|
|
|
8
|
-
|
|
7
|
+
Call the Skill tool twice, for "grilling" and "domain-modeling".
|
|
@@ -1,13 +1,30 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: grilling
|
|
3
|
-
version: 1.0.0
|
|
4
3
|
description: Grill the user relentlessly about a plan, decision, or idea. Use when the user wants to stress-test their thinking, or uses any 'grill' trigger phrases.
|
|
5
4
|
---
|
|
6
5
|
|
|
7
|
-
Interview
|
|
6
|
+
Interview the user relentlessly until you reach a shared understanding. Map this as a **design tree**: every decision branches into the decisions that hang off it.
|
|
8
7
|
|
|
9
|
-
|
|
8
|
+
Work the tree in **rounds**. The **frontier** is every decision whose prerequisites are already settled: the questions you can ask _now_ without guessing at answers you haven't heard yet. Ask the whole frontier in one round: number each question and give your recommended answer. Then wait for the user's answers before the next round.
|
|
10
9
|
|
|
11
|
-
|
|
10
|
+
Format a round like so:
|
|
12
11
|
|
|
13
|
-
|
|
12
|
+
```
|
|
13
|
+
❓ **Q1** - **<question title>**: <question body, might be multiple paragraphs, including multiple choices>
|
|
14
|
+
|
|
15
|
+
➡️ <your recommended answer>
|
|
16
|
+
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
❓ **Q2** - **<question title>**: <question body, might be multiple paragraphs, including multiple choices>
|
|
20
|
+
|
|
21
|
+
➡️ <your recommended answer>
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
Word each question so "yes" accepts your recommended answer.
|
|
25
|
+
|
|
26
|
+
Each round the user answers reshapes the tree: settled decisions push the frontier outward and unblock questions that depended on them. Recompute the frontier and ask the next round. A question whose answer depends on another question still open in this round belongs to a _later_ round, not this one.
|
|
27
|
+
|
|
28
|
+
Finding _facts_ is your job, never the user's. When a frontier question needs a fact from the environment (filesystem, tools, etc.), dispatch a sub-agent to find it; don't ask the user for anything you could look up yourself. Don't block on it: a running exploration is an unsettled prerequisite, so only the questions downstream of it wait for the sub-agent to report; ask the rest of the frontier now. The _decisions_ are the user's: put each to them and wait.
|
|
29
|
+
|
|
30
|
+
The session is done when the frontier is empty: every branch of the design tree visited, nothing left silently assumed. Do not act on it until the user confirms you have reached a shared understanding.
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
# SKILLS/SPRINT-FLOW KNOWLEDGE BASE
|
|
2
2
|
|
|
3
|
-
**Generated:** 2026-10-
|
|
4
|
-
**Commit:**
|
|
3
|
+
**Generated:** 2026-10-17
|
|
4
|
+
**Commit:** b207e882
|
|
5
5
|
**Branch:** main
|
|
6
|
-
**Version:** 0.27.
|
|
6
|
+
**Version:** 0.27.3.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-17
|
|
4
|
+
**Commit:** b207e882
|
|
5
5
|
**Branch:** main
|
|
6
|
-
**Version:** 0.27.
|
|
6
|
+
**Version:** 0.27.3.0
|
|
7
7
|
|
|
8
8
|
## OVERVIEW
|
|
9
9
|
Test-Specification Alignment Engine — two-stage validation ensuring tests accurately reflect requirements and design specs.
|
|
@@ -68,7 +68,7 @@ export declare function expandEnv(value: string, env?: NodeJS.ProcessEnv): strin
|
|
|
68
68
|
*/
|
|
69
69
|
export declare function resolveConfig(cfg: RawDelphiConfig, env?: NodeJS.ProcessEnv): ResolvedDelphiConfig;
|
|
70
70
|
/** Read and resolve `.delphi-config.json` from a project root. */
|
|
71
|
-
export declare function loadDelphiConfig(cwd: string, env?: NodeJS.ProcessEnv): ResolvedDelphiConfig;
|
|
71
|
+
export declare function loadDelphiConfig(cwd: string, env?: NodeJS.ProcessEnv, globalPath?: string): ResolvedDelphiConfig;
|
|
72
72
|
/** Build the transport options a round needs from a resolved config. */
|
|
73
73
|
export declare function toDelphiOptions(cfg: ResolvedDelphiConfig): DelphiOptions;
|
|
74
74
|
export {};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"delphi-config.d.ts","sourceRoot":"","sources":["../src/delphi-config.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;
|
|
1
|
+
{"version":3,"file":"delphi-config.d.ts","sourceRoot":"","sources":["../src/delphi-config.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAKH,OAAO,KAAK,EAAE,UAAU,EAAE,aAAa,EAAE,MAAM,aAAa,CAAA;AAG5D,wDAAwD;AACxD,UAAU,SAAS;IACjB,EAAE,CAAC,EAAE,MAAM,CAAA;IACX,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,eAAe,CAAC,EAAE,MAAM,CAAA;IACxB,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,uEAAuE;IACvE,QAAQ,CAAC,EAAE,MAAM,CAAA;CAClB;AAED,UAAU,UAAU;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,SAAS,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE;QAAE,QAAQ,CAAC,EAAE,MAAM,CAAC;QAAC,OAAO,CAAC,EAAE,MAAM,CAAC;QAAC,OAAO,CAAC,EAAE,MAAM,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,CAAA;IACtG;;;OAGG;IACH,OAAO,CAAC,EAAE,SAAS,EAAE,GAAG,MAAM,CAAC,MAAM,EAAE,SAAS,CAAC,CAAA;IACjD,SAAS,CAAC,EAAE;QAAE,iBAAiB,CAAC,EAAE,MAAM,CAAC;QAAC,iBAAiB,CAAC,EAAE,MAAM,CAAA;KAAE,CAAA;CACvE;AAED,MAAM,WAAW,eAAgB,SAAQ,UAAU;IACjD,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,UAAU,CAAC,CAAA;CACtC;AAED,MAAM,WAAW,oBAAoB;IACnC,QAAQ,CAAC,KAAK,EAAE,UAAU,EAAE,CAAA;IAC5B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;IACxB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;IACvB,QAAQ,CAAC,gBAAgB,EAAE,MAAM,CAAA;IACjC,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAA;IAC1B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAA;CAC1B;AAED,8EAA8E;AAC9E,qBAAa,gBAAiB,SAAQ,KAAK;CAAG;AAE9C;;;GAGG;AACH,wBAAgB,SAAS,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,GAAE,MAAM,CAAC,UAAwB,GAAG,MAAM,CAIrF;AA0ED;;;;GAIG;AACH,wBAAgB,aAAa,CAC3B,GAAG,EAAE,eAAe,EACpB,GAAG,GAAE,MAAM,CAAC,UAAwB,GACnC,oBAAoB,CAuBtB;AAsCD,kEAAkE;AAClE,wBAAgB,gBAAgB,CAC9B,GAAG,EAAE,MAAM,EACX,GAAG,GAAE,MAAM,CAAC,UAAwB,EACpC,UAAU,GAAE,MAAqE,GAChF,oBAAoB,CAkCtB;AAED,wEAAwE;AACxE,wBAAgB,eAAe,CAAC,GAAG,EAAE,oBAAoB,GAAG,aAAa,CAQxE"}
|
|
@@ -11,7 +11,8 @@
|
|
|
11
11
|
* @covers AC-DSH-016-02
|
|
12
12
|
*/
|
|
13
13
|
import { readFileSync } from "node:fs";
|
|
14
|
-
import {
|
|
14
|
+
import { homedir } from "node:os";
|
|
15
|
+
import { join, resolve } from "node:path";
|
|
15
16
|
import { validateSeats } from "./delphi.js";
|
|
16
17
|
/** Raised when config on disk cannot be turned into a usable review setup. */
|
|
17
18
|
export class DelphiSetupError extends Error {
|
|
@@ -54,23 +55,14 @@ function normalizeExperts(experts) {
|
|
|
54
55
|
// Object form: the KEY is the role, which is how xp-gate writes it.
|
|
55
56
|
return Object.entries(experts).map(([role, raw]) => ({ raw, role }));
|
|
56
57
|
}
|
|
57
|
-
/**
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
* the most common way a Delphi run silently degrades.
|
|
61
|
-
*/
|
|
62
|
-
export function resolveConfig(cfg, env = process.env) {
|
|
63
|
-
const profile = selectProfile(cfg);
|
|
64
|
-
const normalized = normalizeExperts(profile.experts);
|
|
65
|
-
// An expert may name its own provider; otherwise there must be a profile-wide
|
|
66
|
-
// default. Resolve base_url/api_key per provider so mixed setups work.
|
|
67
|
-
const providerNames = new Set(normalized.map((e) => e.raw.provider ?? profile.provider).filter((p) => !!p));
|
|
68
|
-
if (providerNames.size === 0) {
|
|
58
|
+
/** Resolve every referenced provider's base_url + api_key, failing loudly. */
|
|
59
|
+
function resolveProviders(profile, needed, env) {
|
|
60
|
+
if (needed.size === 0) {
|
|
69
61
|
throw new DelphiSetupError('No provider configured. Set "provider" on the profile or on each expert.');
|
|
70
62
|
}
|
|
71
63
|
const providerTable = profile.providers ?? {};
|
|
72
|
-
const
|
|
73
|
-
for (const name of
|
|
64
|
+
const resolved = new Map();
|
|
65
|
+
for (const name of needed) {
|
|
74
66
|
const entry = providerTable[name];
|
|
75
67
|
if (!entry) {
|
|
76
68
|
throw new DelphiSetupError(`Provider "${name}" is not defined under "providers". ` +
|
|
@@ -89,8 +81,22 @@ export function resolveConfig(cfg, env = process.env) {
|
|
|
89
81
|
? `API key variable ${varName} is not set in the environment.`
|
|
90
82
|
: `Provider "${name}" has no api_key.`);
|
|
91
83
|
}
|
|
92
|
-
|
|
84
|
+
resolved.set(name, { baseUrl, apiKey });
|
|
93
85
|
}
|
|
86
|
+
return resolved;
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Turn raw config into a ready-to-run review setup.
|
|
90
|
+
* Fails loudly and specifically: a missing key or an unexpanded placeholder is
|
|
91
|
+
* the most common way a Delphi run silently degrades.
|
|
92
|
+
*/
|
|
93
|
+
export function resolveConfig(cfg, env = process.env) {
|
|
94
|
+
const profile = selectProfile(cfg);
|
|
95
|
+
const normalized = normalizeExperts(profile.experts);
|
|
96
|
+
// An expert may name its own provider; otherwise there must be a profile-wide
|
|
97
|
+
// default. Resolve base_url/api_key per provider so mixed setups work.
|
|
98
|
+
const providerNames = new Set(normalized.map((e) => e.raw.provider ?? profile.provider).filter((p) => !!p));
|
|
99
|
+
const resolvedProviders = resolveProviders(profile, providerNames, env);
|
|
94
100
|
const seats = normalized.map((e, i) => seatFromRaw(e.raw, i, e.role));
|
|
95
101
|
// Surface an unexpanded placeholder as a config error, not a provider 404.
|
|
96
102
|
for (const s of seats) {
|
|
@@ -99,6 +105,10 @@ export function resolveConfig(cfg, env = process.env) {
|
|
|
99
105
|
}
|
|
100
106
|
}
|
|
101
107
|
validateSeats(seats);
|
|
108
|
+
return buildResult(profile, normalized, resolvedProviders, seats, cfg);
|
|
109
|
+
}
|
|
110
|
+
/** Assemble the final result after seats and providers validated. */
|
|
111
|
+
function buildResult(profile, normalized, resolvedProviders, seats, cfg) {
|
|
102
112
|
// All seats share one gateway in the supported setup; require agreement so a
|
|
103
113
|
// mixed list cannot silently send one seat's key to another host.
|
|
104
114
|
const chosen = resolvedProviders.get(normalized[0].raw.provider ?? profile.provider ?? "");
|
|
@@ -124,14 +134,32 @@ export function resolveConfig(cfg, env = process.env) {
|
|
|
124
134
|
};
|
|
125
135
|
}
|
|
126
136
|
/** Read and resolve `.delphi-config.json` from a project root. */
|
|
127
|
-
export function loadDelphiConfig(cwd, env = process.env) {
|
|
137
|
+
export function loadDelphiConfig(cwd, env = process.env, globalPath = join(homedir(), ".config", "xp-gate", ".delphi-config.json")) {
|
|
138
|
+
// Global fallback (AC-DSH-016-03): the machine-wide config at
|
|
139
|
+
// ~/.config/xp-gate/.delphi-config.json is the single source of truth for
|
|
140
|
+
// every project. Without it, each repo needed its own copy — and reading a
|
|
141
|
+
// stale or differently-priced config from another project's directory caused
|
|
142
|
+
// real money loss (2026-10-03: an expensive overseas model burned the daily
|
|
143
|
+
// quota). A project-local config still wins, so per-project overrides work.
|
|
128
144
|
const path = resolve(cwd, ".delphi-config.json");
|
|
129
145
|
let raw;
|
|
130
146
|
try {
|
|
131
147
|
raw = readFileSync(path, "utf8");
|
|
132
148
|
}
|
|
133
|
-
catch {
|
|
134
|
-
|
|
149
|
+
catch (projectError) {
|
|
150
|
+
// Fall back ONLY when the project file is genuinely absent (ENOENT); any
|
|
151
|
+
// other read error must surface rather than silently route this run to
|
|
152
|
+
// another config. Same semantics as delphi-external-review.cjs, which
|
|
153
|
+
// gates the fallback on existsSync (walkthrough R8 MA-01).
|
|
154
|
+
if (projectError?.code !== "ENOENT") {
|
|
155
|
+
throw new DelphiSetupError(`Cannot read ${path}: ${projectError.message}`);
|
|
156
|
+
}
|
|
157
|
+
try {
|
|
158
|
+
raw = readFileSync(globalPath, "utf8");
|
|
159
|
+
}
|
|
160
|
+
catch {
|
|
161
|
+
throw new DelphiSetupError(`No .delphi-config.json found at ${path} or the global location ${globalPath}.`);
|
|
162
|
+
}
|
|
135
163
|
}
|
|
136
164
|
let parsed;
|
|
137
165
|
try {
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":"1.6.1","results":[[":src/delphi-config-global-fallback.test.ts",{"duration":13,"failed":false}]]}
|
package/plugins/dsh/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@boyingliu01/dsh-plugin-xp-gate",
|
|
3
|
-
"version": "0.27.
|
|
3
|
+
"version": "0.27.3",
|
|
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
|
-
**Generated:** 2026-10-
|
|
4
|
-
**Commit:**
|
|
3
|
+
**Generated:** 2026-10-17
|
|
4
|
+
**Commit:** b207e882
|
|
5
5
|
**Branch:** main
|
|
6
|
-
**Version:** 0.27.
|
|
6
|
+
**Version:** 0.27.3.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,8 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: grill-with-docs
|
|
3
|
-
version: 1.0.0
|
|
4
3
|
description: A relentless interview to sharpen a plan or design, which also creates docs (ADR's and glossary) as we go.
|
|
5
4
|
disable-model-invocation: true
|
|
6
5
|
---
|
|
7
6
|
|
|
8
|
-
|
|
7
|
+
Call the Skill tool twice, for "grilling" and "domain-modeling".
|
|
@@ -1,13 +1,30 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: grilling
|
|
3
|
-
version: 1.0.0
|
|
4
3
|
description: Grill the user relentlessly about a plan, decision, or idea. Use when the user wants to stress-test their thinking, or uses any 'grill' trigger phrases.
|
|
5
4
|
---
|
|
6
5
|
|
|
7
|
-
Interview
|
|
6
|
+
Interview the user relentlessly until you reach a shared understanding. Map this as a **design tree**: every decision branches into the decisions that hang off it.
|
|
8
7
|
|
|
9
|
-
|
|
8
|
+
Work the tree in **rounds**. The **frontier** is every decision whose prerequisites are already settled: the questions you can ask _now_ without guessing at answers you haven't heard yet. Ask the whole frontier in one round: number each question and give your recommended answer. Then wait for the user's answers before the next round.
|
|
10
9
|
|
|
11
|
-
|
|
10
|
+
Format a round like so:
|
|
12
11
|
|
|
13
|
-
|
|
12
|
+
```
|
|
13
|
+
❓ **Q1** - **<question title>**: <question body, might be multiple paragraphs, including multiple choices>
|
|
14
|
+
|
|
15
|
+
➡️ <your recommended answer>
|
|
16
|
+
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
❓ **Q2** - **<question title>**: <question body, might be multiple paragraphs, including multiple choices>
|
|
20
|
+
|
|
21
|
+
➡️ <your recommended answer>
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
Word each question so "yes" accepts your recommended answer.
|
|
25
|
+
|
|
26
|
+
Each round the user answers reshapes the tree: settled decisions push the frontier outward and unblock questions that depended on them. Recompute the frontier and ask the next round. A question whose answer depends on another question still open in this round belongs to a _later_ round, not this one.
|
|
27
|
+
|
|
28
|
+
Finding _facts_ is your job, never the user's. When a frontier question needs a fact from the environment (filesystem, tools, etc.), dispatch a sub-agent to find it; don't ask the user for anything you could look up yourself. Don't block on it: a running exploration is an unsettled prerequisite, so only the questions downstream of it wait for the sub-agent to report; ask the rest of the frontier now. The _decisions_ are the user's: put each to them and wait.
|
|
29
|
+
|
|
30
|
+
The session is done when the frontier is empty: every branch of the design tree visited, nothing left silently assumed. Do not act on it until the user confirms you have reached a shared understanding.
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
# SKILLS/SPRINT-FLOW KNOWLEDGE BASE
|
|
2
2
|
|
|
3
|
-
**Generated:** 2026-10-
|
|
4
|
-
**Commit:**
|
|
3
|
+
**Generated:** 2026-10-17
|
|
4
|
+
**Commit:** b207e882
|
|
5
5
|
**Branch:** main
|
|
6
|
-
**Version:** 0.27.
|
|
6
|
+
**Version:** 0.27.3.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-17
|
|
4
|
+
**Commit:** b207e882
|
|
5
5
|
**Branch:** main
|
|
6
|
-
**Version:** 0.27.
|
|
6
|
+
**Version:** 0.27.3.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,87 @@
|
|
|
1
|
+
// TDD-RED probe: loadDelphiConfig must fall back to the global config.
|
|
2
|
+
import { describe, it, expect } from "vitest"
|
|
3
|
+
import { mkdtempSync, writeFileSync, rmSync } from "node:fs"
|
|
4
|
+
import { tmpdir } from "node:os"
|
|
5
|
+
import { join } from "node:path"
|
|
6
|
+
import { DelphiSetupError, loadDelphiConfig } from "./delphi-config.js"
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* @test REQ-DSH-016
|
|
10
|
+
* @intent 项目目录没有 .delphi-config.json 时,回退读取全局
|
|
11
|
+
* ~/.config/xp-gate/.delphi-config.json(全局唯一事实源,防止
|
|
12
|
+
* 跨项目漂移);两处都没有才报错。
|
|
13
|
+
* @covers AC-DSH-016-02
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
const globalStyle = JSON.stringify({
|
|
17
|
+
active_profile: "default",
|
|
18
|
+
profiles: {
|
|
19
|
+
default: {
|
|
20
|
+
provider: "whalecloud",
|
|
21
|
+
providers: { whalecloud: { base_url: "https://lab.example.com/gpt-proxy", api_key: "k" } },
|
|
22
|
+
experts: {
|
|
23
|
+
architecture: { model: "local-deepseek-v4-flash" },
|
|
24
|
+
technical: { model: "local-qwen3.8-flash" },
|
|
25
|
+
feasibility: { model: "local-qwen3.8-27b" },
|
|
26
|
+
},
|
|
27
|
+
},
|
|
28
|
+
},
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
describe("loadDelphiConfig global fallback", () => {
|
|
32
|
+
it("falls back to the global config when the project has none", () => {
|
|
33
|
+
const home = mkdtempSync(join(tmpdir(), "xpg-home-"))
|
|
34
|
+
const project = mkdtempSync(join(tmpdir(), "xpg-proj-"))
|
|
35
|
+
try {
|
|
36
|
+
writeFileSync(join(home, ".delphi-config.json"), globalStyle)
|
|
37
|
+
const cfg = loadDelphiConfig(project, {}, join(home, ".delphi-config.json"))
|
|
38
|
+
expect(cfg.seats.map((s) => s.model).sort()).toEqual(
|
|
39
|
+
["local-deepseek-v4-flash", "local-qwen3.8-27b", "local-qwen3.8-flash"].sort()
|
|
40
|
+
)
|
|
41
|
+
} finally {
|
|
42
|
+
rmSync(home, { recursive: true, force: true })
|
|
43
|
+
rmSync(project, { recursive: true, force: true })
|
|
44
|
+
}
|
|
45
|
+
})
|
|
46
|
+
|
|
47
|
+
it("project config still wins over the global one", () => {
|
|
48
|
+
const home = mkdtempSync(join(tmpdir(), "xpg-home-"))
|
|
49
|
+
const project = mkdtempSync(join(tmpdir(), "xpg-proj-"))
|
|
50
|
+
try {
|
|
51
|
+
writeFileSync(join(home, ".delphi-config.json"), globalStyle)
|
|
52
|
+
writeFileSync(
|
|
53
|
+
join(project, ".delphi-config.json"),
|
|
54
|
+
JSON.stringify({
|
|
55
|
+
...JSON.parse(globalStyle),
|
|
56
|
+
profiles: {
|
|
57
|
+
default: {
|
|
58
|
+
provider: "whalecloud",
|
|
59
|
+
providers: { whalecloud: { base_url: "https://lab.example.com/gpt-proxy", api_key: "k" } },
|
|
60
|
+
experts: {
|
|
61
|
+
architecture: { model: "proj-a" },
|
|
62
|
+
technical: { model: "proj-b" },
|
|
63
|
+
feasibility: { model: "proj-c" },
|
|
64
|
+
},
|
|
65
|
+
},
|
|
66
|
+
},
|
|
67
|
+
})
|
|
68
|
+
)
|
|
69
|
+
const cfg = loadDelphiConfig(project, {}, join(home, ".delphi-config.json"))
|
|
70
|
+
expect(cfg.seats.map((s) => s.model).sort()).toEqual(["proj-a", "proj-b", "proj-c"])
|
|
71
|
+
} finally {
|
|
72
|
+
rmSync(home, { recursive: true, force: true })
|
|
73
|
+
rmSync(project, { recursive: true, force: true })
|
|
74
|
+
}
|
|
75
|
+
})
|
|
76
|
+
|
|
77
|
+
it("errors naming BOTH missing locations when neither exists", () => {
|
|
78
|
+
const project = mkdtempSync(join(tmpdir(), "xpg-proj-"))
|
|
79
|
+
try {
|
|
80
|
+
expect(() => loadDelphiConfig(project, {}, join(project, "absent", ".delphi-config.json"))).toThrow(
|
|
81
|
+
DelphiSetupError
|
|
82
|
+
)
|
|
83
|
+
} finally {
|
|
84
|
+
rmSync(project, { recursive: true, force: true })
|
|
85
|
+
}
|
|
86
|
+
})
|
|
87
|
+
})
|
|
@@ -12,7 +12,8 @@
|
|
|
12
12
|
*/
|
|
13
13
|
|
|
14
14
|
import { readFileSync } from "node:fs"
|
|
15
|
-
import {
|
|
15
|
+
import { homedir } from "node:os"
|
|
16
|
+
import { join, resolve } from "node:path"
|
|
16
17
|
import type { ExpertSeat, DelphiOptions } from "./delphi.js"
|
|
17
18
|
import { validateSeats } from "./delphi.js"
|
|
18
19
|
|
|
@@ -99,32 +100,18 @@ function normalizeExperts(experts: RawProfile["experts"]): Array<{ raw: RawExper
|
|
|
99
100
|
return Object.entries(experts).map(([role, raw]) => ({ raw, role }))
|
|
100
101
|
}
|
|
101
102
|
|
|
102
|
-
/**
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
): ResolvedDelphiConfig {
|
|
111
|
-
const profile = selectProfile(cfg)
|
|
112
|
-
const normalized = normalizeExperts(profile.experts)
|
|
113
|
-
|
|
114
|
-
// An expert may name its own provider; otherwise there must be a profile-wide
|
|
115
|
-
// default. Resolve base_url/api_key per provider so mixed setups work.
|
|
116
|
-
const providerNames = new Set(
|
|
117
|
-
normalized.map((e) => e.raw.provider ?? profile.provider).filter((p): p is string => !!p),
|
|
118
|
-
)
|
|
119
|
-
if (providerNames.size === 0) {
|
|
120
|
-
throw new DelphiSetupError(
|
|
121
|
-
'No provider configured. Set "provider" on the profile or on each expert.',
|
|
122
|
-
)
|
|
103
|
+
/** Resolve every referenced provider's base_url + api_key, failing loudly. */
|
|
104
|
+
function resolveProviders(
|
|
105
|
+
profile: RawProfile,
|
|
106
|
+
needed: Set<string>,
|
|
107
|
+
env: NodeJS.ProcessEnv,
|
|
108
|
+
): Map<string, { baseUrl: string; apiKey: string }> {
|
|
109
|
+
if (needed.size === 0) {
|
|
110
|
+
throw new DelphiSetupError('No provider configured. Set "provider" on the profile or on each expert.')
|
|
123
111
|
}
|
|
124
|
-
|
|
125
112
|
const providerTable = profile.providers ?? {}
|
|
126
|
-
const
|
|
127
|
-
for (const name of
|
|
113
|
+
const resolved = new Map<string, { baseUrl: string; apiKey: string }>()
|
|
114
|
+
for (const name of needed) {
|
|
128
115
|
const entry = providerTable[name]
|
|
129
116
|
if (!entry) {
|
|
130
117
|
throw new DelphiSetupError(
|
|
@@ -148,8 +135,29 @@ export function resolveConfig(
|
|
|
148
135
|
: `Provider "${name}" has no api_key.`,
|
|
149
136
|
)
|
|
150
137
|
}
|
|
151
|
-
|
|
138
|
+
resolved.set(name, { baseUrl, apiKey })
|
|
152
139
|
}
|
|
140
|
+
return resolved
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Turn raw config into a ready-to-run review setup.
|
|
145
|
+
* Fails loudly and specifically: a missing key or an unexpanded placeholder is
|
|
146
|
+
* the most common way a Delphi run silently degrades.
|
|
147
|
+
*/
|
|
148
|
+
export function resolveConfig(
|
|
149
|
+
cfg: RawDelphiConfig,
|
|
150
|
+
env: NodeJS.ProcessEnv = process.env,
|
|
151
|
+
): ResolvedDelphiConfig {
|
|
152
|
+
const profile = selectProfile(cfg)
|
|
153
|
+
const normalized = normalizeExperts(profile.experts)
|
|
154
|
+
|
|
155
|
+
// An expert may name its own provider; otherwise there must be a profile-wide
|
|
156
|
+
// default. Resolve base_url/api_key per provider so mixed setups work.
|
|
157
|
+
const providerNames = new Set(
|
|
158
|
+
normalized.map((e) => e.raw.provider ?? profile.provider).filter((p): p is string => !!p),
|
|
159
|
+
)
|
|
160
|
+
const resolvedProviders = resolveProviders(profile, providerNames, env)
|
|
153
161
|
|
|
154
162
|
const seats = normalized.map((e, i) => seatFromRaw(e.raw, i, e.role))
|
|
155
163
|
// Surface an unexpanded placeholder as a config error, not a provider 404.
|
|
@@ -162,6 +170,17 @@ export function resolveConfig(
|
|
|
162
170
|
}
|
|
163
171
|
validateSeats(seats)
|
|
164
172
|
|
|
173
|
+
return buildResult(profile, normalized, resolvedProviders, seats, cfg)
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/** Assemble the final result after seats and providers validated. */
|
|
177
|
+
function buildResult(
|
|
178
|
+
profile: RawProfile,
|
|
179
|
+
normalized: Array<{ raw: RawExpert; role: string }>,
|
|
180
|
+
resolvedProviders: Map<string, { baseUrl: string; apiKey: string }>,
|
|
181
|
+
seats: ExpertSeat[],
|
|
182
|
+
cfg: RawDelphiConfig,
|
|
183
|
+
): ResolvedDelphiConfig {
|
|
165
184
|
// All seats share one gateway in the supported setup; require agreement so a
|
|
166
185
|
// mixed list cannot silently send one seat's key to another host.
|
|
167
186
|
const chosen = resolvedProviders.get(normalized[0].raw.provider ?? profile.provider ?? "")
|
|
@@ -194,13 +213,33 @@ export function resolveConfig(
|
|
|
194
213
|
export function loadDelphiConfig(
|
|
195
214
|
cwd: string,
|
|
196
215
|
env: NodeJS.ProcessEnv = process.env,
|
|
216
|
+
globalPath: string = join(homedir(), ".config", "xp-gate", ".delphi-config.json"),
|
|
197
217
|
): ResolvedDelphiConfig {
|
|
218
|
+
// Global fallback (AC-DSH-016-03): the machine-wide config at
|
|
219
|
+
// ~/.config/xp-gate/.delphi-config.json is the single source of truth for
|
|
220
|
+
// every project. Without it, each repo needed its own copy — and reading a
|
|
221
|
+
// stale or differently-priced config from another project's directory caused
|
|
222
|
+
// real money loss (2026-10-03: an expensive overseas model burned the daily
|
|
223
|
+
// quota). A project-local config still wins, so per-project overrides work.
|
|
198
224
|
const path = resolve(cwd, ".delphi-config.json")
|
|
199
225
|
let raw: string
|
|
200
226
|
try {
|
|
201
227
|
raw = readFileSync(path, "utf8")
|
|
202
|
-
} catch {
|
|
203
|
-
|
|
228
|
+
} catch (projectError) {
|
|
229
|
+
// Fall back ONLY when the project file is genuinely absent (ENOENT); any
|
|
230
|
+
// other read error must surface rather than silently route this run to
|
|
231
|
+
// another config. Same semantics as delphi-external-review.cjs, which
|
|
232
|
+
// gates the fallback on existsSync (walkthrough R8 MA-01).
|
|
233
|
+
if ((projectError as NodeJS.ErrnoException)?.code !== "ENOENT") {
|
|
234
|
+
throw new DelphiSetupError(`Cannot read ${path}: ${(projectError as Error).message}`)
|
|
235
|
+
}
|
|
236
|
+
try {
|
|
237
|
+
raw = readFileSync(globalPath, "utf8")
|
|
238
|
+
} catch {
|
|
239
|
+
throw new DelphiSetupError(
|
|
240
|
+
`No .delphi-config.json found at ${path} or the global location ${globalPath}.`
|
|
241
|
+
)
|
|
242
|
+
}
|
|
204
243
|
}
|
|
205
244
|
let parsed: RawDelphiConfig
|
|
206
245
|
try {
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
# SKILLS/DELPHI-REVIEW KNOWLEDGE BASE
|
|
2
2
|
|
|
3
|
-
**Generated:** 2026-10-
|
|
4
|
-
**Commit:**
|
|
3
|
+
**Generated:** 2026-10-17
|
|
4
|
+
**Commit:** b207e882
|
|
5
5
|
**Branch:** main
|
|
6
|
-
**Version:** 0.27.
|
|
6
|
+
**Version:** 0.27.3.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,8 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: grill-with-docs
|
|
3
|
-
version: 1.0.0
|
|
4
3
|
description: A relentless interview to sharpen a plan or design, which also creates docs (ADR's and glossary) as we go.
|
|
5
4
|
disable-model-invocation: true
|
|
6
5
|
---
|
|
7
6
|
|
|
8
|
-
|
|
7
|
+
Call the Skill tool twice, for "grilling" and "domain-modeling".
|
|
@@ -1,13 +1,30 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: grilling
|
|
3
|
-
version: 1.0.0
|
|
4
3
|
description: Grill the user relentlessly about a plan, decision, or idea. Use when the user wants to stress-test their thinking, or uses any 'grill' trigger phrases.
|
|
5
4
|
---
|
|
6
5
|
|
|
7
|
-
Interview
|
|
6
|
+
Interview the user relentlessly until you reach a shared understanding. Map this as a **design tree**: every decision branches into the decisions that hang off it.
|
|
8
7
|
|
|
9
|
-
|
|
8
|
+
Work the tree in **rounds**. The **frontier** is every decision whose prerequisites are already settled: the questions you can ask _now_ without guessing at answers you haven't heard yet. Ask the whole frontier in one round: number each question and give your recommended answer. Then wait for the user's answers before the next round.
|
|
10
9
|
|
|
11
|
-
|
|
10
|
+
Format a round like so:
|
|
12
11
|
|
|
13
|
-
|
|
12
|
+
```
|
|
13
|
+
❓ **Q1** - **<question title>**: <question body, might be multiple paragraphs, including multiple choices>
|
|
14
|
+
|
|
15
|
+
➡️ <your recommended answer>
|
|
16
|
+
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
❓ **Q2** - **<question title>**: <question body, might be multiple paragraphs, including multiple choices>
|
|
20
|
+
|
|
21
|
+
➡️ <your recommended answer>
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
Word each question so "yes" accepts your recommended answer.
|
|
25
|
+
|
|
26
|
+
Each round the user answers reshapes the tree: settled decisions push the frontier outward and unblock questions that depended on them. Recompute the frontier and ask the next round. A question whose answer depends on another question still open in this round belongs to a _later_ round, not this one.
|
|
27
|
+
|
|
28
|
+
Finding _facts_ is your job, never the user's. When a frontier question needs a fact from the environment (filesystem, tools, etc.), dispatch a sub-agent to find it; don't ask the user for anything you could look up yourself. Don't block on it: a running exploration is an unsettled prerequisite, so only the questions downstream of it wait for the sub-agent to report; ask the rest of the frontier now. The _decisions_ are the user's: put each to them and wait.
|
|
29
|
+
|
|
30
|
+
The session is done when the frontier is empty: every branch of the design tree visited, nothing left silently assumed. Do not act on it until the user confirms you have reached a shared understanding.
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
# SKILLS/SPRINT-FLOW KNOWLEDGE BASE
|
|
2
2
|
|
|
3
|
-
**Generated:** 2026-10-
|
|
4
|
-
**Commit:**
|
|
3
|
+
**Generated:** 2026-10-17
|
|
4
|
+
**Commit:** b207e882
|
|
5
5
|
**Branch:** main
|
|
6
|
-
**Version:** 0.27.
|
|
6
|
+
**Version:** 0.27.3.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-17
|
|
4
|
+
**Commit:** b207e882
|
|
5
5
|
**Branch:** main
|
|
6
|
-
**Version:** 0.27.
|
|
6
|
+
**Version:** 0.27.3.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.27.
|
|
3
|
+
"version": "0.27.3",
|
|
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
|
-
**Generated:** 2026-10-
|
|
4
|
-
**Commit:**
|
|
3
|
+
**Generated:** 2026-10-17
|
|
4
|
+
**Commit:** b207e882
|
|
5
5
|
**Branch:** main
|
|
6
|
-
**Version:** 0.27.
|
|
6
|
+
**Version:** 0.27.3.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-17
|
|
4
|
+
**Commit:** b207e882
|
|
5
5
|
**Branch:** main
|
|
6
|
-
**Version:** 0.27.
|
|
6
|
+
**Version:** 0.27.3.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-17
|
|
4
|
+
**Commit:** b207e882
|
|
5
5
|
**Branch:** main
|
|
6
|
-
**Version:** 0.27.
|
|
6
|
+
**Version:** 0.27.3.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
|
-
**Generated:** 2026-10-
|
|
4
|
-
**Commit:**
|
|
3
|
+
**Generated:** 2026-10-17
|
|
4
|
+
**Commit:** b207e882
|
|
5
5
|
**Branch:** main
|
|
6
|
-
**Version:** 0.27.
|
|
6
|
+
**Version:** 0.27.3.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.
|
|
@@ -112,6 +112,32 @@ function validateProviders(providers, profileName) {
|
|
|
112
112
|
}
|
|
113
113
|
|
|
114
114
|
function readConfig(configPath, profileOverride) {
|
|
115
|
+
// Global fallback: a relative --config that this cwd does not have resolves
|
|
116
|
+
// to the machine-wide ~/.config/xp-gate/.delphi-config.json. This is what
|
|
117
|
+
// makes `--config .delphi-config.json` work from ANY project without each
|
|
118
|
+
// repo carrying its own (drifting, differently-priced) copy — reading a
|
|
119
|
+
// foreign project's config used to route calls to expensive models
|
|
120
|
+
// (2026-10-03 quota incident). An existing local file always wins.
|
|
121
|
+
// Fallback fires ONLY when the project file is genuinely absent: statSync
|
|
122
|
+
// distinguishes ENOENT from EACCES/EISDIR, and any other error aborts here
|
|
123
|
+
// rather than silently routing this run to another config — matching the
|
|
124
|
+
// TS loader's ENOENT-only semantics (walkthrough R10 FC-01).
|
|
125
|
+
const missingHere = (() => {
|
|
126
|
+
try { fs.statSync(configPath); return false; } catch (e) { return e.code === 'ENOENT'; }
|
|
127
|
+
})();
|
|
128
|
+
if (missingHere && !path.isAbsolute(configPath)) {
|
|
129
|
+
// r16 MA-01 disposition: XDG_CONFIG_HOME is an accepted override for the
|
|
130
|
+
// global config location. The CLI lib (shared-paths/doctor) intentionally
|
|
131
|
+
// pins ~/.config/xp-gate — overriding only this loader would make the two
|
|
132
|
+
// disagree. On Linux, $HOME/.config IS the XDG default, so behaviour there
|
|
133
|
+
// is identical; the env var only matters for non-default setups.
|
|
134
|
+
const configHome = process.env.XDG_CONFIG_HOME || path.join(os.homedir(), '.config');
|
|
135
|
+
const globalPath = path.join(configHome, 'xp-gate', '.delphi-config.json');
|
|
136
|
+
if (fs.existsSync(globalPath)) {
|
|
137
|
+
console.error(`[delphi-review] ${configPath} not found here — using global config ${globalPath}`);
|
|
138
|
+
configPath = globalPath;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
115
141
|
if (!fs.existsSync(configPath)) {
|
|
116
142
|
console.error(`[delphi-review] ERROR: Config file not found: ${configPath}`);
|
|
117
143
|
console.error('[delphi-review] Copy .delphi-config.json.example to .delphi-config.json and fill in your API keys.');
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
# SKILLS/DELPHI-REVIEW KNOWLEDGE BASE
|
|
2
2
|
|
|
3
|
-
**Generated:** 2026-10-
|
|
4
|
-
**Commit:**
|
|
3
|
+
**Generated:** 2026-10-17
|
|
4
|
+
**Commit:** b207e882
|
|
5
5
|
**Branch:** main
|
|
6
|
-
**Version:** 0.27.
|
|
6
|
+
**Version:** 0.27.3.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,8 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: grill-with-docs
|
|
3
|
-
version: 1.0.0
|
|
4
3
|
description: A relentless interview to sharpen a plan or design, which also creates docs (ADR's and glossary) as we go.
|
|
5
4
|
disable-model-invocation: true
|
|
6
5
|
---
|
|
7
6
|
|
|
8
|
-
|
|
7
|
+
Call the Skill tool twice, for "grilling" and "domain-modeling".
|
package/skills/grilling/SKILL.md
CHANGED
|
@@ -1,13 +1,30 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: grilling
|
|
3
|
-
version: 1.0.0
|
|
4
3
|
description: Grill the user relentlessly about a plan, decision, or idea. Use when the user wants to stress-test their thinking, or uses any 'grill' trigger phrases.
|
|
5
4
|
---
|
|
6
5
|
|
|
7
|
-
Interview
|
|
6
|
+
Interview the user relentlessly until you reach a shared understanding. Map this as a **design tree**: every decision branches into the decisions that hang off it.
|
|
8
7
|
|
|
9
|
-
|
|
8
|
+
Work the tree in **rounds**. The **frontier** is every decision whose prerequisites are already settled: the questions you can ask _now_ without guessing at answers you haven't heard yet. Ask the whole frontier in one round: number each question and give your recommended answer. Then wait for the user's answers before the next round.
|
|
10
9
|
|
|
11
|
-
|
|
10
|
+
Format a round like so:
|
|
12
11
|
|
|
13
|
-
|
|
12
|
+
```
|
|
13
|
+
❓ **Q1** - **<question title>**: <question body, might be multiple paragraphs, including multiple choices>
|
|
14
|
+
|
|
15
|
+
➡️ <your recommended answer>
|
|
16
|
+
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
❓ **Q2** - **<question title>**: <question body, might be multiple paragraphs, including multiple choices>
|
|
20
|
+
|
|
21
|
+
➡️ <your recommended answer>
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
Word each question so "yes" accepts your recommended answer.
|
|
25
|
+
|
|
26
|
+
Each round the user answers reshapes the tree: settled decisions push the frontier outward and unblock questions that depended on them. Recompute the frontier and ask the next round. A question whose answer depends on another question still open in this round belongs to a _later_ round, not this one.
|
|
27
|
+
|
|
28
|
+
Finding _facts_ is your job, never the user's. When a frontier question needs a fact from the environment (filesystem, tools, etc.), dispatch a sub-agent to find it; don't ask the user for anything you could look up yourself. Don't block on it: a running exploration is an unsettled prerequisite, so only the questions downstream of it wait for the sub-agent to report; ask the rest of the frontier now. The _decisions_ are the user's: put each to them and wait.
|
|
29
|
+
|
|
30
|
+
The session is done when the frontier is empty: every branch of the design tree visited, nothing left silently assumed. Do not act on it until the user confirms you have reached a shared understanding.
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
# SKILLS/SPRINT-FLOW KNOWLEDGE BASE
|
|
2
2
|
|
|
3
|
-
**Generated:** 2026-10-
|
|
4
|
-
**Commit:**
|
|
3
|
+
**Generated:** 2026-10-17
|
|
4
|
+
**Commit:** b207e882
|
|
5
5
|
**Branch:** main
|
|
6
|
-
**Version:** 0.27.
|
|
6
|
+
**Version:** 0.27.3.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-17
|
|
4
|
+
**Commit:** b207e882
|
|
5
5
|
**Branch:** main
|
|
6
|
-
**Version:** 0.27.
|
|
6
|
+
**Version:** 0.27.3.0
|
|
7
7
|
|
|
8
8
|
## OVERVIEW
|
|
9
9
|
Test-Specification Alignment Engine — two-stage validation ensuring tests accurately reflect requirements and design specs.
|