@boyingliu01/xp-gate 0.9.4 → 0.10.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 (35) hide show
  1. package/adapters/python.sh +61 -1
  2. package/lib/doctor.js +34 -18
  3. package/lib/shared-phase-constants.js +23 -9
  4. package/mock-policy/AGENTS.md +3 -3
  5. package/mutation/AGENTS.md +3 -3
  6. package/mutation/gate-m.ts +209 -274
  7. package/mutation/runners/index.ts +22 -0
  8. package/mutation/runners/mutmut-runner.ts +220 -0
  9. package/mutation/runners/stryker-runner.ts +146 -0
  10. package/mutation/runners/types.ts +99 -0
  11. package/package.json +1 -1
  12. package/plugins/claude-code/.claude-plugin/plugin.json +1 -1
  13. package/plugins/claude-code/skills/delphi-review/AGENTS.md +3 -3
  14. package/plugins/claude-code/skills/delphi-review/SKILL.md +34 -0
  15. package/plugins/claude-code/skills/sprint-flow/AGENTS.md +3 -3
  16. package/plugins/claude-code/skills/sprint-flow/SKILL.md +53 -1
  17. package/plugins/claude-code/skills/test-specification-alignment/AGENTS.md +3 -3
  18. package/plugins/opencode/__tests__/xp-gate-update.test.ts +1 -11
  19. package/plugins/opencode/index.ts +0 -1
  20. package/plugins/opencode/package.json +1 -1
  21. package/plugins/opencode/skills/delphi-review/AGENTS.md +3 -3
  22. package/plugins/opencode/skills/delphi-review/SKILL.md +34 -0
  23. package/plugins/opencode/skills/sprint-flow/AGENTS.md +3 -3
  24. package/plugins/opencode/skills/sprint-flow/SKILL.md +53 -1
  25. package/plugins/opencode/skills/test-specification-alignment/AGENTS.md +3 -3
  26. package/plugins/opencode/tui-plugin.ts +69 -6
  27. package/plugins/qoder/skills/delphi-review/AGENTS.md +3 -3
  28. package/plugins/qoder/skills/sprint-flow/AGENTS.md +3 -3
  29. package/plugins/qoder/skills/test-specification-alignment/AGENTS.md +3 -3
  30. package/principles/AGENTS.md +3 -3
  31. package/skills/delphi-review/AGENTS.md +3 -3
  32. package/skills/delphi-review/SKILL.md +34 -0
  33. package/skills/sprint-flow/AGENTS.md +3 -3
  34. package/skills/sprint-flow/SKILL.md +53 -1
  35. package/skills/test-specification-alignment/AGENTS.md +3 -3
