@boyingliu01/xp-gate 0.25.0 → 0.27.0

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.
Files changed (120) hide show
  1. package/adapters/python.sh +53 -3
  2. package/bin/xp-gate.js +161 -28
  3. package/hooks/lib/python-incremental-typecheck.sh +104 -0
  4. package/hooks/lib/typecheck.sh +10 -0
  5. package/hooks/pre-commit +84 -32
  6. package/lib/__tests__/check-alignment-cli.test.mjs +77 -0
  7. package/lib/__tests__/doctor-469-hook-env.test.js +12 -7
  8. package/lib/__tests__/doctor-module-resolution-parity.test.js +15 -6
  9. package/lib/__tests__/doctor-tui-skill-freshness.test.js +16 -4
  10. package/lib/__tests__/test-alignment.test.ts +37 -0
  11. package/lib/__tests__/windows-bash-probe.test.js +73 -0
  12. package/lib/check-alignment-runner.cjs +74 -0
  13. package/lib/detect-deps.js +82 -19
  14. package/lib/gate-runner.js +12 -5
  15. package/lib/mattpocock-license-verdict.json +1 -1
  16. package/lib/sprint-status.js +5 -2
  17. package/lib/test-alignment.ts +136 -12
  18. package/mock-policy/AGENTS.md +3 -3
  19. package/mutation/AGENTS.md +3 -3
  20. package/package.json +1 -1
  21. package/plugins/claude-code/.claude-plugin/plugin.json +1 -1
  22. package/plugins/claude-code/skills/delphi-review/AGENTS.md +3 -3
  23. package/plugins/claude-code/skills/sprint-flow/AGENTS.md +3 -3
  24. package/plugins/claude-code/skills/test-specification-alignment/AGENTS.md +3 -3
  25. package/plugins/dsh/README.md +1 -1
  26. package/plugins/dsh/package.json +1 -1
  27. package/plugins/dsh/scripts/prepack.cjs +2 -2
  28. package/plugins/dsh/skills/delphi-review/AGENTS.md +3 -3
  29. package/plugins/dsh/skills/sprint-flow/AGENTS.md +3 -3
  30. package/plugins/dsh/skills/test-specification-alignment/AGENTS.md +3 -3
  31. package/plugins/dsh/src/skills.test.ts +20 -7
  32. package/plugins/opencode/package.json +1 -1
  33. package/plugins/opencode/scripts/prepack.cjs +0 -1
  34. package/plugins/opencode/skills/delphi-review/AGENTS.md +3 -3
  35. package/plugins/opencode/skills/sprint-flow/AGENTS.md +3 -3
  36. package/plugins/opencode/skills/test-specification-alignment/AGENTS.md +3 -3
  37. package/plugins/qoder/plugin.json +1 -1
  38. package/plugins/qoder/skills/delphi-review/AGENTS.md +3 -3
  39. package/plugins/qoder/skills/sprint-flow/AGENTS.md +3 -3
  40. package/plugins/qoder/skills/test-specification-alignment/AGENTS.md +3 -3
  41. package/principles/AGENTS.md +3 -3
  42. package/principles/rules/__tests__/clean-code/code-duplication.test.ts +9 -8
  43. package/principles/rules/__tests__/clean-code/deep-nesting.test.ts +3 -4
  44. package/principles/rules/__tests__/clean-code/god-class.test.ts +2 -4
  45. package/principles/rules/__tests__/clean-code/large-file.test.ts +2 -4
  46. package/principles/rules/__tests__/clean-code/long-function.test.ts +2 -4
  47. package/principles/rules/__tests__/clean-code/many-exports.test.ts +2 -3
  48. package/principles/rules/__tests__/clean-code/missing-error-handling.test.ts +2 -4
  49. package/principles/rules/__tests__/clean-code/too-many-params.test.ts +2 -4
  50. package/principles/rules/__tests__/clean-code/unused-imports.test.ts +4 -4
  51. package/principles/rules/__tests__/solid/dip.test.ts +2 -4
  52. package/principles/rules/__tests__/solid/isp.test.ts +2 -4
  53. package/principles/rules/__tests__/solid/lsp.test.ts +2 -4
  54. package/principles/rules/__tests__/solid/ocp.test.ts +2 -4
  55. package/principles/rules/__tests__/solid/srp.test.ts +2 -4
  56. package/principles/rules/clean-code/code-duplication.ts +12 -14
  57. package/principles/rules/clean-code/deep-nesting.ts +18 -20
  58. package/principles/rules/clean-code/god-class.ts +32 -34
  59. package/principles/rules/clean-code/large-file.ts +14 -16
  60. package/principles/rules/clean-code/long-function.ts +26 -28
  61. package/principles/rules/clean-code/magic-numbers.ts +39 -38
  62. package/principles/rules/clean-code/many-exports.ts +21 -23
  63. package/principles/rules/clean-code/missing-error-handling.ts +13 -15
  64. package/principles/rules/clean-code/too-many-params.ts +15 -17
  65. package/principles/rules/clean-code/unused-imports.ts +13 -15
  66. package/principles/rules/solid/dip.ts +16 -18
  67. package/principles/rules/solid/isp.ts +17 -19
  68. package/principles/rules/solid/lsp.ts +4 -6
  69. package/principles/rules/solid/ocp.ts +17 -19
  70. package/principles/rules/solid/srp.ts +20 -22
  71. package/scripts/__tests__/sync-package-content.test.js +0 -1
  72. package/scripts/sync-package-content.js +0 -1
  73. package/skills/delphi-review/AGENTS.md +3 -3
  74. package/skills/sprint-flow/AGENTS.md +3 -3
  75. package/skills/test-specification-alignment/AGENTS.md +3 -3
  76. package/plugins/claude-code/skills/clipboard-vision/SKILL.md +0 -48
  77. package/plugins/claude-code/skills/clipboard-vision/__tests__/clipboard-vision-distribution.test.cjs +0 -64
  78. package/plugins/claude-code/skills/clipboard-vision/__tests__/clipboard-vision-security.test.cjs +0 -185
  79. package/plugins/claude-code/skills/clipboard-vision/__tests__/clipboard-vision.Tests.ps1 +0 -138
  80. package/plugins/claude-code/skills/clipboard-vision/__tests__/clipboard-vision.test.cjs +0 -273
  81. package/plugins/claude-code/skills/clipboard-vision/describe-clipboard.ps1 +0 -115
  82. package/plugins/claude-code/skills/clipboard-vision/describe-clipboard.sh +0 -161
  83. package/plugins/claude-code/skills/clipboard-vision/scripts/clipboard-vision.ps1 +0 -86
  84. package/plugins/claude-code/skills/clipboard-vision/scripts/clipboard-vision.sh +0 -137
  85. package/plugins/dsh/skills/clipboard-vision/SKILL.md +0 -48
  86. package/plugins/dsh/skills/clipboard-vision/__tests__/clipboard-vision-distribution.test.cjs +0 -64
  87. package/plugins/dsh/skills/clipboard-vision/__tests__/clipboard-vision-security.test.cjs +0 -185
  88. package/plugins/dsh/skills/clipboard-vision/__tests__/clipboard-vision.Tests.ps1 +0 -138
  89. package/plugins/dsh/skills/clipboard-vision/__tests__/clipboard-vision.test.cjs +0 -273
  90. package/plugins/dsh/skills/clipboard-vision/describe-clipboard.ps1 +0 -115
  91. package/plugins/dsh/skills/clipboard-vision/describe-clipboard.sh +0 -161
  92. package/plugins/dsh/skills/clipboard-vision/scripts/clipboard-vision.ps1 +0 -86
  93. package/plugins/dsh/skills/clipboard-vision/scripts/clipboard-vision.sh +0 -137
  94. package/plugins/opencode/skills/clipboard-vision/SKILL.md +0 -48
  95. package/plugins/opencode/skills/clipboard-vision/__tests__/clipboard-vision-distribution.test.cjs +0 -64
  96. package/plugins/opencode/skills/clipboard-vision/__tests__/clipboard-vision-security.test.cjs +0 -185
  97. package/plugins/opencode/skills/clipboard-vision/__tests__/clipboard-vision.Tests.ps1 +0 -138
  98. package/plugins/opencode/skills/clipboard-vision/__tests__/clipboard-vision.test.cjs +0 -273
  99. package/plugins/opencode/skills/clipboard-vision/describe-clipboard.ps1 +0 -115
  100. package/plugins/opencode/skills/clipboard-vision/describe-clipboard.sh +0 -161
  101. package/plugins/opencode/skills/clipboard-vision/scripts/clipboard-vision.ps1 +0 -86
  102. package/plugins/opencode/skills/clipboard-vision/scripts/clipboard-vision.sh +0 -137
  103. package/plugins/qoder/skills/clipboard-vision/SKILL.md +0 -48
  104. package/plugins/qoder/skills/clipboard-vision/__tests__/clipboard-vision-distribution.test.cjs +0 -64
  105. package/plugins/qoder/skills/clipboard-vision/__tests__/clipboard-vision-security.test.cjs +0 -185
  106. package/plugins/qoder/skills/clipboard-vision/__tests__/clipboard-vision.Tests.ps1 +0 -138
  107. package/plugins/qoder/skills/clipboard-vision/__tests__/clipboard-vision.test.cjs +0 -273
  108. package/plugins/qoder/skills/clipboard-vision/describe-clipboard.ps1 +0 -115
  109. package/plugins/qoder/skills/clipboard-vision/describe-clipboard.sh +0 -161
  110. package/plugins/qoder/skills/clipboard-vision/scripts/clipboard-vision.ps1 +0 -86
  111. package/plugins/qoder/skills/clipboard-vision/scripts/clipboard-vision.sh +0 -137
  112. package/skills/clipboard-vision/SKILL.md +0 -48
  113. package/skills/clipboard-vision/__tests__/clipboard-vision-distribution.test.cjs +0 -64
  114. package/skills/clipboard-vision/__tests__/clipboard-vision-security.test.cjs +0 -185
  115. package/skills/clipboard-vision/__tests__/clipboard-vision.Tests.ps1 +0 -138
  116. package/skills/clipboard-vision/__tests__/clipboard-vision.test.cjs +0 -273
  117. package/skills/clipboard-vision/describe-clipboard.ps1 +0 -115
  118. package/skills/clipboard-vision/describe-clipboard.sh +0 -161
  119. package/skills/clipboard-vision/scripts/clipboard-vision.ps1 +0 -86
  120. package/skills/clipboard-vision/scripts/clipboard-vision.sh +0 -137
