@boyingliu01/xp-gate 0.32.2 → 0.32.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-3.sh +483 -28
- package/adapters/gate-4.sh +20 -0
- package/adapters/gate-7.sh +4 -0
- package/bin/xp-gate.js +48 -2
- package/gate-4.sh +8 -0
- package/hooks/gate-4.sh +8 -0
- package/hooks/post-commit +82 -0
- package/hooks/pre-commit +19 -0
- package/hooks/pre-push +21 -2
- package/lib/__tests__/doctor-audit-ledger.test.js +61 -0
- package/lib/__tests__/gate-audit.test.ts +144 -0
- package/lib/__tests__/shared-utils.test.js +22 -0
- package/lib/doctor.js +59 -1
- package/lib/gate-audit.ts +215 -7
- package/lib/mattpocock-license-verdict.json +1 -1
- package/lib/shared-utils.js +4 -1
- 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/sprint-flow/references/phase-3-build.md +21 -0
- package/plugins/claude-code/skills/test-specification-alignment/AGENTS.md +2 -2
- package/plugins/dsh/package.json +1 -1
- package/plugins/dsh/scripts/copy-client.cjs +6 -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/sprint-flow/references/phase-3-build.md +21 -0
- package/plugins/dsh/skills/test-specification-alignment/AGENTS.md +2 -2
- package/plugins/dsh/src/contract.test.ts +25 -1
- 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/sprint-flow/references/phase-3-build.md +21 -0
- 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/sprint-flow/references/phase-3-build.md +21 -0
- package/plugins/qoder/skills/test-specification-alignment/AGENTS.md +2 -2
- package/principles/AGENTS.md +2 -2
- package/scripts/sync-package-content.js +1 -1
- package/skills/delphi-review/AGENTS.md +2 -2
- package/skills/sprint-flow/AGENTS.md +2 -2
- package/skills/sprint-flow/references/phase-3-build.md +21 -0
- package/skills/test-specification-alignment/AGENTS.md +2 -2
package/lib/gate-audit.ts
CHANGED
|
@@ -14,6 +14,7 @@ import {
|
|
|
14
14
|
renameSync,
|
|
15
15
|
statSync,
|
|
16
16
|
chmodSync,
|
|
17
|
+
rmSync,
|
|
17
18
|
} from 'fs';
|
|
18
19
|
import { join } from 'path';
|
|
19
20
|
|
|
@@ -23,6 +24,7 @@ const AUDIT_DIR = '.xp-gate';
|
|
|
23
24
|
const AUDIT_FILE = 'audit.jsonl';
|
|
24
25
|
const MAX_FILE_BYTES = 10 * 1024 * 1024; // 10 MB
|
|
25
26
|
const MAX_ARCHIVES = 3;
|
|
27
|
+
const MAX_PLAUSIBLE_DURATION_MS = 6 * 60 * 60 * 1000; // 6h — no single gate runs longer
|
|
26
28
|
|
|
27
29
|
// ── Types ────────────────────────────────────────────────────────────────────
|
|
28
30
|
|
|
@@ -85,6 +87,75 @@ export function appendAuditEntry(
|
|
|
85
87
|
}
|
|
86
88
|
}
|
|
87
89
|
|
|
90
|
+
// ── Pre-commit staging ───────────────────────────────────────────────────────
|
|
91
|
+
// Pre-commit cannot know the commit it is about to create (HEAD is still the
|
|
92
|
+
// parent there), so per-gate entries are staged to a sidecar and stamped with
|
|
93
|
+
// the real commit by post-commit. Fallback safety: `flushPendingAudit` also
|
|
94
|
+
// runs at the START of the next pre-commit, so if post-commit never fired
|
|
95
|
+
// (hooks skipped, exception) the entries still land, attributed by `git log -1`
|
|
96
|
+
// at that later point — slightly late but never lost, never misattributed to
|
|
97
|
+
// the wrong commit when post-commit does run.
|
|
98
|
+
|
|
99
|
+
const PENDING_SUFFIX = 'audit.pending.jsonl';
|
|
100
|
+
|
|
101
|
+
function pendingPath(repoRoot: string): string {
|
|
102
|
+
return join(repoRoot, AUDIT_DIR, PENDING_SUFFIX);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/** Stage one entry for the not-yet-created commit. Never throws. */
|
|
106
|
+
export function stageAuditEntry(entry: GateAuditEntry, repoRoot: string = process.cwd()): void {
|
|
107
|
+
try {
|
|
108
|
+
const dir = join(repoRoot, AUDIT_DIR);
|
|
109
|
+
if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
|
|
110
|
+
appendFileSync(pendingPath(repoRoot), JSON.stringify(entry) + '\n', 'utf8');
|
|
111
|
+
} catch (err) {
|
|
112
|
+
console.error('[xp-gate audit] Failed to stage entry:', (err as Error).message);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Move staged entries into audit.jsonl, stamped with the real commit hash.
|
|
118
|
+
* Idempotent and safe to call when nothing is staged. A blank/absent hash
|
|
119
|
+
* leaves `commit_hash` exactly as staged so a wrong commit is never invented.
|
|
120
|
+
*/
|
|
121
|
+
export function flushPendingAudit(
|
|
122
|
+
repoRoot: string = process.cwd(),
|
|
123
|
+
commitHash: string = '',
|
|
124
|
+
): number {
|
|
125
|
+
const p = pendingPath(repoRoot);
|
|
126
|
+
if (!existsSync(p)) return 0;
|
|
127
|
+
let flushed = 0;
|
|
128
|
+
try {
|
|
129
|
+
const raw = readFileSync(p, 'utf8');
|
|
130
|
+
const lines = raw.split('\n').filter((l) => l.trim());
|
|
131
|
+
for (const line of lines) {
|
|
132
|
+
let entry: GateAuditEntry;
|
|
133
|
+
try {
|
|
134
|
+
entry = JSON.parse(line) as GateAuditEntry;
|
|
135
|
+
} catch {
|
|
136
|
+
// A torn line (interrupted mid-write) is dropped rather than crashing
|
|
137
|
+
// the hook that is trying to flush a previous run's leftovers.
|
|
138
|
+
continue;
|
|
139
|
+
}
|
|
140
|
+
if (commitHash) entry.commit_hash = commitHash;
|
|
141
|
+
appendAuditEntry(entry, repoRoot);
|
|
142
|
+
flushed++;
|
|
143
|
+
}
|
|
144
|
+
rmSyncSafe(p);
|
|
145
|
+
} catch (err) {
|
|
146
|
+
console.error('[xp-gate audit] Failed to flush pending entries:', (err as Error).message);
|
|
147
|
+
}
|
|
148
|
+
return flushed;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
function rmSyncSafe(p: string): void {
|
|
152
|
+
try {
|
|
153
|
+
rmSync(p, { force: true });
|
|
154
|
+
} catch {
|
|
155
|
+
/* best effort */
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
88
159
|
/**
|
|
89
160
|
* Read the last N entries from the audit log.
|
|
90
161
|
*/
|
|
@@ -152,7 +223,10 @@ function aggregateByGate(entries: GateAuditEntry[]): Map<string, { total: number
|
|
|
152
223
|
const buckets = new Map<string, { total: number; passed: number; ms: number; issues: number; anomaly_count: number }>();
|
|
153
224
|
|
|
154
225
|
for (const e of entries) {
|
|
155
|
-
|
|
226
|
+
// Tolerate malformed/legacy rows: a missing gate_id would otherwise create
|
|
227
|
+
// an `undefined` bucket key (and crash the sort comparing it to a string).
|
|
228
|
+
const gateId = typeof e.gate_id === 'string' && e.gate_id ? e.gate_id : 'unknown';
|
|
229
|
+
const b = buckets.get(gateId) ?? { total: 0, passed: 0, ms: 0, issues: 0, anomaly_count: 0 };
|
|
156
230
|
b.total += 1;
|
|
157
231
|
if (e.passed) b.passed += 1;
|
|
158
232
|
// Issue #370: exclude anomalous durations from ms aggregation. REQ-B: a
|
|
@@ -163,8 +237,10 @@ function aggregateByGate(entries: GateAuditEntry[]): Map<string, { total: number
|
|
|
163
237
|
} else if (typeof e.duration_ms === 'number') {
|
|
164
238
|
b.ms += e.duration_ms;
|
|
165
239
|
}
|
|
166
|
-
|
|
167
|
-
|
|
240
|
+
// A malformed/legacy row with a non-numeric issues_found must not turn
|
|
241
|
+
// the whole gate's average into NaN.
|
|
242
|
+
b.issues += Number.isFinite(e.issues_found) ? e.issues_found : 0;
|
|
243
|
+
buckets.set(gateId, b);
|
|
168
244
|
}
|
|
169
245
|
|
|
170
246
|
return buckets;
|
|
@@ -202,6 +278,70 @@ export function computeStats(
|
|
|
202
278
|
return results;
|
|
203
279
|
}
|
|
204
280
|
|
|
281
|
+
export interface AuditAnomalies {
|
|
282
|
+
/** Entries with an explicit duration_anomaly flag (Issue #370). */
|
|
283
|
+
flagged: number;
|
|
284
|
+
/** Entries with a null duration + reason (REQ-B unmeasurable). */
|
|
285
|
+
null_with_reason: number;
|
|
286
|
+
/** Entries persisting a provably-impossible duration (>6h / negative). */
|
|
287
|
+
implausible_duration: number;
|
|
288
|
+
/** Entries with no commit attribution (the off-by-one bug's fingerprints). */
|
|
289
|
+
missing_commit: number;
|
|
290
|
+
/** Sum of the above — the number CI should treat as "must be 0". */
|
|
291
|
+
total: number;
|
|
292
|
+
/** Running counter of entries scanned. */
|
|
293
|
+
scanned: number;
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
/**
|
|
297
|
+
* Count audit-health problems so CI can fail when the ledger is untrustworthy.
|
|
298
|
+
*
|
|
299
|
+
* These are exactly the artefacts of the two telemetry bugs we fixed: epoch
|
|
300
|
+
* durations (implausible_duration) and misattributed/blank commits
|
|
301
|
+
* (missing_commit). A clean ledger returns total=0.
|
|
302
|
+
*
|
|
303
|
+
* `since` (ISO-8601) scopes the check to entries at/after that instant. Both
|
|
304
|
+
* bugs predate the fix and left ~900 historical rows in the ledger; a CI gate
|
|
305
|
+
* that counted them would be permanently red. Scoping by the fix date turns
|
|
306
|
+
* the gate into "no NEW anomalies", which is exactly what we can enforce.
|
|
307
|
+
*/
|
|
308
|
+
export function computeAnomalies(repoRoot: string = process.cwd(), since?: string): AuditAnomalies {
|
|
309
|
+
const logPath = join(repoRoot, AUDIT_DIR, AUDIT_FILE);
|
|
310
|
+
const entries = parseAuditEntries(logPath);
|
|
311
|
+
let flagged = 0;
|
|
312
|
+
let null_with_reason = 0;
|
|
313
|
+
let implausible = 0;
|
|
314
|
+
let missingCommit = 0;
|
|
315
|
+
let considered = 0;
|
|
316
|
+
for (const e of entries) {
|
|
317
|
+
if (since && typeof e.timestamp === 'string' && e.timestamp < since) continue;
|
|
318
|
+
considered += 1;
|
|
319
|
+
if (e.duration_anomaly === true) flagged += 1;
|
|
320
|
+
if (e.duration_ms === null && e.duration_reason) null_with_reason += 1;
|
|
321
|
+
if (typeof e.duration_ms === 'number'
|
|
322
|
+
&& (!Number.isFinite(e.duration_ms) || e.duration_ms < 0 || e.duration_ms > MAX_PLAUSIBLE_DURATION_MS)) {
|
|
323
|
+
implausible += 1;
|
|
324
|
+
}
|
|
325
|
+
if (!e.commit_hash || e.commit_hash === 'unknown') missingCommit += 1;
|
|
326
|
+
}
|
|
327
|
+
return {
|
|
328
|
+
flagged,
|
|
329
|
+
null_with_reason,
|
|
330
|
+
implausible_duration: implausible,
|
|
331
|
+
missing_commit: missingCommit,
|
|
332
|
+
total: implausible + missingCommit,
|
|
333
|
+
scanned: considered,
|
|
334
|
+
};
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
/**
|
|
338
|
+
* Entries at/after this instant are expected to be clean. It is the date the
|
|
339
|
+
* two telemetry bugs were fixed; anything older is grandfathered because it
|
|
340
|
+
* cannot be re-measured. Keep in sync with the fix commit if you regenerate.
|
|
341
|
+
*/
|
|
342
|
+
export const AUDIT_ANOMALY_SINCE = '2026-10-30T00:00:00.000Z';
|
|
343
|
+
|
|
344
|
+
|
|
205
345
|
/**
|
|
206
346
|
* Rotate the audit log if it exceeds MAX_FILE_BYTES.
|
|
207
347
|
* Renames current file to .1, shifts existing archives.
|
|
@@ -250,24 +390,48 @@ function runCli(): void {
|
|
|
250
390
|
|
|
251
391
|
if (command === 'record') {
|
|
252
392
|
const opts = parseCliOptions(args.slice(1));
|
|
393
|
+
// Writer-side backstop: never persist an implausible duration (epoch-sized
|
|
394
|
+
// `end - 0`, unit error). Any caller-supplied reason wins; otherwise a
|
|
395
|
+
// rejected number is recorded as null + poisoned_start_ms.
|
|
396
|
+
const rawDuration = opts['duration-ms'] === 'null' ? null : parseInt(opts['duration-ms'] || '0', 10);
|
|
397
|
+
const durationCheck = sanitizeDurationMs(rawDuration);
|
|
398
|
+
const durationReason = opts['duration-reason'] || durationCheck.duration_reason;
|
|
253
399
|
const entry: GateAuditEntry = {
|
|
254
400
|
timestamp: new Date().toISOString(),
|
|
255
401
|
gate_id: opts['gate-id'] || 'unknown',
|
|
256
402
|
gate_name: opts['gate-name'] || 'unknown',
|
|
257
403
|
passed: opts['passed'] === 'true',
|
|
258
404
|
issues_found: parseInt(opts['issues-found'] || '0', 10),
|
|
259
|
-
duration_ms:
|
|
405
|
+
duration_ms: durationCheck.duration_ms,
|
|
260
406
|
trigger: (opts['trigger'] as 'commit' | 'push' | 'manual') || 'manual',
|
|
261
407
|
repo_path: process.cwd(),
|
|
262
|
-
|
|
408
|
+
// Pre-commit runs BEFORE the commit object exists, so `git rev-parse HEAD`
|
|
409
|
+
// here resolves the PARENT. resolveCommitHash lets an explicit
|
|
410
|
+
// --commit-hash win and falls back to HEAD otherwise.
|
|
411
|
+
commit_hash: resolveCommitHash(opts),
|
|
263
412
|
// Issue #370: only set when explicitly flagged (omit field otherwise)
|
|
264
413
|
...(opts['duration-anomaly'] === 'true' ? { duration_anomaly: true } : {}),
|
|
265
414
|
// REQ-B: why the duration is null — never guess a number for it.
|
|
266
|
-
...(
|
|
415
|
+
...(durationReason ? { duration_reason: durationReason as 'poisoned_start_ms' | 'clock_skew' } : {}),
|
|
267
416
|
// #507: only set when provided (omit field otherwise)
|
|
268
417
|
...(opts['detail'] ? { detail: opts['detail'] } : {}),
|
|
269
418
|
};
|
|
270
|
-
|
|
419
|
+
// Pre-commit (trigger=commit) is staging for a commit that does not exist
|
|
420
|
+
// yet: park it in the pending sidecar so post-commit can stamp the real
|
|
421
|
+
// hash. Its commit_hash is deliberately blank until then — stamping the
|
|
422
|
+
// parent here is exactly the off-by-one we are removing.
|
|
423
|
+
if (entry.trigger === 'commit' && !opts['commit-hash']) {
|
|
424
|
+
entry.commit_hash = '';
|
|
425
|
+
stageAuditEntry(entry);
|
|
426
|
+
} else {
|
|
427
|
+
appendAuditEntry(entry);
|
|
428
|
+
}
|
|
429
|
+
} else if (command === 'flush-pending') {
|
|
430
|
+
// Called by post-commit with the now-real HEAD.
|
|
431
|
+
const opts = parseCliOptions(args.slice(1));
|
|
432
|
+
const n = flushPendingAudit(process.cwd(), opts['commit-hash'] || '');
|
|
433
|
+
// Quiet by default (hooks are noisy enough); --report prints the count.
|
|
434
|
+
if (opts['report'] === 'true') console.log(`flushed ${n}`);
|
|
271
435
|
} else {
|
|
272
436
|
console.error(`Unknown audit command: ${command}`);
|
|
273
437
|
process.exit(1);
|
|
@@ -297,3 +461,47 @@ export function getCommitHash(): string {
|
|
|
297
461
|
return 'unknown';
|
|
298
462
|
}
|
|
299
463
|
}
|
|
464
|
+
|
|
465
|
+
/**
|
|
466
|
+
* Which commit a gate entry belongs to.
|
|
467
|
+
*
|
|
468
|
+
* Pre-commit fires BEFORE the commit object exists, so `git rev-parse HEAD`
|
|
469
|
+
* there returns the PARENT. If the hook let that stand, every gate entry would
|
|
470
|
+
* be filed under the previous commit — the newest commit would look like it
|
|
471
|
+
* "ran no gates", and any per-commit battery analysis silently mixes one
|
|
472
|
+
* commit's timings into another's. An explicit `--commit-hash` (from a caller
|
|
473
|
+
* that knows the subject, e.g. post-commit) always wins; otherwise HEAD is the
|
|
474
|
+
* subject (push/manual) and the resolver is used.
|
|
475
|
+
*
|
|
476
|
+
* `headResolver` is injectable so the precedence can be tested without a repo.
|
|
477
|
+
*/
|
|
478
|
+
export function resolveCommitHash(
|
|
479
|
+
opts: Record<string, string>,
|
|
480
|
+
headResolver: () => string = getCommitHash,
|
|
481
|
+
): string {
|
|
482
|
+
const explicit = (opts['commit-hash'] || '').trim();
|
|
483
|
+
if (explicit) return explicit;
|
|
484
|
+
return headResolver();
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
// ── Duration sanity (last line of defence) ───────────────────────────────────
|
|
488
|
+
// The shell side sanitizes start_ms before computing the delta
|
|
489
|
+
// (githooks/lib/audit-sanitize.sh, REQ-B). This is the writer-side backstop:
|
|
490
|
+
// if any caller path ever hands us a duration that is provably not a real
|
|
491
|
+
// gate runtime, refuse to persist it as a number. An epoch-sized value (the
|
|
492
|
+
// old bug produced `end - 0 = now`, ~1.7e12) or anything absurdly large would
|
|
493
|
+
// otherwise poison every average and p90 downstream and looks exactly like a
|
|
494
|
+
// "78-hour gate". Precision matters less than honesty: a rejected duration is
|
|
495
|
+
// null with a reason, never a fabricated number.
|
|
496
|
+
|
|
497
|
+
export function sanitizeDurationMs(
|
|
498
|
+
raw: number | null,
|
|
499
|
+
): { duration_ms: number | null; duration_reason?: 'poisoned_start_ms' } {
|
|
500
|
+
if (raw === null) return { duration_ms: null };
|
|
501
|
+
if (!Number.isFinite(raw) || raw < 0 || raw > MAX_PLAUSIBLE_DURATION_MS) {
|
|
502
|
+
// >6h or negative can only come from a poisoned start_ms (epoch-sized
|
|
503
|
+
// value, unit error) — mark it, don't guess a number.
|
|
504
|
+
return { duration_ms: null, duration_reason: 'poisoned_start_ms' };
|
|
505
|
+
}
|
|
506
|
+
return { duration_ms: raw };
|
|
507
|
+
}
|
package/lib/shared-utils.js
CHANGED
|
@@ -34,7 +34,10 @@ function readXpGateConfig() {
|
|
|
34
34
|
}
|
|
35
35
|
|
|
36
36
|
function copyHooks(srcDir, destDir) {
|
|
37
|
-
|
|
37
|
+
// post-commit carries the audit-attribution flush and --no-verify detection.
|
|
38
|
+
// It was absent here, so neither ever ran for installed users (the source
|
|
39
|
+
// existed but no installer copied it).
|
|
40
|
+
['pre-commit', 'pre-push', 'post-commit', 'post-merge'].forEach(hook => {
|
|
38
41
|
const src = path.join(srcDir, 'hooks', hook);
|
|
39
42
|
const dest = path.join(destDir, hook);
|
|
40
43
|
if (fs.existsSync(src)) {
|
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:** e5ca7d9d
|
|
5
5
|
**Branch:** main
|
|
6
|
-
**Version:** 0.32.
|
|
6
|
+
**Version:** 0.32.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
3
|
**Generated:** 2026-10-30
|
|
4
|
-
**Commit:**
|
|
4
|
+
**Commit:** e5ca7d9d
|
|
5
5
|
**Branch:** main
|
|
6
|
-
**Version:** 0.32.
|
|
6
|
+
**Version:** 0.32.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.32.
|
|
3
|
+
"version": "0.32.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
3
|
**Generated:** 2026-10-30
|
|
4
|
-
**Commit:**
|
|
4
|
+
**Commit:** e5ca7d9d
|
|
5
5
|
**Branch:** main
|
|
6
|
-
**Version:** 0.32.
|
|
6
|
+
**Version:** 0.32.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
3
|
**Generated:** 2026-10-30
|
|
4
|
-
**Commit:**
|
|
4
|
+
**Commit:** e5ca7d9d
|
|
5
5
|
**Branch:** main
|
|
6
|
-
**Version:** 0.32.
|
|
6
|
+
**Version:** 0.32.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.
|
|
@@ -236,6 +236,27 @@ Without these events, the Harness can only infer changes from file diffs and has
|
|
|
236
236
|
|
|
237
237
|
---
|
|
238
238
|
|
|
239
|
+
## 工具调用效率自检(P2 硬性 — 2026-10-30 诊断)
|
|
240
|
+
|
|
241
|
+
> BUILD 阶段是工具调用最密集的阶段,也是"等待时间长"的主要来源。依据
|
|
242
|
+
> `docs/plans/2026-10-30-dev-speed-diagnosis.md`:工具延迟占总耗时 58%,其中 32.7% 的
|
|
243
|
+
> shell 调用是纯读取/搜索、calls/step 仅 1.03、`job_output` 阻塞 66.5 h。以下四条在 BUILD
|
|
244
|
+
> 期间是硬性自检项,不满足不得宣称 REQ 完成。
|
|
245
|
+
|
|
246
|
+
| ❌ 低效做法 | ✅ 正确做法 |
|
|
247
|
+
|------------|------------|
|
|
248
|
+
| `cat` / `Get-Content` / `sed -n` / `head` 读文件 | 用 `read` 原生工具(毫秒级,可并发) |
|
|
249
|
+
| `grep` / `rg` / `Select-String` / `findstr` 搜内容 | 用 `grep` 原生工具 |
|
|
250
|
+
| `find` / `ls` / `Get-ChildItem` 找文件 | 用 `glob` 原生工具 |
|
|
251
|
+
| 一个一个发独立调用(改完再想下一个) | 多个互不依赖的调用放进**同一步**并发发出 |
|
|
252
|
+
| 用 `job_output wait:true` / `sleep` 反复阻塞等长任务 | `run_in_background` + 完成通知,需要时短超时取一次 |
|
|
253
|
+
| 打包/发布/插件改动只靠 CI 发现契约破坏 | 先写契约测试(`plugins/dsh/src/contract.test.ts` 范式),本地先红 |
|
|
254
|
+
|
|
255
|
+
**验收目标**:纯读取 shell 占比 <5%(现 32.7%);calls/step ≥2.0(现 1.03);`job_output`
|
|
256
|
+
调用数下降 ≥50%。这三项在 `xp-gate retro` 的 activity 统计里可复查。
|
|
257
|
+
|
|
258
|
+
---
|
|
259
|
+
|
|
239
260
|
## Blind Review — Read-Only Subagent Convention (v0.18.0+, 证据型标准步骤)
|
|
240
261
|
|
|
241
262
|
**Purpose**: Code review by an isolated subagent with read-only tool access. Replaces the former `freeze/unfreeze` mechanism.
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
# SKILLS/TEST-SPECIFICATION-ALIGNMENT KNOWLEDGE BASE
|
|
2
2
|
|
|
3
3
|
**Generated:** 2026-10-30
|
|
4
|
-
**Commit:**
|
|
4
|
+
**Commit:** e5ca7d9d
|
|
5
5
|
**Branch:** main
|
|
6
|
-
**Version:** 0.32.
|
|
6
|
+
**Version:** 0.32.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/plugins/dsh/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@boyingliu01/dsh-plugin-xp-gate",
|
|
3
|
-
"version": "0.32.
|
|
3
|
+
"version": "0.32.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",
|
|
@@ -29,4 +29,9 @@ fs.mkdirSync(path.dirname(OUT), { recursive: true });
|
|
|
29
29
|
fs.copyFileSync(SRC, OUT);
|
|
30
30
|
|
|
31
31
|
const rel = (p) => path.relative(PLUGIN_ROOT, p).split(path.sep).join('/');
|
|
32
|
-
|
|
32
|
+
// stderr, not stdout: this runs from `prepack`, and npm's `pack --json` contract
|
|
33
|
+
// is that stdout carries the JSON blob and nothing else. A single console.log here
|
|
34
|
+
// made `npm pack --dry-run --json | jq ...` fail with "Invalid numeric literal"
|
|
35
|
+
// and took the whole publish job down. `prepack.cjs` already logs to stderr for
|
|
36
|
+
// the same reason; match it.
|
|
37
|
+
console.error(`[client] ${rel(SRC)} -> ${rel(OUT)}`);
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
# SKILLS/DELPHI-REVIEW KNOWLEDGE BASE
|
|
2
2
|
|
|
3
3
|
**Generated:** 2026-10-30
|
|
4
|
-
**Commit:**
|
|
4
|
+
**Commit:** e5ca7d9d
|
|
5
5
|
**Branch:** main
|
|
6
|
-
**Version:** 0.32.
|
|
6
|
+
**Version:** 0.32.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
3
|
**Generated:** 2026-10-30
|
|
4
|
-
**Commit:**
|
|
4
|
+
**Commit:** e5ca7d9d
|
|
5
5
|
**Branch:** main
|
|
6
|
-
**Version:** 0.32.
|
|
6
|
+
**Version:** 0.32.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.
|
|
@@ -236,6 +236,27 @@ Without these events, the Harness can only infer changes from file diffs and has
|
|
|
236
236
|
|
|
237
237
|
---
|
|
238
238
|
|
|
239
|
+
## 工具调用效率自检(P2 硬性 — 2026-10-30 诊断)
|
|
240
|
+
|
|
241
|
+
> BUILD 阶段是工具调用最密集的阶段,也是"等待时间长"的主要来源。依据
|
|
242
|
+
> `docs/plans/2026-10-30-dev-speed-diagnosis.md`:工具延迟占总耗时 58%,其中 32.7% 的
|
|
243
|
+
> shell 调用是纯读取/搜索、calls/step 仅 1.03、`job_output` 阻塞 66.5 h。以下四条在 BUILD
|
|
244
|
+
> 期间是硬性自检项,不满足不得宣称 REQ 完成。
|
|
245
|
+
|
|
246
|
+
| ❌ 低效做法 | ✅ 正确做法 |
|
|
247
|
+
|------------|------------|
|
|
248
|
+
| `cat` / `Get-Content` / `sed -n` / `head` 读文件 | 用 `read` 原生工具(毫秒级,可并发) |
|
|
249
|
+
| `grep` / `rg` / `Select-String` / `findstr` 搜内容 | 用 `grep` 原生工具 |
|
|
250
|
+
| `find` / `ls` / `Get-ChildItem` 找文件 | 用 `glob` 原生工具 |
|
|
251
|
+
| 一个一个发独立调用(改完再想下一个) | 多个互不依赖的调用放进**同一步**并发发出 |
|
|
252
|
+
| 用 `job_output wait:true` / `sleep` 反复阻塞等长任务 | `run_in_background` + 完成通知,需要时短超时取一次 |
|
|
253
|
+
| 打包/发布/插件改动只靠 CI 发现契约破坏 | 先写契约测试(`plugins/dsh/src/contract.test.ts` 范式),本地先红 |
|
|
254
|
+
|
|
255
|
+
**验收目标**:纯读取 shell 占比 <5%(现 32.7%);calls/step ≥2.0(现 1.03);`job_output`
|
|
256
|
+
调用数下降 ≥50%。这三项在 `xp-gate retro` 的 activity 统计里可复查。
|
|
257
|
+
|
|
258
|
+
---
|
|
259
|
+
|
|
239
260
|
## Blind Review — Read-Only Subagent Convention (v0.18.0+, 证据型标准步骤)
|
|
240
261
|
|
|
241
262
|
**Purpose**: Code review by an isolated subagent with read-only tool access. Replaces the former `freeze/unfreeze` mechanism.
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
# SKILLS/TEST-SPECIFICATION-ALIGNMENT KNOWLEDGE BASE
|
|
2
2
|
|
|
3
3
|
**Generated:** 2026-10-30
|
|
4
|
-
**Commit:**
|
|
4
|
+
**Commit:** e5ca7d9d
|
|
5
5
|
**Branch:** main
|
|
6
|
-
**Version:** 0.32.
|
|
6
|
+
**Version:** 0.32.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,5 +1,5 @@
|
|
|
1
1
|
import { describe, it, expect } from "vitest"
|
|
2
|
-
import { readFileSync } from "node:fs"
|
|
2
|
+
import { readFileSync, readdirSync } from "node:fs"
|
|
3
3
|
import { join, resolve } from "node:path"
|
|
4
4
|
import { fileURLToPath } from "node:url"
|
|
5
5
|
|
|
@@ -95,4 +95,28 @@ describe("packaging contract", () => {
|
|
|
95
95
|
// A report with no explicit verdict must not be counted toward consensus.
|
|
96
96
|
expect(core).toContain("INVALID_NO_VERDICT")
|
|
97
97
|
})
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* @test REQ-DSH-019 prepack scripts keep stdout clean for `npm pack --json`
|
|
101
|
+
* @intent 发布流水线用 `npm pack --dry-run --json | jq ...` 量 tarball 大小。
|
|
102
|
+
* `prepack` 里任何一句 console.log 都会混进那个 JSON 流,jq 报
|
|
103
|
+
* "Invalid numeric literal" 并以 exit 5 结束——0.32.2 的 dsh 插件正是
|
|
104
|
+
* 因此没发出去,而 create-release 依赖三个 publish job,于是 tag 与
|
|
105
|
+
* release 一起被跳过。日志一律走 stderr。
|
|
106
|
+
* @covers AC-DSH-019-01
|
|
107
|
+
*/
|
|
108
|
+
it("prepack-time scripts log to stderr, never stdout", () => {
|
|
109
|
+
const scriptsDir = join(PKG_ROOT, "scripts")
|
|
110
|
+
const files = readdirSync(scriptsDir).filter((f) => f.endsWith(".cjs"))
|
|
111
|
+
expect(files.length, "no prepack scripts found").toBeGreaterThan(0)
|
|
112
|
+
|
|
113
|
+
for (const f of files) {
|
|
114
|
+
const source = readFileSync(join(scriptsDir, f), "utf8")
|
|
115
|
+
// Strip comments so an explanatory "console.log" in prose does not fail it.
|
|
116
|
+
const code = source.replace(/\/\*[\s\S]*?\*\//g, "").replace(/\/\/.*$/gm, "")
|
|
117
|
+
expect(code, `${f} writes to stdout and would corrupt npm pack --json`).not.toMatch(
|
|
118
|
+
/console\.log\s*\(/,
|
|
119
|
+
)
|
|
120
|
+
}
|
|
121
|
+
})
|
|
98
122
|
})
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
# SKILLS/DELPHI-REVIEW KNOWLEDGE BASE
|
|
2
2
|
|
|
3
3
|
**Generated:** 2026-10-30
|
|
4
|
-
**Commit:**
|
|
4
|
+
**Commit:** e5ca7d9d
|
|
5
5
|
**Branch:** main
|
|
6
|
-
**Version:** 0.32.
|
|
6
|
+
**Version:** 0.32.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
3
|
**Generated:** 2026-10-30
|
|
4
|
-
**Commit:**
|
|
4
|
+
**Commit:** e5ca7d9d
|
|
5
5
|
**Branch:** main
|
|
6
|
-
**Version:** 0.32.
|
|
6
|
+
**Version:** 0.32.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.
|
|
@@ -236,6 +236,27 @@ Without these events, the Harness can only infer changes from file diffs and has
|
|
|
236
236
|
|
|
237
237
|
---
|
|
238
238
|
|
|
239
|
+
## 工具调用效率自检(P2 硬性 — 2026-10-30 诊断)
|
|
240
|
+
|
|
241
|
+
> BUILD 阶段是工具调用最密集的阶段,也是"等待时间长"的主要来源。依据
|
|
242
|
+
> `docs/plans/2026-10-30-dev-speed-diagnosis.md`:工具延迟占总耗时 58%,其中 32.7% 的
|
|
243
|
+
> shell 调用是纯读取/搜索、calls/step 仅 1.03、`job_output` 阻塞 66.5 h。以下四条在 BUILD
|
|
244
|
+
> 期间是硬性自检项,不满足不得宣称 REQ 完成。
|
|
245
|
+
|
|
246
|
+
| ❌ 低效做法 | ✅ 正确做法 |
|
|
247
|
+
|------------|------------|
|
|
248
|
+
| `cat` / `Get-Content` / `sed -n` / `head` 读文件 | 用 `read` 原生工具(毫秒级,可并发) |
|
|
249
|
+
| `grep` / `rg` / `Select-String` / `findstr` 搜内容 | 用 `grep` 原生工具 |
|
|
250
|
+
| `find` / `ls` / `Get-ChildItem` 找文件 | 用 `glob` 原生工具 |
|
|
251
|
+
| 一个一个发独立调用(改完再想下一个) | 多个互不依赖的调用放进**同一步**并发发出 |
|
|
252
|
+
| 用 `job_output wait:true` / `sleep` 反复阻塞等长任务 | `run_in_background` + 完成通知,需要时短超时取一次 |
|
|
253
|
+
| 打包/发布/插件改动只靠 CI 发现契约破坏 | 先写契约测试(`plugins/dsh/src/contract.test.ts` 范式),本地先红 |
|
|
254
|
+
|
|
255
|
+
**验收目标**:纯读取 shell 占比 <5%(现 32.7%);calls/step ≥2.0(现 1.03);`job_output`
|
|
256
|
+
调用数下降 ≥50%。这三项在 `xp-gate retro` 的 activity 统计里可复查。
|
|
257
|
+
|
|
258
|
+
---
|
|
259
|
+
|
|
239
260
|
## Blind Review — Read-Only Subagent Convention (v0.18.0+, 证据型标准步骤)
|
|
240
261
|
|
|
241
262
|
**Purpose**: Code review by an isolated subagent with read-only tool access. Replaces the former `freeze/unfreeze` mechanism.
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
# SKILLS/TEST-SPECIFICATION-ALIGNMENT KNOWLEDGE BASE
|
|
2
2
|
|
|
3
3
|
**Generated:** 2026-10-30
|
|
4
|
-
**Commit:**
|
|
4
|
+
**Commit:** e5ca7d9d
|
|
5
5
|
**Branch:** main
|
|
6
|
-
**Version:** 0.32.
|
|
6
|
+
**Version:** 0.32.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.32.
|
|
3
|
+
"version": "0.32.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
3
|
**Generated:** 2026-10-30
|
|
4
|
-
**Commit:**
|
|
4
|
+
**Commit:** e5ca7d9d
|
|
5
5
|
**Branch:** main
|
|
6
|
-
**Version:** 0.32.
|
|
6
|
+
**Version:** 0.32.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.
|