@@ -101,4 +101,64 @@ run_coverage() {
101
101
  fi
102
102
 
103
103
  return $PYTEST_EXIT
104
- }
104
+ }
105
+ # ── Gate M: Python mutation testing (mutmut) ──
106
+
107
+ run_mutation() {
108
+ local files_arg="$1"
109
+ local timeout_ms="${2:-120000}"
110
+ local timeout_s=$((timeout_ms / 1000))
111
+
112
+ if ! detect_python_mutation_testable; then
113
+ echo "⚠ mutmut not installed. SKIP — Gate M (Python)."
114
+ return 0
115
+ fi
116
+
117
+ # Parse comma-separated file list into mutmut --paths-to-mulate args
118
+ local file_list=""
119
+ IFS=',' read -ra FILE_ARRAY <<< "$files_arg"
120
+ for f in "${FILE_ARRAY[@]}"; do
121
+ f=$(echo "$f" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//')
122
+ [ -f "$f" ] && file_list="$file_list $f"
123
+ done
124
+
125
+ if [ -z "$file_list" ]; then
126
+ echo "📚 No valid Python source files for mutation. SKIP — Gate M (Python)."
127
+ return 0
128
+ fi
129
+
130
+ echo "🐍 Running Python mutation testing (mutmut) on: $file_list"
131
+
132
+ # Gate M orchestrator (TypeScript) handles mutmut via MutmutRunner in src/mutation/runners/
133
+ # The shell adapter delegates to the TS runner for consistency with TS Gate M.
134
+ if [ -f "src/mutation/gate-m.ts" ]; then
135
+ timeout "${timeout_s}s" npx tsx src/mutation/gate-m.ts \
136
+ --changed-files "$files_arg" 2>&1
137
+ return $?
138
+ fi
139
+
140
+ # Fallback: direct mutmut CLI if TS gate module not available
141
+ local MUTATION_OUTPUT
142
+ MUTATION_OUTPUT=$(mktemp)
143
+
144
+ timeout "${timeout_s}s" mutmut run --paths-to-mutate $file_list > "$MUTATION_OUTPUT" 2>&1
145
+ local EXIT_CODE=$?
146
+
147
+ cat "$MUTATION_OUTPUT"
148
+ rm -f "$MUTATION_OUTPUT"
149
+
150
+ case $EXIT_CODE in
151
+ 0)
152
+ echo "✅ Gate M (Python): PASS"
153
+ return 0
154
+ ;;
155
+ 124)
156
+ echo "⏱ Gate M (Python): TIMEOUT (${timeout_s}s). Allowing push with warning."
157
+ return 0
158
+ ;;
159
+ *)
160
+ echo "❌ Gate M (Python): mutation score below threshold"
161
+ return 1
162
+ ;;
163
+ esac
164
+ }
package/lib/doctor.js CHANGED
@@ -386,21 +386,15 @@ function fixMissingAdapters(mode, srcDir, adaptersDir) {
386
386
  return false;
387
387
  }
388
388
 