@@ -230,28 +230,124 @@ function countAssertions(content: string): number {
230
230
  }
231
231
 
232
232
  /**
233
- * Extract JSDoc block preceding a test/it/describe declaration.
234
- * Handles .each(), .skip(), .todo() variants and backtick template literals.
233
+ * Given a content string and the index where the test callback arrow/function
234
+ * begins (the position of `() =>` or `function() {`), scan forward with brace
235
+ * matching to return the full callback body (including the outer braces).
236
+ * Skips single/double quoted strings, backtick template literals, and line/block
237
+ * comments so braces inside them do not throw off matching.
238
+ */
239
+ function extractCallbackBody(content: string, callbackStart: number): string {
240
+ // Find the opening brace of the callback body after `() => {` / `function() {`.
241
+ let i = callbackStart;
242
+ let inLineComment = false;
243
+ let inBlockComment = false;
244
+ let inSingle = false;
245
+ let inDouble = false;
246
+ let inTemplate = false;
247
+ let depth = 0;
248
+ let started = false;
249
+ let openIdx = -1;
250
+
251
+ while (i < content.length) {
252
+ const c = content[i];
253
+ const n = content[i + 1];
254
+
255
+ if (inLineComment) {
256
+ if (c === '\n') inLineComment = false;
257
+ i++;
258
+ continue;
259
+ }
260
+ if (inBlockComment) {
261
+ if (c === '*' && n === '/') { inBlockComment = false; i += 2; continue; }
262
+ i++;
263
+ continue;
264
+ }
265
+ if (inSingle) {
266
+ if (c === '\\') { i += 2; continue; }
267
+ if (c === '\'') inSingle = false;
268
+ i++;
269
+ continue;
270
+ }
271
+ if (inDouble) {
272
+ if (c === '\\') { i += 2; continue; }
273
+ if (c === '"') inDouble = false;
274
+ i++;
275
+ continue;
276
+ }
277
+ if (inTemplate) {
278
+ if (c === '\\') { i += 2; continue; }
279
+ if (c === '`') inTemplate = false;
280
+ // Template `${}` interpolation can contain braces; treat interpolation
281
+ // content conservatively by not toggling depth on braces inside it.
282
+ i++;
283
+ continue;
284
+ }
285
+
286
+ if (c === '/' && n === '/') { inLineComment = true; i += 2; continue; }
287
+ if (c === '/' && n === '*') { inBlockComment = true; i += 2; continue; }
288
+ if (c === '\'') { inSingle = true; i++; continue; }
289
+ if (c === '"') { inDouble = true; i++; continue; }
290
+ if (c === '`') { inTemplate = true; i++; continue; }
291
+
292
+ if (c === '{') {
293
+ if (!started) { started = true; openIdx = i; depth = 1; }
294
+ else depth++;
295
+ i++;
296
+ continue;
297
+ }
298
+ if (c === '}') {
299
+ depth--;
300
+ if (depth === 0) {
301
+ return content.slice(openIdx, i + 1);
302
+ }
303
+ i++;
304
+ continue;
305
+ }
306
+
307
+ i++;
308
+ }
309
+
310
+ // No balanced body found — the declaration had no callback body (e.g. a
311
+ // `test.todo('name');` line). Return an empty body so it counts 0 assertions.
312
+ return '';
313
+ }
314
+
315
+ /**
316
+ * Extract JSDoc block preceding a test/it/describe declaration, along with the
317
+ * *function body* of the callback (so assertion counting is per-test, not
318
+ * per-file). Handles .skip(), .todo(), .only() variants and backtick template
319
+ * literals.
320
+ *
321
+ * Known limitation (pre-existing, not introduced by #392): `test.each([...])`
322
+ * parameterised forms are NOT matched, because the argument list is a bracketed
323
+ * array literal and the name string follows it without a comma. This means such
324
+ * tests are omitted from assertion counting. Coverage determination itself is
325
+ * driven by `@covers` tags, so this only affects the avg-assertions data-validity
326
+ * heuristic, never AC coverage. Tracked separately from #392.
235
327
  */
