@boyingliu01/xp-gate 0.34.1 → 0.35.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/bin/xp-gate.js +8 -0
- package/gate-3.sh +46 -2
- package/gate-4.sh +20 -3
- package/hooks/gate-3.sh +46 -2
- package/hooks/gate-4.sh +20 -3
- package/hooks/lib/ccn-ratchet.sh +109 -0
- package/hooks/pre-commit +92 -0
- package/lib/__tests__/doctor-timeout-partial.test.js +79 -0
- package/lib/__tests__/rework.test.js +127 -0
- package/lib/doctor.js +60 -3
- package/lib/gate-audit.ts +8 -0
- package/lib/mattpocock-license-verdict.json +1 -1
- package/lib/rework.js +262 -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/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/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/scripts/__tests__/sync-package-content-isolation.test.js +73 -0
- package/scripts/__tests__/sync-package-content.test.js +109 -2
- package/skills/delphi-review/AGENTS.md +2 -2
- package/skills/sprint-flow/AGENTS.md +2 -2
- package/skills/test-specification-alignment/AGENTS.md +2 -2
package/lib/doctor.js
CHANGED
|
@@ -80,6 +80,38 @@ const EXEC_TIMEOUT_MS = 1500;
|
|
|
80
80
|
*/
|
|
81
81
|
const GLOBAL_DIAGNOSIS_TIMEOUT_MS = 10000;
|
|
82
82
|
|
|
83
|
+
/**
|
|
84
|
+
* Merge a timed-out partial diagnosis into one honest verdict (#629).
|
|
85
|
+
*
|
|
86
|
+
* `doctor`'s global timeout (#348) exists so the command never hangs, but the
|
|
87
|
+
* original handler threw away the entire `diagnoseAsync()` result and substituted
|
|
88
|
+
* a single placeholder check. Against an identical healthy install that made the
|
|
89
|
+
* SAME command print "All checks passed" or "Timed out after 10s" depending on
|
|
90
|
+
* machine load, and a timed-out run reported 0 installed skills — doctor giving
|
|
91
|
+
* LESS information the LONGER it runs is the opposite of a diagnostic.
|
|
92
|
+
*
|
|
93
|
+
* Keeps every completed check, adds ONE Diagnosis notice naming what did not
|
|
94
|
+
* report, and never lets a partial run read as clean (#495).
|
|
95
|
+
*
|
|
96
|
+
* @param {{checks?: Array, issues?: number}} partial checks finished before the deadline
|
|
97
|
+
* @param {string[]} pending names of surfaces that did not report in time
|
|
98
|
+
*/
|
|
99
|
+
function mergeTimedOutDiagnosis(partial, pending) {
|
|
100
|
+
const completed = (partial && Array.isArray(partial.checks)) ? partial.checks : [];
|
|
101
|
+
const inherited = (partial && Number.isFinite(partial.issues)) ? partial.issues : 0;
|
|
102
|
+
const pendingNames = Array.isArray(pending) ? pending.filter(Boolean) : [];
|
|
103
|
+
const detail = pendingNames.length > 0
|
|
104
|
+
? `Timed out after ${GLOBAL_DIAGNOSIS_TIMEOUT_MS / 1000}s — did not report in time: ${pendingNames.join(', ')}`
|
|
105
|
+
: `Timed out after ${GLOBAL_DIAGNOSIS_TIMEOUT_MS / 1000}s — some checks incomplete`;
|
|
106
|
+
return {
|
|
107
|
+
checks: [...completed, { name: 'Diagnosis', status: 'WARN', detail }],
|
|
108
|
+
// A partial run is never clean, and a real issue count is never reset.
|
|
109
|
+
issues: Math.max(inherited, 1),
|
|
110
|
+
timedOut: true,
|
|
111
|
+
completedCheckCount: completed.length,
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
|
|
83
115
|
/**
|
|
84
116
|
* Fixed attribution text for the WorkBuddy-desktop sandbox restriction (#525).
|
|
85
117
|
*
|
|
@@ -1605,9 +1637,17 @@ async function checkAdapters(checks, mode, gitDirPromise) {
|
|
|
1605
1637
|
* D) Env + CLI tools — fully independent, run IN PARALLEL
|
|
1606
1638
|
* E) TUI + Skills — independent, run IN PARALLEL
|
|
1607
1639
|
*/
|
|
1608
|
-
async function diagnoseAsync(refreshEnv = false) {
|
|
1640
|
+
async function diagnoseAsync(refreshEnv = false, progress = null) {
|
|
1641
|
+
// #629: `progress` is a live view of the checks completed so far. The global
|
|
1642
|
+
// timeout used to discard everything diagnoseAsync had already produced; with a
|
|
1643
|
+
// shared sink, a deadline can report "these finished, those did not" instead of
|
|
1644
|
+
// replacing the whole diagnosis with a placeholder.
|
|
1609
1645
|
const checks = [];
|
|
1610
1646
|
let issues = 0;
|
|
1647
|
+
if (progress) {
|
|
1648
|
+
progress.checks = checks;
|
|
1649
|
+
progress.getIssues = () => issues;
|
|
1650
|
+
}
|
|
1611
1651
|
|
|
1612
1652
|
// Spawn capability probe FIRST (sync, no config dependency) — #525: it must
|
|
1613
1653
|
// report even when the config early-exits below, and its WARN (environmental,
|
|
@@ -2869,10 +2909,26 @@ async function doctor(args) {
|
|
|
2869
2909
|
|
|
2870
2910
|
// Global timeout wrapper — ensures doctor completes within GLOBAL_DIAGNOSIS_TIMEOUT_MS
|
|
2871
2911
|
// even if network/subprocess checks stack up (fix #348: 43s on Windows).
|
|
2872
|
-
|
|
2912
|
+
// #629: give diagnoseAsync a live sink so a deadline can keep what finished.
|
|
2913
|
+
// Without it, `Promise.race` discarded the entire diagnosis -- so the SAME
|
|
2914
|
+
// healthy install reported different results run to run, and a timed-out run
|
|
2915
|
+
// lost the skills section entirely (scrapers read 0/12 installed skills).
|
|
2916
|
+
const progress = { checks: [], getIssues: () => 0 };
|
|
2917
|
+
const diagnosisPromise = diagnoseAsync(refreshEnv, progress);
|
|
2873
2918
|
let diagnosisTimer = null;
|
|
2874
2919
|
const timeoutPromise = new Promise((resolve) => {
|
|
2875
|
-
diagnosisTimer = setTimeout(() =>
|
|
2920
|
+
diagnosisTimer = setTimeout(() => {
|
|
2921
|
+
const completed = Array.isArray(progress.checks) ? progress.checks : [];
|
|
2922
|
+
const reported = new Set(completed.map((c) => c && c.name));
|
|
2923
|
+
// Name the surfaces that did NOT report, so the reader knows exactly which
|
|
2924
|
+
// conclusions are missing rather than just "something timed out".
|
|
2925
|
+
const pending = ['Hooks', 'Adapters', 'Skills', 'Gate env', 'OpenCode TUI']
|
|
2926
|
+
.filter((name) => !reported.has(name));
|
|
2927
|
+
resolve(mergeTimedOutDiagnosis(
|
|
2928
|
+
{ checks: completed, issues: progress.getIssues() },
|
|
2929
|
+
pending,
|
|
2930
|
+
));
|
|
2931
|
+
}, GLOBAL_DIAGNOSIS_TIMEOUT_MS);
|
|
2876
2932
|
});
|
|
2877
2933
|
const { checks, issues: diagnosedIssues, timedOut } = await Promise.race([
|
|
2878
2934
|
diagnosisPromise.then(r => ({ ...r, timedOut: false })),
|
|
@@ -3349,6 +3405,7 @@ module.exports = {
|
|
|
3349
3405
|
readTuiJson,
|
|
3350
3406
|
formatDoctorJson,
|
|
3351
3407
|
diagnoseHookDrift,
|
|
3408
|
+
mergeTimedOutDiagnosis,
|
|
3352
3409
|
classifyHooksDir,
|
|
3353
3410
|
syncGlobalHooksFromRepo,
|
|
3354
3411
|
diagnoseModuleDrift,
|
package/lib/gate-audit.ts
CHANGED
|
@@ -41,6 +41,12 @@ export interface GateAuditEntry {
|
|
|
41
41
|
duration_anomaly?: boolean; // Issue #370: set when duration_ms > max_duration_ms
|
|
42
42
|
duration_reason?: 'poisoned_start_ms' | 'clock_skew'; // REQ-B: why duration_ms is null
|
|
43
43
|
detail?: string; // #507: optional gate-specific payload (e.g. effective CCN threshold)
|
|
44
|
+
/**
|
|
45
|
+
* #651: WHY the gate blocked. Without this the journal can only answer "how
|
|
46
|
+
* many failed", never "which rule fails most" — and the second question is the
|
|
47
|
+
* one that drives fixes. Only meaningful when passed=false.
|
|
48
|
+
*/
|
|
49
|
+
failure_reason?: string;
|
|
44
50
|
}
|
|
45
51
|
|
|
46
52
|
// ── Public API ───────────────────────────────────────────────────────────────
|
|
@@ -415,6 +421,8 @@ function runCli(): void {
|
|
|
415
421
|
...(durationReason ? { duration_reason: durationReason as 'poisoned_start_ms' | 'clock_skew' } : {}),
|
|
416
422
|
// #507: only set when provided (omit field otherwise)
|
|
417
423
|
...(opts['detail'] ? { detail: opts['detail'] } : {}),
|
|
424
|
+
// #651: only set when provided, so successful records stay byte-identical.
|
|
425
|
+
...(opts['failure-reason'] ? { failure_reason: String(opts['failure-reason']).slice(0, 300) } : {}),
|
|
418
426
|
};
|
|
419
427
|
// Pre-commit (trigger=commit) is staging for a commit that does not exist
|
|
420
428
|
// yet: park it in the pending sidecar so post-commit can stamp the real
|
package/lib/rework.js
ADDED
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `xp-gate rework` — what the gate journal says about rework (#651).
|
|
3
|
+
*
|
|
4
|
+
* `xp-gate retro` already answers "how much did we ship". This answers the
|
|
5
|
+
* narrower and more actionable question: how often does a round of work clear
|
|
6
|
+
* the gates on the first attempt, and which rule stops it most.
|
|
7
|
+
*
|
|
8
|
+
* The reason this needs a dedicated command rather than a `retro` line: before
|
|
9
|
+
* #651 the journal contained NO failure records at all, so a naive rework rate
|
|
10
|
+
* computed from it returned 100% every time. A metric that reads "perfect" when
|
|
11
|
+
* it is actually unmeasured is worse than no metric -- it manufactures
|
|
12
|
+
* confidence. So `measurable: false` is a first-class outcome here.
|
|
13
|
+
*
|
|
14
|
+
* No runtime dependencies: this ships inside the zero-install package.
|
|
15
|
+
*/
|
|
16
|
+
'use strict';
|
|
17
|
+
|
|
18
|
+
const fs = require('node:fs');
|
|
19
|
+
const path = require('node:path');
|
|
20
|
+
|
|
21
|
+
const AUDIT_DIR = '.xp-gate';
|
|
22
|
+
const AUDIT_FILE = 'audit.jsonl';
|
|
23
|
+
const PENDING_FILE = 'audit.pending.jsonl';
|
|
24
|
+
|
|
25
|
+
/** Bucket name for failures the journal cannot attribute to a rule. */
|
|
26
|
+
const UNATTRIBUTED = 'unattributed';
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Read every journal record under `repoRoot`, oldest first.
|
|
30
|
+
*
|
|
31
|
+
* Malformed lines are skipped rather than thrown on: the journal is append-only
|
|
32
|
+
* and a single truncated write must not take the whole report down.
|
|
33
|
+
*
|
|
34
|
+
* @param {string} [repoRoot]
|
|
35
|
+
* @returns {Array<object>}
|
|
36
|
+
*/
|
|
37
|
+
function readJournal(repoRoot = process.cwd()) {
|
|
38
|
+
const dir = path.join(repoRoot, AUDIT_DIR);
|
|
39
|
+
const entries = [];
|
|
40
|
+
for (const file of [AUDIT_FILE, PENDING_FILE]) {
|
|
41
|
+
const full = path.join(dir, file);
|
|
42
|
+
let raw;
|
|
43
|
+
try {
|
|
44
|
+
raw = fs.readFileSync(full, 'utf8');
|
|
45
|
+
} catch {
|
|
46
|
+
continue;
|
|
47
|
+
}
|
|
48
|
+
for (const line of raw.split('\n')) {
|
|
49
|
+
if (!line.trim()) continue;
|
|
50
|
+
try {
|
|
51
|
+
const parsed = JSON.parse(line);
|
|
52
|
+
if (parsed && typeof parsed === 'object') entries.push(parsed);
|
|
53
|
+
} catch {
|
|
54
|
+
/* truncated/garbled line -- skip, do not fail the report */
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
return entries;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/** Median of a numeric array (0 for an empty array). */
|
|
62
|
+
function median(values) {
|
|
63
|
+
if (values.length === 0) return 0;
|
|
64
|
+
const sorted = [...values].sort((a, b) => a - b);
|
|
65
|
+
const mid = Math.floor(sorted.length / 2);
|
|
66
|
+
return sorted.length % 2 === 0 ? (sorted[mid - 1] + sorted[mid]) / 2 : sorted[mid];
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/** Mean of a numeric array (0 for an empty array). */
|
|
70
|
+
function mean(values) {
|
|
71
|
+
if (values.length === 0) return 0;
|
|
72
|
+
return values.reduce((a, b) => a + b, 0) / values.length;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Group a flat record list into rework rounds.
|
|
77
|
+
*
|
|
78
|
+
* The journal is a flat stream of gate executions with no round identifier, so a
|
|
79
|
+
* round is reconstructed the only way the data allows: a run of FAIL records
|
|
80
|
+
* terminated by the next PASS. That is exactly "how many attempts before this
|
|
81
|
+
* landed", which is the definition rework needs. A trailing run of failures with
|
|
82
|
+
* no resolving pass is a round that has not landed yet, and is counted as
|
|
83
|
+
* `unresolved` rather than being dropped.
|
|
84
|
+
*
|
|
85
|
+
* @param {Array<object>} entries
|
|
86
|
+
*/
|
|
87
|
+
function groupRounds(entries) {
|
|
88
|
+
const rounds = [];
|
|
89
|
+
let current = 0;
|
|
90
|
+
for (const entry of entries) {
|
|
91
|
+
if (entry.passed) {
|
|
92
|
+
if (current > 0) rounds.push(current);
|
|
93
|
+
current = 0;
|
|
94
|
+
continue;
|
|
95
|
+
}
|
|
96
|
+
current += 1;
|
|
97
|
+
}
|
|
98
|
+
return { resolved: rounds, unresolved: current };
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Aggregate the journal into rework metrics.
|
|
103
|
+
*
|
|
104
|
+
* @param {Array<object>} entries journal records
|
|
105
|
+
* @returns {object} see AC-670-59..62
|
|
106
|
+
*/
|
|
107
|
+
function computeReworkStats(entries) {
|
|
108
|
+
const list = Array.isArray(entries) ? entries : [];
|
|
109
|
+
const totals = list.length;
|
|
110
|
+
const failures = list.filter((e) => e.passed === false).length;
|
|
111
|
+
const passes = totals - failures;
|
|
112
|
+
|
|
113
|
+
// ── Failures by reason ────────────────────────────────────────────────────
|
|
114
|
+
// Unlabelled failures are bucketed, never dropped: dropping them would lower
|
|
115
|
+
// the failure count and push the first-pass rate UP, which is the optimistic
|
|
116
|
+
// direction and therefore the dangerous one.
|
|
117
|
+
const byReason = new Map();
|
|
118
|
+
for (const entry of list) {
|
|
119
|
+
if (entry.passed !== false) continue;
|
|
120
|
+
const raw = typeof entry.failure_reason === 'string' ? entry.failure_reason.trim() : '';
|
|
121
|
+
const key = raw === '' ? UNATTRIBUTED : raw;
|
|
122
|
+
byReason.set(key, (byReason.get(key) || 0) + 1);
|
|
123
|
+
}
|
|
124
|
+
const topFailures = [...byReason.entries()]
|
|
125
|
+
.map(([reason, count]) => ({ reason, count }))
|
|
126
|
+
.sort((a, b) => b.count - a.count || a.reason.localeCompare(b.reason));
|
|
127
|
+
|
|
128
|
+
// ── Durations ─────────────────────────────────────────────────────────────
|
|
129
|
+
// Two independent guards, because the flag alone is not enough:
|
|
130
|
+
//
|
|
131
|
+
// 1. honour `duration_anomaly` -- the writer's own verdict;
|
|
132
|
+
// 2. reject implausible magnitudes. Measured on the real journal: 391 records
|
|
133
|
+
// carry the flag, but 6 MORE epoch-sized values (up to 1.78e12) do not,
|
|
134
|
+
// so trusting the flag alone left the mean at 1.3e9 ms -- the very
|
|
135
|
+
// "average 430 million seconds" artifact this is supposed to kill.
|
|
136
|
+
//
|
|
137
|
+
// The ceiling is deliberately generous (1 hour): a real gate run that long is
|
|
138
|
+
// pathological but conceivable, while an epoch stamp is ~1.78e12 and cannot be
|
|
139
|
+
// mistaken for anything else.
|
|
140
|
+
const MAX_PLAUSIBLE_DURATION_MS = 60 * 60 * 1000;
|
|
141
|
+
const isPlausible = (e) => typeof e.duration_ms === 'number'
|
|
142
|
+
&& Number.isFinite(e.duration_ms)
|
|
143
|
+
&& e.duration_ms >= 0
|
|
144
|
+
&& e.duration_ms <= MAX_PLAUSIBLE_DURATION_MS
|
|
145
|
+
&& e.duration_anomaly !== true;
|
|
146
|
+
const sane = list.filter(isPlausible);
|
|
147
|
+
const duration = sane.length > 0
|
|
148
|
+
? {
|
|
149
|
+
medianMs: median(sane.map((e) => e.duration_ms)),
|
|
150
|
+
meanMs: Math.round(mean(sane.map((e) => e.duration_ms))),
|
|
151
|
+
samples: sane.length,
|
|
152
|
+
// Every record that did NOT contribute -- flagged or implausible. Reporting
|
|
153
|
+
// only the flagged count would understate the exclusion and make the
|
|
154
|
+
// sample size look closer to the record count than it is.
|
|
155
|
+
excludedAnomalies: list.filter((e) => typeof e.duration_ms === 'number' && !isPlausible(e)).length,
|
|
156
|
+
}
|
|
157
|
+
: null;
|
|
158
|
+
|
|
159
|
+
// ── Rounds ────────────────────────────────────────────────────────────────
|
|
160
|
+
const rounds = groupRounds(list);
|
|
161
|
+
const reworkRounds = rounds.resolved;
|
|
162
|
+
|
|
163
|
+
// ── First-pass rate ───────────────────────────────────────────────────────
|
|
164
|
+
// Undefined without failures. A rate over an empty failure set is not 100%.
|
|
165
|
+
const measurable = failures > 0;
|
|
166
|
+
const firstPassRate = measurable ? Math.round((passes / totals) * 1000) / 10 : null;
|
|
167
|
+
|
|
168
|
+
return {
|
|
169
|
+
measurable,
|
|
170
|
+
reason: measurable ? null : 'no failure records in the journal -- first-pass rate is not measurable (pre-#651 journals only recorded passes)',
|
|
171
|
+
totalRecords: totals,
|
|
172
|
+
passes,
|
|
173
|
+
failures,
|
|
174
|
+
firstPassRate,
|
|
175
|
+
rounds: {
|
|
176
|
+
resolved: reworkRounds.length,
|
|
177
|
+
unresolved: rounds.unresolved > 0,
|
|
178
|
+
medianRework: median(reworkRounds),
|
|
179
|
+
meanRework: Math.round(mean(reworkRounds) * 100) / 100,
|
|
180
|
+
worstRework: reworkRounds.length > 0 ? Math.max(...reworkRounds) : 0,
|
|
181
|
+
},
|
|
182
|
+
topFailures,
|
|
183
|
+
duration,
|
|
184
|
+
};
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/** Render a stats object for the terminal. */
|
|
188
|
+
function formatReworkReport(stats, { limit = 10 } = {}) {
|
|
189
|
+
const lines = [];
|
|
190
|
+
lines.push('');
|
|
191
|
+
lines.push('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
|
|
192
|
+
lines.push(' REWORK REPORT');
|
|
193
|
+
lines.push('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
|
|
194
|
+
lines.push('');
|
|
195
|
+
lines.push(` Journal records: ${stats.totalRecords} (${stats.passes} pass, ${stats.failures} fail)`);
|
|
196
|
+
|
|
197
|
+
if (!stats.measurable) {
|
|
198
|
+
// Say WHY, and say it loudly: a silent 100% would be a lie.
|
|
199
|
+
lines.push('');
|
|
200
|
+
lines.push(' ⚠️ First-pass rate: NOT MEASURABLE');
|
|
201
|
+
lines.push(` ${stats.reason}`);
|
|
202
|
+
} else {
|
|
203
|
+
lines.push(` First-pass rate: ${stats.firstPassRate}%`);
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
if (stats.rounds.resolved > 0) {
|
|
207
|
+
lines.push('');
|
|
208
|
+
lines.push(` Rework rounds (failures before a landing): ${stats.rounds.resolved}`);
|
|
209
|
+
lines.push(` median ${stats.rounds.medianRework} / mean ${stats.rounds.meanRework} / worst ${stats.rounds.worstRework}`);
|
|
210
|
+
if (stats.rounds.unresolved) {
|
|
211
|
+
lines.push(' (a trailing run of failures has not landed yet)');
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
if (stats.topFailures.length > 0) {
|
|
216
|
+
lines.push('');
|
|
217
|
+
lines.push(` Top ${limit} blocking reasons:`);
|
|
218
|
+
for (const { reason, count } of stats.topFailures.slice(0, limit)) {
|
|
219
|
+
lines.push(` ${String(count).padStart(5)} ${reason}`);
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
if (stats.duration) {
|
|
224
|
+
lines.push('');
|
|
225
|
+
lines.push(` Gate duration: median ${stats.duration.medianMs} ms / mean ${stats.duration.meanMs} ms (${stats.duration.samples} samples`);
|
|
226
|
+
lines.push(` ${stats.duration.excludedAnomalies} anomaly record(s) excluded)`);
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
lines.push('');
|
|
230
|
+
return lines.join('\n');
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
/**
|
|
234
|
+
* CLI entry.
|
|
235
|
+
* @param {string[]} argv args after `rework`
|
|
236
|
+
*/
|
|
237
|
+
function main(argv = []) {
|
|
238
|
+
const repoRootIdx = argv.indexOf('--dir');
|
|
239
|
+
const repoRoot = repoRootIdx !== -1 && argv[repoRootIdx + 1] ? argv[repoRootIdx + 1] : process.cwd();
|
|
240
|
+
const limitIdx = argv.indexOf('--top');
|
|
241
|
+
const limit = limitIdx !== -1 && argv[limitIdx + 1] ? parseInt(argv[limitIdx + 1], 10) : 10;
|
|
242
|
+
|
|
243
|
+
const stats = computeReworkStats(readJournal(repoRoot));
|
|
244
|
+
if (argv.includes('--json')) {
|
|
245
|
+
process.stdout.write(`${JSON.stringify(stats, null, 2)}\n`);
|
|
246
|
+
return 0;
|
|
247
|
+
}
|
|
248
|
+
process.stdout.write(`${formatReworkReport(stats, { limit })}\n`);
|
|
249
|
+
return 0;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
module.exports = {
|
|
253
|
+
readJournal,
|
|
254
|
+
computeReworkStats,
|
|
255
|
+
formatReworkReport,
|
|
256
|
+
main,
|
|
257
|
+
UNATTRIBUTED,
|
|
258
|
+
};
|
|
259
|
+
|
|
260
|
+
if (require.main === module) {
|
|
261
|
+
process.exitCode = main(process.argv.slice(2));
|
|
262
|
+
}
|
package/mock-policy/AGENTS.md
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
# SRC/MOCK-POLICY KNOWLEDGE BASE
|
|
2
2
|
|
|
3
3
|
**Generated:** 2026-10-30
|
|
4
|
-
**Commit:**
|
|
4
|
+
**Commit:** 2ce96f13
|
|
5
5
|
**Branch:** main
|
|
6
|
-
**Version:** 0.
|
|
6
|
+
**Version:** 0.35.1.0
|
|
7
7
|
|
|
8
8
|
## OVERVIEW
|
|
9
9
|
Mock layering policy enforcement — Gate M3 of pre-push hook. Ensures integration tests use real implementations for internal dependencies, mock external dependencies, and annotate pending mocks with removal plans. Combines project scope scanning, mock decision engine, and per-file validation into a single pipeline.
|
package/mutation/AGENTS.md
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
# SRC/MUTATION KNOWLEDGE BASE
|
|
2
2
|
|
|
3
3
|
**Generated:** 2026-10-30
|
|
4
|
-
**Commit:**
|
|
4
|
+
**Commit:** 2ce96f13
|
|
5
5
|
**Branch:** main
|
|
6
|
-
**Version:** 0.
|
|
6
|
+
**Version:** 0.35.1.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.
|
|
3
|
+
"version": "0.35.1",
|
|
4
4
|
"displayName": "XP-Gate",
|
|
5
5
|
"description": "Extreme Programming quality gates + AI workflow skills for Claude Code. Includes 12 pre-commit gates (Gate 0-11), 8 pre-push gates, Sprint Flow (6 phases), and Delphi multi-expert review (>=90% consensus).",
|
|
6
6
|
"author": {
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
# SKILLS/DELPHI-REVIEW KNOWLEDGE BASE
|
|
2
2
|
|
|
3
3
|
**Generated:** 2026-10-30
|
|
4
|
-
**Commit:**
|
|
4
|
+
**Commit:** 2ce96f13
|
|
5
5
|
**Branch:** main
|
|
6
|
-
**Version:** 0.
|
|
6
|
+
**Version:** 0.35.1.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:** 2ce96f13
|
|
5
5
|
**Branch:** main
|
|
6
|
-
**Version:** 0.
|
|
6
|
+
**Version:** 0.35.1.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:** 2ce96f13
|
|
5
5
|
**Branch:** main
|
|
6
|
-
**Version:** 0.
|
|
6
|
+
**Version:** 0.35.1.0
|
|
7
7
|
|
|
8
8
|
## OVERVIEW
|
|
9
9
|
Test-Specification Alignment Engine — two-stage validation ensuring tests accurately reflect requirements and design specs.
|
package/plugins/dsh/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@boyingliu01/dsh-plugin-xp-gate",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.35.1",
|
|
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:** 2ce96f13
|
|
5
5
|
**Branch:** main
|
|
6
|
-
**Version:** 0.
|
|
6
|
+
**Version:** 0.35.1.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:** 2ce96f13
|
|
5
5
|
**Branch:** main
|
|
6
|
-
**Version:** 0.
|
|
6
|
+
**Version:** 0.35.1.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:** 2ce96f13
|
|
5
5
|
**Branch:** main
|
|
6
|
-
**Version:** 0.
|
|
6
|
+
**Version:** 0.35.1.0
|
|
7
7
|
|
|
8
8
|
## OVERVIEW
|
|
9
9
|
Test-Specification Alignment Engine — two-stage validation ensuring tests accurately reflect requirements and design specs.
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
# SKILLS/DELPHI-REVIEW KNOWLEDGE BASE
|
|
2
2
|
|
|
3
3
|
**Generated:** 2026-10-30
|
|
4
|
-
**Commit:**
|
|
4
|
+
**Commit:** 2ce96f13
|
|
5
5
|
**Branch:** main
|
|
6
|
-
**Version:** 0.
|
|
6
|
+
**Version:** 0.35.1.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:** 2ce96f13
|
|
5
5
|
**Branch:** main
|
|
6
|
-
**Version:** 0.
|
|
6
|
+
**Version:** 0.35.1.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:** 2ce96f13
|
|
5
5
|
**Branch:** main
|
|
6
|
-
**Version:** 0.
|
|
6
|
+
**Version:** 0.35.1.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.
|
|
3
|
+
"version": "0.35.1",
|
|
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:** 2ce96f13
|
|
5
5
|
**Branch:** main
|
|
6
|
-
**Version:** 0.
|
|
6
|
+
**Version:** 0.35.1.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:** 2ce96f13
|
|
5
5
|
**Branch:** main
|
|
6
|
-
**Version:** 0.
|
|
6
|
+
**Version:** 0.35.1.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:** 2ce96f13
|
|
5
5
|
**Branch:** main
|
|
6
|
-
**Version:** 0.
|
|
6
|
+
**Version:** 0.35.1.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:** 2ce96f13
|
|
5
5
|
**Branch:** main
|
|
6
|
-
**Version:** 0.
|
|
6
|
+
**Version:** 0.35.1.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.
|