389
- /**
390
- * Attempt to fix known issues.
391
- * Only operates when mode === 'active' (local or global).
392
- */
393
- function fixIssues(checks, config) {
394
- console.log('');
395
- console.log('Attempting fixes...');
396
- console.log('-------------------');
397
-
398
- const srcDir = PKG_DIR;
389
+ function fixConfigMismatches(config) {
399
390
  let fixed = false;
400
-
401
391
  fixed = fixVersionMismatch(config, getPackageVersion()) || fixed;
402
392
  fixed = fixTemplateDirMismatch(config, getTemplateDir()) || fixed;
393
+ return fixed;
394
+ }
403
395
 
396
+ function fixHooksByMode(config, srcDir) {
397
+ let fixed = false;
404
398
  if (config.mode === 'local') {
405
399
  const gitDir = getGitDir();
406
400
  if (gitDir) {
@@ -410,18 +404,40 @@ function fixIssues(checks, config) {
410
404
  } else {
411
405
  fixed = fixMissingHooks('global', srcDir, GLOBAL_HOOKS_DIR) || fixed;
412
406
  }
407
+ return fixed;
408
+ }
413
409
 
414
- if (config.mode === 'global') {
415
- const hooksPath = getCurrentHooksPath();
416
- if (hooksPath !== GLOBAL_HOOKS_DIR) {
417
- fixed = fixCoreHooksPath(GLOBAL_HOOKS_DIR) || fixed;
418
- }
410
+ function fixGlobalHooksPath(config) {
411
+ if (config.mode !== 'global') return false;
412
+ const hooksPath = getCurrentHooksPath();
413
+ if (hooksPath !== GLOBAL_HOOKS_DIR) {
414
+ return fixCoreHooksPath(GLOBAL_HOOKS_DIR);
419
415
  }
416
+ return false;
417
+ }
420
418
 
421
- const adaptersDir = config.mode === 'local'
419
+ function getAdaptersDirByMode(config) {
420
+ return config.mode === 'local'
422
421
  ? path.join(path.dirname(getGitDir() || ''), 'githooks', 'adapters')
423
422
  : GLOBAL_ADAPTERS_DIR;
424
- fixed = fixMissingAdapters(config.mode, srcDir, adaptersDir) || fixed;
423
+ }
424
+
425
+ /**
426
+ * Attempt to fix known issues.
427
+ * Only operates when mode === 'active' (local or global).
428
+ */
429
+ function fixIssues(checks, config) {
430
+ console.log('');
431
+ console.log('Attempting fixes...');
432
+ console.log('-------------------');
433
+
434
+ const srcDir = PKG_DIR;
435
+ let fixed = false;
436
+
437
+ fixed = fixConfigMismatches(config) || fixed;
438
+ fixed = fixHooksByMode(config, srcDir) || fixed;
439
+ fixed = fixGlobalHooksPath(config) || fixed;
440
+ fixed = fixMissingAdapters(config.mode, srcDir, getAdaptersDirByMode(config)) || fixed;
425
441
 
426
442
  if (!fixed) {
427
443
  console.log(' No fixable issues found.');
@@ -24,6 +24,27 @@ const PHASE_NAMES = {
24
24
 
25
25
  const PHASE_ORDER = ['-1', '-0.5', '0', '1', '2', '3', '4', '5', '6', '7', '8'];
26
26
 
27
+ /**
28
+ * Safely parse a date-like value into epoch ms.
29
+ * @param {*} value - Timestamp to parse
30
+ * @returns {number} Epoch ms, or NaN if invalid
31
+ */
32
+ function parseTime(value) {
33
+ return new Date(value).getTime();
34
+ }
35
+
36
+ /**
37
+ * Get the max of an existing value and a new timestamp (if valid and larger).
38
+ * @param {number} current - Current best value
39
+ * @param {*} candidate - Candidate timestamp
40
+ * @returns {number} Max value
41
+ */
42
+ function maxValid(current, candidate) {
43
+ if (!candidate) return current;
44
+ const t = parseTime(candidate);
45
+ return !isNaN(t) && t > current ? t : current;
46
+ }
47
+
27
48
  /**
28
49
  * Find the most recent timestamp across started_at and phase_history.
29
50
  * @param {object} state - Sprint state object
@@ -31,19 +52,12 @@ const PHASE_ORDER = ['-1', '-0.5', '0', '1', '2', '3', '4', '5', '6', '7', '8'];
31
52
  */
32
53
  function getLatestTimestamp(state) {
33
54
  if (!state || !state.started_at) return 0;
34
- const started = new Date(state.started_at).getTime();
55
+ const started = parseTime(state.started_at);
35
56
  if (isNaN(started)) return 0;
36
57
  let latest = started;
37
58
  if (Array.isArray(state.phase_history)) {
38
59
  for (const ph of state.phase_history) {
39
- if (ph.completed_at) {
40
- const t = new Date(ph.completed_at).getTime();
41
- if (!isNaN(t) && t > latest) latest = t;
42
- }
43
- if (ph.started_at) {
44
- const t = new Date(ph.started_at).getTime();
45
- if (!isNaN(t) && t > latest) latest = t;
46
- }
60
+ latest = maxValid(maxValid(latest, ph.completed_at), ph.started_at);
47
61
  }
48
62
  }
49
63
  return latest;
@@ -1,9 +1,9 @@
1
1
  # SRC/MOCK-POLICY KNOWLEDGE BASE
2
2
 
3
- **Generated:** 2026-06-19
4
- **Commit:** b67f7f9
3
+ **Generated:** 2026-06-22
4
+ **Commit:** 984d568
5
5
  **Branch:** main
6
- **Version:** 0.9.4.0
6
+ **Version:** 0.10.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-06-19
4
- **Commit:** b67f7f9
3
+ **Generated:** 2026-06-22
4
+ **Commit:** 984d568
5
5
  **Branch:** main
6
- **Version:** 0.9.4.0
6
+ **Version:** 0.10.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.