236
328
  function extractJSDocBeforeTest(content: string): Array<{ jsdoc: string; testBody: string; testName: string }> {
237
329
  const results: Array<{ jsdoc: string; testBody: string; testName: string }> = [];
238
330
 
239
- // Match: /** JSDOC */ (test|it|describe)[.modifier]( [args], )'name', ...
240
- // `.each` variants have an extra `()` before the test name string
241
- const testPattern = /\/\*\*([\s\S]*?)\*\/(?:[\s\n]*)(?:test|it|describe)(?:\.(?:each|skip|todo|only))?\s*\([^)]*\)\s*,\s*\(\s*(?:['"]([^'"]+)['"]|`([^`]*)`)\s*[,\)]/g;
331
+ // Match: /** JSDOC */ (test|it|describe)[.modifier]( [args], )'name', ( [params] ) => {
332
+ // `.each` variants have an extra `()` before the test name string.
333
+ // Group 4 (bodyStart) points at the callback open paren so we can brace-match
334
+ // the real function body.
335
+ const testPattern = /\/\*\*([\s\S]*?)\*\/(?:[\s\n]*)(?:test|it|describe)(?:\.(?:each|skip|todo|only))?\s*\([^)]*\)\s*,\s*\(\s*(?:['"]([^'"]+)['"]|`([^`]*)`)\s*\)?\s*,?\s*(?:\([^)]*\)\s*=>)?\s*/g;
242
336
 
243
337
  let match: RegExpExecArray | null;
244
338
  while ((match = testPattern.exec(content)) !== null) {
245
339
  const jsdoc = match[1]!;
246
340
  const testName = match[2] || match[3] || '';
247
341
  if (testName) {
248
- const testBody = content.slice(match.index, match.index + match[0].length);
342
+ const body = extractCallbackBody(content, match.index + match[0].length);
343
+ // JSDoc + declaration + function body gives a self-contained test body.
344
+ const testBody = content.slice(match.index, match.index + match[0].length) + body;
249
345
  results.push({ jsdoc, testBody, testName });
250
346
  }
251
347
  }
252
348
 
253
349
  // Second pass: match standard test/it/describe without .each
254
- const simplePattern = /\/\*\*([\s\S]*?)\*\/(?:[\s\n]*)(?:test|it|describe)(?:\.(?:skip|todo|only))?\s*\(\s*(?:['"]([^'"]+)['"]|`([^`]*)`)\s*[,\)]/g;
350
+ const simplePattern = /\/\*\*([\s\S]*?)\*\/(?:[\s\n]*)(?:test|it|describe)(?:\.(?:skip|todo|only))?\s*\(\s*(?:['"]([^'"]+)['"]|`([^`]*)`)\s*\)?\s*,?\s*(?:\([^)]*\)\s*=>)?\s*/g;
255
351
 
256
352
  while ((match = simplePattern.exec(content)) !== null) {
257
353
  const jsdoc = match[1]!;
@@ -260,7 +356,8 @@ function extractJSDocBeforeTest(content: string): Array<{ jsdoc: string; testBod
260
356
  // Don't duplicate if already found in first pass
261
357
  const alreadyFound = results.some(r => r.jsdoc === jsdoc);
262
358
  if (!alreadyFound) {
263
- const testBody = content.slice(match.index, match.index + match[0].length);
359
+ const body = extractCallbackBody(content, match.index + match[0].length);
360
+ const testBody = content.slice(match.index, match.index + match[0].length) + body;
264
361
  results.push({ jsdoc, testBody, testName });
265
362
  }
266
363
  }
@@ -269,6 +366,27 @@ function extractJSDocBeforeTest(content: string): Array<{ jsdoc: string; testBod
269
366
  return results;
270
367
  }
271
368
 
369
+ /**
370
+ * Extract a Python test function body: from the `def test_xxx():` line up to
371
+ * (but not including) the next top-level `def ` line or end of file. This keeps
372
+ * assertion counting scoped to the individual test.
373
+ */
374
+ function extractPythonTestBody(content: string, defIndex: number): string {
375
+ const nextDef = content.indexOf('\ndef ', defIndex);
376
+ const endIdx = nextDef === -1 ? content.length : nextDef;
377
+ return content.slice(defIndex, endIdx);
378
+ }
379
+
380
+ /**
381
+ * Extract a Go test function body via brace matching starting at the opening
382
+ * brace that follows `func Test_xxx()`. Returns the full `{ ... }` block.
383
+ */
384
+ function extractGoTestBody(content: string, funcIndex: number): string {
385
+ const braceIdx = content.indexOf('{', funcIndex);
386
+ if (braceIdx === -1) return '';
387
+ return extractCallbackBody(content, braceIdx);
388
+ }
389
+
272
390
  function pushTestWithCovers(
273
391
  tests: TestCase[],
274
392
  testPath: string,
@@ -276,6 +394,7 @@ function pushTestWithCovers(
276
394
  reqId: string,
277
395
  intent: string | undefined,
278
396
  fileContent: string,
397
+ bodyContent: string,
279
398
  totalAssertionsRef: { value: number },
280
399
  ) {
281
400
  const coversRegex = /@covers\s+(.+)/g;
@@ -285,7 +404,9 @@ function pushTestWithCovers(
285
404
  covers.push(...coversMatch[1]!.split(',').map(s => s.trim()).filter(c => /^AC-[A-Z0-9]+-\d+-\d+$/.test(c)));
286
405
  }
287
406
 
288
- const assertions = countAssertions(fileContent);
407
+ // Count assertions against the *per-test body*, not the whole file, so
408
+ // totalAssertions is not inflated by fileCount x perFileCount (#392-#1).
409
+ const assertions = countAssertions(bodyContent);
289
410
  totalAssertionsRef.value += assertions;
290
411
 
291
412
  tests.push({
@@ -319,7 +440,7 @@ export function parseTestFiles(testPaths: string[]): TestMap {
319
440
  // Filter: only valid AC IDs (AC-XXX-NNN-NN pattern)
320
441
  const covers = coversRaw.filter(c => /^AC-[A-Z0-9]+-\d+-\d+$/.test(c));
321
442
 
322
- const assertions = countAssertions(content);
443
+ const assertions = countAssertions(m.testBody);
323
444
  totalAssertionsObj.value += assertions;
324
445
 
325
446
  tests.push({
@@ -336,7 +457,8 @@ export function parseTestFiles(testPaths: string[]): TestMap {
336
457
  const pyTestPattern = /(# @test\s+(REQ-[A-Z0-9]+-[A-Z0-9]+)[\s\S]*?)(?:# @intent\s+(.+?)\n)?def\s+(test_\w+)/g;
337
458
  let m: RegExpExecArray | null;
338
459
  while ((m = pyTestPattern.exec(content)) !== null) {
339
- pushTestWithCovers(tests, testPath, m[4]!, m[2]!, m[3]?.trim(), content, totalAssertionsObj);
460
+ const body = extractPythonTestBody(content, m.index + m[0].lastIndexOf('def '));
461
+ pushTestWithCovers(tests, testPath, m[4]!, m[2]!, m[3]?.trim(), content, body, totalAssertionsObj);
340
462
  }
341
463
  } else if (ext === '.go') {
342
464
  const goTestPattern = /(\/\/ @test\s+(REQ-[A-Z0-9]+-[A-Z0-9]+))\s*[\s\S]*?func\s+(Test_\w+)/g;
@@ -344,7 +466,9 @@ export function parseTestFiles(testPaths: string[]): TestMap {
344
466
  while ((m = goTestPattern.exec(content)) !== null) {
345
467
  const block = content.slice(Math.max(0, m.index - 500), m.index);
346
468
  const intentMatch = block.match(/\/\/ @intent\s+(.+)/);
347
- pushTestWithCovers(tests, testPath, m[3]!, m[2]!, intentMatch?.[1]?.trim(), content, totalAssertionsObj);
469
+ const funcStart = m.index + m[0].lastIndexOf('func ');
470
+ const body = extractGoTestBody(content, funcStart);
471
+ pushTestWithCovers(tests, testPath, m[3]!, m[2]!, intentMatch?.[1]?.trim(), content, body, totalAssertionsObj);
348
472
  }
349
473
  }
350
474
  }
@@ -1,9 +1,9 @@
1
1
  # SRC/MOCK-POLICY KNOWLEDGE BASE
2
2
 
3
- **Generated:** 2026-10-14
4
- **Commit:** a757d625
3
+ **Generated:** 2026-10-16
4
+ **Commit:** 429f73c5
5
5
  **Branch:** main
6
- **Version:** 0.25.0.0
6
+ **Version:** 0.27.0.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.
@@ -1,9 +1,9 @@
1
1
  # SRC/MUTATION KNOWLEDGE BASE
2
2
 
3
- **Generated:** 2026-10-14
4
- **Commit:** a757d625
3
+ **Generated:** 2026-10-16
4
+ **Commit:** 429f73c5
5
5
  **Branch:** main
6
- **Version:** 0.25.0.0
6
+ **Version:** 0.27.0.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": "@boyingliu01/xp-gate",
3
- "version": "0.25.0",
3
+ "version": "0.27.0",
4
4
  "description": "AI-driven development workflow: 6 quality gates + Delphi review + Sprint Flow",
5
5
  "bin": {
6
6
  "xp-gate": "./bin/xp-gate.js"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "xp-gate",
3
- "version": "0.25.0",
3
+ "version": "0.27.0",
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-14
4
- **Commit:** a757d625
3
+ **Generated:** 2026-10-16
4
+ **Commit:** 429f73c5
5
5
  **Branch:** main
6
- **Version:** 0.25.0.0
6
+ **Version:** 0.27.0.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-14
4
- **Commit:** a757d625
3
+ **Generated:** 2026-10-16
4
+ **Commit:** 429f73c5
5
5
  **Branch:** main
6
- **Version:** 0.25.0.0
6
+ **Version:** 0.27.0.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-14
4
- **Commit:** a757d625
3
+ **Generated:** 2026-10-16
4
+ **Commit:** 429f73c5
5
5
  **Branch:** main
6
- **Version:** 0.25.0.0
6
+ **Version:** 0.27.0.0
7
7
 
8
8
  ## OVERVIEW
9
9
  Test-Specification Alignment Engine — two-stage validation ensuring tests accurately reflect requirements and design specs.
@@ -185,6 +185,6 @@ npm run build:dsh-plugin # = node scripts/build-plugin.mjs --platform dsh
185
185
  ## 注意
186
186
 
187
187
  - `lib/` 与 `skills/` 是**构建产物**(已 gitignore),不提交。
188
- - `scripts/prepack.cjs` 目前硬编码 12 个 skill,**不含 `clipboard-vision`**(仓库有 13 个)。
188
+ - `scripts/prepack.cjs` 的 skill 列表为**动态发现**(读取仓库 `skills/`),不硬编码。
189
189
  - `plugins/dsh/package.json` 的 peerDeps 仍 pin `0.1.1-rc.2`,但实测运行在 `0.2.0-rc.2` 上
190
190
  `defineTool` / `TOOL_ABORTED` / `HarnessError` 签名未变,无需改写。
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@boyingliu01/dsh-plugin-xp-gate",
3
- "version": "0.25.0",
3
+ "version": "0.27.0",
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",
@@ -10,8 +10,8 @@
10
10
  * plugins/opencode/scripts/prepack.cjs).
11
11
  *
12
12
  * The skill list is DISCOVERED, not hardcoded: a hardcoded list silently drifts
13
- * from the repo (it previously shipped 12 of 13, omitting clipboard-vision, and
14
- * would need editing on every new skill) — see #448.
13
+ * from the repo (a hardcoded set previously omitted one shipped skill, breaking
14
+ * parity) — see #448.
15
15
  */
16
16
 
17
17
  const fs = require('fs');
@@ -1,9 +1,9 @@
1
1
  # SKILLS/DELPHI-REVIEW KNOWLEDGE BASE
2
2
 
3
- **Generated:** 2026-10-14
4
- **Commit:** a757d625
3
+ **Generated:** 2026-10-16
4
+ **Commit:** 429f73c5
5
5
  **Branch:** main
6
- **Version:** 0.25.0.0
6
+ **Version:** 0.27.0.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-14
4
- **Commit:** a757d625
3
+ **Generated:** 2026-10-16
4
+ **Commit:** 429f73c5
5
5
  **Branch:** main
6
- **Version:** 0.25.0.0
6
+ **Version:** 0.27.0.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-14
4
- **Commit:** a757d625
3
+ **Generated:** 2026-10-16
4
+ **Commit:** 429f73c5
5
5
  **Branch:** main
6
- **Version:** 0.25.0.0
6
+ **Version:** 0.27.0.0
7
7
 
8
8
  ## OVERVIEW
9
9
  Test-Specification Alignment Engine — two-stage validation ensuring tests accurately reflect requirements and design specs.
@@ -14,10 +14,20 @@ const REPO_SKILLS_DIR = resolve(PLUGIN_ROOT, "..", "..", "skills")
14
14
  /**
15
15
  * The expected set is the REPO's skill directories, discovered rather than
16
16
  * hardcoded. A hardcoded list silently drifts: it previously named 12 skills and
17
- * omitted clipboard-vision, while prepack shipped 12 of 13 (#448).
17
+ * omitted one that prepack shipped, breaking parity (#448).
18
18
  */
19
19
  function repoSkills(): string[] {
20
- return readdirSync(REPO_SKILLS_DIR, { withFileTypes: true })
20
+ // Guarded IO: a missing/unreadable repo skills dir is a test-environment
21
+ // failure we surface loudly, not a silent empty set (clean-code.missing-error-handling).
22
+ let entries;
23
+ try {
24
+ entries = readdirSync(REPO_SKILLS_DIR, { withFileTypes: true })
25
+ } catch (err) {
26
+ throw new Error(
27
+ `Cannot read repo skills dir ${REPO_SKILLS_DIR}: ${err instanceof Error ? err.message : String(err)}`
28
+ )
29
+ }
30
+ return entries
21
31
  .filter((d) => d.isDirectory())
22
32
  .map((d) => d.name)
23
33
  .filter((name) => existsSync(join(REPO_SKILLS_DIR, name, "SKILL.md")))
@@ -25,7 +35,14 @@ function repoSkills(): string[] {
25
35
  }
26
36
 
27
37
  function readFrontmatter(file: string): Record<string, unknown> {
28
- const raw = readFileSync(file, "utf8")
38
+ // Guarded IO: re-throw with context so a broken SKILL.md names the file
39
+ // (clean-code.missing-error-handling).
40
+ let raw;
41
+ try {
42
+ raw = readFileSync(file, "utf8")
43
+ } catch (err) {
44
+ throw new Error(`${file}: cannot read file (${err instanceof Error ? err.message : String(err)})`)
45
+ }
29
46
  const m = raw.match(/^---\s*\r?\n([\s\S]*?)\r?\n---\s*\r?\n/)
30
47
  if (!m) throw new Error(`${file}: missing YAML frontmatter`)
31
48
  return parse(m[1]) as Record<string, unknown>
@@ -58,8 +75,4 @@ describe("bundled skills", () => {
58
75
  expect((fm.description as string).trim().length, `${name}: non-empty description`).toBeGreaterThan(0)
59
76
  }
60
77
  })
61
-
62
- it("bundles clipboard-vision, which the old hardcoded list omitted (#448)", () => {
63
- expect(existsSync(join(SKILLS_DIR, "clipboard-vision", "SKILL.md"))).toBe(true)
64
- })
65
78
  })
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@boyingliu01/opencode-plugin",
3
- "version": "0.25.0",
3
+ "version": "0.27.0",
4
4
  "type": "module",
5
5
  "main": "index.ts",
6
6
  "description": "XP-Gate quality gates + AI workflow skills + Sprint Flow TUI sidebar for OpenCode",
@@ -16,7 +16,6 @@ const REPO_ROOT = path.resolve(PLUGIN_ROOT, '..', '..');
16
16
 
17
17
  const CORE_SKILLS = [
18
18
  'admin-template-guidelines',
19
- 'clipboard-vision',
20
19
  'delphi-review',
21
20
  'improve-codebase-architecture',
22
21
  'ralph-loop',
@@ -1,9 +1,9 @@
1
1
  # SKILLS/DELPHI-REVIEW KNOWLEDGE BASE
2
2
 
3
- **Generated:** 2026-10-14
4
- **Commit:** a757d625
3
+ **Generated:** 2026-10-16
4
+ **Commit:** 429f73c5
5
5
  **Branch:** main
6
- **Version:** 0.25.0.0
6
+ **Version:** 0.27.0.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-14
4
- **Commit:** a757d625
3
+ **Generated:** 2026-10-16
4
+ **Commit:** 429f73c5
5
5
  **Branch:** main
6
- **Version:** 0.25.0.0
6
+ **Version:** 0.27.0.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-14
4
- **Commit:** a757d625
3
+ **Generated:** 2026-10-16
4
+ **Commit:** 429f73c5
5
5
  **Branch:** main
6
- **Version:** 0.25.0.0
6
+ **Version:** 0.27.0.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.25.0",
3
+ "version": "0.27.0",
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-14
4
- **Commit:** a757d625
3
+ **Generated:** 2026-10-16
4
+ **Commit:** 429f73c5
5
5
  **Branch:** main
6
- **Version:** 0.25.0.0
6
+ **Version:** 0.27.0.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-14
4
- **Commit:** a757d625
3
+ **Generated:** 2026-10-16
4
+ **Commit:** 429f73c5
5
5
  **Branch:** main
6
- **Version:** 0.25.0.0
6
+ **Version:** 0.27.0.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-14
4
- **Commit:** a757d625
3
+ **Generated:** 2026-10-16
4
+ **Commit:** 429f73c5
5
5
  **Branch:** main
6
- **Version:** 0.25.0.0
6
+ **Version:** 0.27.0.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
  # PRINCIPLES CHECKER MODULE
2
2
 
3
- **Generated:** 2026-10-14
4
- **Commit:** a757d625
3
+ **Generated:** 2026-10-16
4
+ **Commit:** 429f73c5
5
5
  **Branch:** main
6
- **Version:** 0.25.0.0
6
+ **Version:** 0.27.0.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.
@@ -56,15 +56,16 @@ describe('code-duplication.ts - Code Duplication Rule', () => {
56
56
  expect(violations).toEqual([]);
57
57
  });
58
58
 
59
- it('should return empty array when adapter throws', () => {
60
- const mockAdapterThrowing = {
61
- ...mockAdapter,
62
- duplicationPercentage: () => { throw new Error('jscpd error'); }
63
- };
59
+ it('should propagate adapter errors (no empty catch, #392)', () => {
60
+ // #392-#4: the rule no longer swallows adapter errors in an empty catch, so
61
+ // a throwing adapter getter must bubble up to analyzer.runRuleOnFile().
62
+ const mockAdapterThrowing = Object.defineProperty(
63
+ { ...mockAdapter },
64
+ 'duplicationPercentage',
65
+ { get: () => { throw new Error('jscpd error'); } }
66
+ );
64
67
 
65
- const violations = codeDuplicationRule.check('test.ts', mockAdapterThrowing as never);
66
-
67
- expect(violations).toEqual([]);
68
+ expect(() => codeDuplicationRule.check('test.ts', mockAdapterThrowing as never)).toThrow('jscpd error');
68
69
  });
69
70
 
70
71
  it('should handle multiple files with duplication', () => {
@@ -61,14 +61,13 @@ function deeplyNested() {
61
61
  expect(deepNestingRule.severity).toBe('warning');
62
62
  });
63
63
 
64
- it('should return empty violations when adapter throws error', () => {
64
+ it('should propagate adapter errors (no empty catch, #392)', () => {
65
65
  const mockAdapterThatThrows = {
66
66
  ...mockAdapter,
67
67
  extractFunctions: () => { throw new Error('Adapter failed'); }
68
68
  };
69
69
 
70
- const violations = deepNestingRule.check('test.ts', mockAdapterThatThrows as never);
71
-
72
- expect(violations.length).toBe(0);
70
+ // Rule errors must bubble up to analyzer.runRuleOnFile(), not be swallowed.
71
+ expect(() => deepNestingRule.check('test.ts', mockAdapterThatThrows as never)).toThrow('Adapter failed');
73
72
  });
74
73
  });
@@ -98,14 +98,12 @@ class DataClass {
98
98
  expect(godClassRule.severity).toBe('warning');
99
99
  });
100
100
 
101
- it('should return empty violations when adapter throws error', () => {
101
+ it('should propagate adapter errors (no empty catch, #392)', () => {
102
102
  const mockAdapterThatThrows = {
103
103
  ...mockAdapter,
104
104
  extractClasses: () => { throw new Error('Adapter failed'); }
105
105
  };
106
106
 
107
- const violations = godClassRule.check('test.ts', mockAdapterThatThrows as never);
108
-
109
- expect(violations.length).toBe(0);
107
+ expect(() => godClassRule.check('test.ts', mockAdapterThatThrows as never)).toThrow('Adapter failed');
110
108
  });
111
109
  });
@@ -61,14 +61,12 @@ describe('largeFileRule', () => {
61
61
  expect(largeFileRule.threshold).toEqual(1150);
62
62
  });
63
63
 
64
- it('should return empty violations when adapter throws error', () => {
64
+ it('should propagate adapter errors (no empty catch, #392)', () => {
65
65
  const mockAdapterThatThrows = {
66
66
  ...mockAdapter,
67
67
  countLines: () => { throw new Error('Adapter failed'); }
68
68
  };
69
69
 
70
- const violations = largeFileRule.check('test.ts', mockAdapterThatThrows);
71
-
72
- expect(violations).toHaveLength(0);
70
+ expect(() => largeFileRule.check('test.ts', mockAdapterThatThrows)).toThrow('Adapter failed');
73
71
  });
74
72
  });