@boyingliu01/xp-gate 0.11.3 → 0.12.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.
package/adapter-common.sh CHANGED
@@ -2,65 +2,90 @@
2
2
 
3
3
  # Common adapter functions for language detection and routing
4
4
 
5
+ # OS detection: returns linux/macos/windows/unknown
6
+ # Uses uname -s (POSIX) as primary, ${OSTYPE-} as fallback
7
+ detect_os_env() {
8
+ local os
9
+ os=$(uname -s 2>/dev/null || echo "unknown")
10
+ case "$os" in
11
+ Linux*) echo "linux";;
12
+ Darwin*) echo "macos";;
13
+ MINGW*|MSYS*|CYGWIN*) echo "windows";;
14
+ *)
15
+ # Fallback: OSTYPE (bash built-in, not always available)
16
+ if [ -n "${OSTYPE-}" ]; then
17
+ case "${OSTYPE-}" in
18
+ linux*) echo "linux";;
19
+ darwin*) echo "macos";;
20
+ msys*|cygwin*) echo "windows";;
21
+ *) echo "unknown";;
22
+ esac
23
+ else
24
+ echo "unknown"
25
+ fi
26
+ ;;
27
+ esac
28
+ }
29
+
5
30
  detect_project_lang() {
6
- if [[ -f "tsconfig.json" ]]; then
31
+ if [ -f "tsconfig.json" ]; then
7
32
  echo "typescript"
8
- elif [[ -f "pyproject.toml" ]] || [[ -f "requirements.txt" ]] || [[ -f "setup.py" ]]; then
33
+ elif [ -f "pyproject.toml" ] || [ -f "requirements.txt" ] || [ -f "setup.py" ]; then
9
34
  echo "python"
10
- elif [[ -f "go.mod" ]]; then
35
+ elif [ -f "go.mod" ]; then
11
36
  echo "go"
12
- elif [[ -f "build.gradle" ]] || [[ -f "build.gradle.kts" ]]; then
13
- if [[ -n "$(find . -name "*.kt" -type f | head -n 1)" ]]; then
37
+ elif [ -f "build.gradle" ] || [ -f "build.gradle.kts" ]; then
38
+ if [ -n "$(find . -name "*.kt" -type f | sed -n '1p; 1q')" ]; then
14
39
  echo "kotlin"
15
40
  else
16
41
  echo "java"
17
42
  fi
18
- elif [[ -f "pom.xml" ]]; then
43
+ elif [ -f "pom.xml" ]; then
19
44
  echo "java"
20
- elif [[ -f "pubspec.yaml" ]]; then
21
- if grep -q "flutter:" "pubspec.yaml" 2>/dev/null || [[ -f ".metadata" ]]; then
45
+ elif [ -f "pubspec.yaml" ]; then
46
+ if grep -q "flutter:" "pubspec.yaml" 2>/dev/null || [ -f ".metadata" ]; then
22
47
  echo "flutter"
23
48
  else
24
49
  echo "dart"
25
50
  fi
26
- elif [[ -n "$(find . -name "*.ps1" -type f | head -n 1)" ]]; then
51
+ elif [ -n "$(find . -name "*.ps1" -type f | sed -n '1p; 1q')" ]; then
27
52
  echo "powershell"
28
- elif [[ -f "Package.swift" ]]; then
53
+ elif [ -f "Package.swift" ]; then
29
54
  echo "swift"
30
- elif [[ -f "CMakeLists.txt" ]] || [[ -n "$(find . -name "*.cpp" -o -name "*.cc" -type f | head -n 1)" ]]; then
55
+ elif [ -f "CMakeLists.txt" ] || [ -n "$(find . -name "*.cpp" -o -name "*.cc" -type f | sed -n '1p; 1q')" ]; then
31
56
  echo "cpp"
32
- elif [[ -n "$(find . -name "*.m" -o -name "*.mm" -type f | head -n 1)" ]]; then
57
+ elif [ -n "$(find . -name "*.m" -o -name "*.mm" -type f | sed -n '1p; 1q')" ]; then
33
58
  echo "objectivec"
34
- elif [[ -n "$(find . -name "*.sh" -type f | head -n 1)" ]] || [[ -n "$(find . -name "Dockerfile" -o -name "*.dockerfile" -type f | head -n 1)" ]]; then
59
+ elif [ -n "$(find . -name "*.sh" -type f | sed -n '1p; 1q')" ]; then
35
60
  echo "shell"
36
- elif [[ -n "$(find . -name "*.ps1" -type f -not -path "./.git/*" | head -n 1)" ]]; then
61
+ elif [ -n "$(find . -name "*.ps1" -type f -not -path "./.git/*" | sed -n '1p; 1q')" ]; then
37
62
  echo "powershell"
38
63
  else
39
- if [[ -n "$(find . -name "*.ts" -o -name "*.tsx" -type f | head -n 1)" ]]; then
64
+ if [ -n "$(find . -name "*.ts" -o -name "*.tsx" -type f | sed -n '1p; 1q')" ]; then
40
65
  echo "typescript"
41
- elif [[ -n "$(find . -name "*.py" -type f | head -n 1)" ]]; then
66
+ elif [ -n "$(find . -name "*.py" -type f | sed -n '1p; 1q')" ]; then
42
67
  echo "python"
43
- elif [[ -n "$(find . -name "*.go" -type f | head -n 1)" ]]; then
68
+ elif [ -n "$(find . -name "*.go" -type f | sed -n '1p; 1q')" ]; then
44
69
  echo "go"
45
- elif [[ -n "$(find . -name "*.kt" -type f | head -n 1)" ]]; then
70
+ elif [ -n "$(find . -name "*.kt" -type f | sed -n '1p; 1q')" ]; then
46
71
  echo "kotlin"
47
- elif [[ -n "$(find . -name "*.java" -type f | head -n 1)" ]]; then
72
+ elif [ -n "$(find . -name "*.java" -type f | sed -n '1p; 1q')" ]; then
48
73
  echo "java"
49
- elif [[ -n "$(find . -name "*.dart" -type f | head -n 1)" ]]; then
50
- if grep -q "flutter:" "pubspec.yaml" 2>/dev/null || [[ -f ".flutter" ]]; then
74
+ elif [ -n "$(find . -name "*.dart" -type f | sed -n '1p; 1q')" ]; then
75
+ if grep -q "flutter:" "pubspec.yaml" 2>/dev/null || [ -f ".flutter" ]; then
51
76
  echo "flutter"
52
77
  else
53
78
  echo "dart"
54
79
  fi
55
- elif [[ -n "$(find . -name "*.swift" -type f | head -n 1)" ]]; then
80
+ elif [ -n "$(find . -name "*.swift" -type f | sed -n '1p; 1q')" ]; then
56
81
  echo "swift"
57
- elif [[ -n "$(find . -name "*.cpp" -o -name "*.cc" -o -name "*.c" -o -name "*.h" -type f | head -n 1)" ]]; then
82
+ elif [ -n "$(find . -name "*.cpp" -o -name "*.cc" -o -name "*.c" -o -name "*.h" -type f | sed -n '1p; 1q')" ]; then
58
83
  echo "cpp"
59
- elif [[ -n "$(find . -name "*.m" -o -name "*.mm" -type f | head -n 1)" ]]; then
84
+ elif [ -n "$(find . -name "*.m" -o -name "*.mm" -type f | sed -n '1p; 1q')" ]; then
60
85
  echo "objectivec"
61
- elif [[ -n "$(find . -name "*.sh" -type f | head -n 1)" ]]; then
86
+ elif [ -n "$(find . -name "*.sh" -type f | sed -n '1p; 1q')" ]; then
62
87
  echo "shell"
63
- elif [[ -n "$(find . -name "*.ps1" -type f -not -path "./.git/*" | head -n 1)" ]]; then
88
+ elif [ -n "$(find . -name "*.ps1" -type f -not -path "./.git/*" | sed -n '1p; 1q')" ]; then
64
89
  echo "powershell"
65
90
  else
66
91
  echo "unknown"
@@ -73,23 +98,22 @@ route_to_adapter() {
73
98
  local lang
74
99
  lang=$(detect_project_lang)
75
100
 
76
- # Source the appropriate adapter
77
- if [[ -f "githooks/adapters/${lang}.sh" ]]; then
78
- # shellcheck source=githooks/adapters/"${lang}".sh
79
- source "githooks/adapters/${lang}.sh"
80
-
81
- # Execute the requested action
82
- case "$action" in
83
- "static_analysis") run_static_analysis ;;
84
- "lint") run_lint ;;
85
- "tests") run_tests ;;
86
- "coverage") run_coverage ;;
87
- *) return 1 ;;
88
- esac
89
- elif [[ -f "./githooks/adapters/${lang}.sh" ]]; then
90
- # Alternative: source with ./ prefix
91
- # shellcheck source=./githooks/adapters/"${lang}".sh
92
- source "./githooks/adapters/${lang}.sh"
101
+ local adapter_file=""
102
+
103
+ # 3-tier resolution matching pre-commit ADAPTER_DIR logic
104
+ # Tier 1: Global flat layout (~/.config/xp-gate/adapters/lang.sh)
105
+ if [ -f "$HOME/.config/xp-gate/adapters/${lang}.sh" ]; then
106
+ adapter_file="$HOME/.config/xp-gate/adapters/${lang}.sh"
107
+ # Tier 2: Project nested layout (githooks/adapters/lang.sh)
108
+ elif [ -f "githooks/adapters/${lang}.sh" ]; then
109
+ adapter_file="githooks/adapters/${lang}.sh"
110
+ elif [ -f "./githooks/adapters/${lang}.sh" ]; then
111
+ adapter_file="./githooks/adapters/${lang}.sh"
112
+ fi
113
+
114
+ if [ -n "$adapter_file" ]; then
115
+ # shellcheck source=./githooks/adapters/lang.sh
116
+ source "$adapter_file"
93
117
 
94
118
  # Execute the requested action
95
119
  case "$action" in
@@ -100,7 +124,7 @@ route_to_adapter() {
100
124
  *) return 1 ;;
101
125
  esac
102
126
  else
103
- echo "No adapter found for language: $lang"
127
+ echo "No adapter found for language: $lang (searched: ~/.config/xp-gate/adapters/, githooks/adapters/)"
104
128
  return 1
105
129
  fi
106
130
  }
@@ -130,7 +154,7 @@ require_tool() {
130
154
  fi
131
155
 
132
156
  echo "❌ BLOCKED - Required tool '$tool_name' not available for $gate_name"
133
- if [[ -n "$install_hint" ]]; then
157
+ if [ -n "$install_hint" ]; then
134
158
  echo " Install: $install_hint"
135
159
  fi
136
160
  echo " Per QUALITY-GATES-CODE-OF-CONDUCT.md: tool unavailable = BLOCK, not SKIP"
@@ -142,20 +166,20 @@ detect_iac_project() {
142
166
  local has_iac=false
143
167
 
144
168
  # Check for Terraform files
145
- if [[ -n "$(find . -maxdepth 2 -name "*.tf" -not -path "./.git/*" 2>/dev/null | head -1)" ]]; then
169
+ if [ -n "$(find . -maxdepth 2 -name "*.tf" -not -path "./.git/*" 2>/dev/null | sed -n '1p; 1q')" ]; then
146
170
  has_iac=true
147
171
  fi
148
172
 
149
173
  # Check for Kubernetes manifests (YAML with apiVersion/kind)
150
- if [[ -n "$(find . -maxdepth 2 \( -name "*.yaml" -o -name "*.yml" \) -not -path "./.git/*" 2>/dev/null | head -1)" ]]; then
151
- local yaml_file=$(find . -maxdepth 2 \( -name "*.yaml" -o -name "*.yml" \) -not -path "./.git/*" 2>/dev/null | head -1)
174
+ if [ -n "$(find . -maxdepth 2 \( -name "*.yaml" -o -name "*.yml" \) -not -path "./.git/*" 2>/dev/null | sed -n '1p; 1q')" ]; then
175
+ local yaml_file=$(find . -maxdepth 2 \( -name "*.yaml" -o -name "*.yml" \) -not -path "./.git/*" 2>/dev/null | sed -n '1p; 1q')
152
176
  if grep -qE "^(apiVersion|kind):" "$yaml_file" 2>/dev/null; then
153
177
  has_iac=true
154
178
  fi
155
179
  fi
156
180
 
157
181
  # Check for Dockerfiles
158
- if [[ -n "$(find . -maxdepth 2 -name "Dockerfile" -o -name "*.dockerfile" -not -path "./.git/*" 2>/dev/null | head -1)" ]]; then
182
+ if [ -n "$(find . -maxdepth 2 -name "Dockerfile" -o -name "*.dockerfile" -not -path "./.git/*" 2>/dev/null | sed -n '1p; 1q')" ]; then
159
183
  has_iac=true
160
184
  fi
161
185
 
@@ -168,9 +192,9 @@ detect_iac_project() {
168
192
 
169
193
  # Stryker 9.x config files (new format, takes priority)
170
194
  detect_mutation_testable() {
171
- if [[ -f "stryker.config.mjs" ]] || [[ -f "stryker.config.js" ]] || \
172
- [[ -f "stryker.config.cjs" ]] || [[ -f "stryker.config.json" ]]; then
173
- if [[ -f "package.json" ]] && grep -qE '"@stryker-mutator[^"]*"' package.json 2>/dev/null; then
195
+ if [ -f "stryker.config.mjs" ] || [ -f "stryker.config.js" ] || \
196
+ [ -f "stryker.config.cjs" ] || [ -f "stryker.config.json" ]; then
197
+ if [ -f "package.json" ] && grep -qE '"@stryker-mutator[^"]*"' package.json 2>/dev/null; then
174
198
  return 0
175
199
  fi
176
200
  if command -v npx >/dev/null 2>&1 && npx --no-install stryker --version >/dev/null 2>&1; then
@@ -179,8 +203,8 @@ detect_mutation_testable() {
179
203
  fi
180
204
 
181
205
  # Legacy Stryker config files (backwards compatibility)
182
- if [[ -f "stryker.conf.json" ]] || [[ -f "stryker.prepush.conf.json" ]]; then
183
- if [[ -f "package.json" ]] && grep -qE '"@stryker-mutator[^"]*"' package.json 2>/dev/null; then
206
+ if [ -f "stryker.conf.json" ] || [ -f "stryker.prepush.conf.json" ]; then
207
+ if [ -f "package.json" ] && grep -qE '"@stryker-mutator[^"]*"' package.json 2>/dev/null; then
184
208
  return 0
185
209
  fi
186
210
  if command -v npx >/dev/null 2>&1 && npx --no-install stryker --version >/dev/null 2>&1; then
@@ -189,4 +213,60 @@ detect_mutation_testable() {
189
213
  fi
190
214
 
191
215
  return 1
192
- }
216
+ }
217
+ # Detect if a Python project has mutmut installed and configured
218
+ detect_python_mutation_testable() {
219
+ # Check for mutmut installation
220
+ if command -v mutmut >/dev/null 2>&1; then
221
+ return 0
222
+ fi
223
+ # Check for pip-installed mutmut in current venv
224
+ if command -v pip >/dev/null 2>&1 || command -v pip3 >/dev/null 2>&1; then
225
+ if pip show mutmut >/dev/null 2>&1; then
226
+ return 0
227
+ fi
228
+ if pip3 show mutmut >/dev/null 2>&1; then
229
+ return 0
230
+ fi
231
+ fi
232
+ return 1
233
+ }
234
+ # Detect if a Go project has gomutants installed and configured
235
+ detect_go_mutation_testable() {
236
+ # Check for gomutants CLI
237
+ if command -v gomutants >/dev/null 2>&1; then
238
+ return 0
239
+ fi
240
+ # Check GOPATH/bin/gomutants if go.mod exists (default GOPATH via go env when unset)
241
+ if [ -f "go.mod" ]; then
242
+ local go_gopath="${GOPATH:-$(go env GOPATH 2>/dev/null)}"
243
+ if [ -n "$go_gopath" ] && [ -x "$go_gopath/bin/gomutants" ]; then
244
+ return 0
245
+ fi
246
+ fi
247
+ return 1
248
+ }
249
+ # Detect if a Java/Kotlin project has PITest (pitest-maven or pitest-gradle) configured
250
+ detect_pitest_testable() {
251
+ # Check for Maven + pitest-maven plugin
252
+ if command -v mvn >/dev/null 2>&1 && [ -f "pom.xml" ]; then
253
+ if grep -q "pitest-maven" pom.xml 2>/dev/null; then
254
+ return 0
255
+ fi
256
+ fi
257
+ # Check for Gradle + pitest plugin
258
+ if [ -f "build.gradle" ] || [ -f "build.gradle.kts" ]; then
259
+ local gradle_cmd=""
260
+ if [ -f "gradlew" ] && [ -x "gradlew" ]; then
261
+ gradle_cmd="./gradlew"
262
+ elif command -v gradle >/dev/null 2>&1; then
263
+ gradle_cmd="gradle"
264
+ fi
265
+ if [ -n "$gradle_cmd" ]; then
266
+ if grep -qE "info\.solidsoft\.pitest|info\.solidsoft\.gradle\.pitest" build.gradle build.gradle.kts 2>/dev/null; then
267
+ return 0
268
+ fi
269
+ fi
270
+ fi
271
+ return 1
272
+ }
@@ -2,65 +2,90 @@
2
2
 
3
3
  # Common adapter functions for language detection and routing
4
4
 
5
+ # OS detection: returns linux/macos/windows/unknown
6
+ # Uses uname -s (POSIX) as primary, ${OSTYPE-} as fallback
7
+ detect_os_env() {
8
+ local os
9
+ os=$(uname -s 2>/dev/null || echo "unknown")
10
+ case "$os" in
11
+ Linux*) echo "linux";;
12
+ Darwin*) echo "macos";;
13
+ MINGW*|MSYS*|CYGWIN*) echo "windows";;
14
+ *)
15
+ # Fallback: OSTYPE (bash built-in, not always available)
16
+ if [ -n "${OSTYPE-}" ]; then
17
+ case "${OSTYPE-}" in
18
+ linux*) echo "linux";;
19
+ darwin*) echo "macos";;
20
+ msys*|cygwin*) echo "windows";;
21
+ *) echo "unknown";;
22
+ esac
23
+ else
24
+ echo "unknown"
25
+ fi
26
+ ;;
27
+ esac
28
+ }
29
+
5
30
  detect_project_lang() {
6
- if [[ -f "tsconfig.json" ]]; then
31
+ if [ -f "tsconfig.json" ]; then
7
32
  echo "typescript"
8
- elif [[ -f "pyproject.toml" ]] || [[ -f "requirements.txt" ]] || [[ -f "setup.py" ]]; then
33
+ elif [ -f "pyproject.toml" ] || [ -f "requirements.txt" ] || [ -f "setup.py" ]; then
9
34
  echo "python"
10
- elif [[ -f "go.mod" ]]; then
35
+ elif [ -f "go.mod" ]; then
11
36
  echo "go"
12
- elif [[ -f "build.gradle" ]] || [[ -f "build.gradle.kts" ]]; then
13
- if [[ -n "$(find . -name "*.kt" -type f | head -n 1)" ]]; then
37
+ elif [ -f "build.gradle" ] || [ -f "build.gradle.kts" ]; then
38
+ if [ -n "$(find . -name "*.kt" -type f | sed -n '1p; 1q')" ]; then
14
39
  echo "kotlin"
15
40
  else
16
41
  echo "java"
17
42
  fi
18
- elif [[ -f "pom.xml" ]]; then
43
+ elif [ -f "pom.xml" ]; then
19
44
  echo "java"
20
- elif [[ -f "pubspec.yaml" ]]; then
21
- if grep -q "flutter:" "pubspec.yaml" 2>/dev/null || [[ -f ".metadata" ]]; then
45
+ elif [ -f "pubspec.yaml" ]; then
46
+ if grep -q "flutter:" "pubspec.yaml" 2>/dev/null || [ -f ".metadata" ]; then
22
47
  echo "flutter"
23
48
  else
24
49
  echo "dart"
25
50
  fi
26
- elif [[ -n "$(find . -name "*.ps1" -type f | head -n 1)" ]]; then
51
+ elif [ -n "$(find . -name "*.ps1" -type f | sed -n '1p; 1q')" ]; then
27
52
  echo "powershell"
28
- elif [[ -f "Package.swift" ]]; then
53
+ elif [ -f "Package.swift" ]; then
29
54
  echo "swift"
30
- elif [[ -f "CMakeLists.txt" ]] || [[ -n "$(find . -name "*.cpp" -o -name "*.cc" -type f | head -n 1)" ]]; then
55
+ elif [ -f "CMakeLists.txt" ] || [ -n "$(find . -name "*.cpp" -o -name "*.cc" -type f | sed -n '1p; 1q')" ]; then
31
56
  echo "cpp"
32
- elif [[ -n "$(find . -name "*.m" -o -name "*.mm" -type f | head -n 1)" ]]; then
57
+ elif [ -n "$(find . -name "*.m" -o -name "*.mm" -type f | sed -n '1p; 1q')" ]; then
33
58
  echo "objectivec"
34
- elif [[ -n "$(find . -name "*.sh" -type f | head -n 1)" ]] || [[ -n "$(find . -name "Dockerfile" -o -name "*.dockerfile" -type f | head -n 1)" ]]; then
59
+ elif [ -n "$(find . -name "*.sh" -type f | sed -n '1p; 1q')" ]; then
35
60
  echo "shell"
36
- elif [[ -n "$(find . -name "*.ps1" -type f -not -path "./.git/*" | head -n 1)" ]]; then
61
+ elif [ -n "$(find . -name "*.ps1" -type f -not -path "./.git/*" | sed -n '1p; 1q')" ]; then
37
62
  echo "powershell"
38
63
  else
39
- if [[ -n "$(find . -name "*.ts" -o -name "*.tsx" -type f | head -n 1)" ]]; then
64
+ if [ -n "$(find . -name "*.ts" -o -name "*.tsx" -type f | sed -n '1p; 1q')" ]; then
40
65
  echo "typescript"
41
- elif [[ -n "$(find . -name "*.py" -type f | head -n 1)" ]]; then
66
+ elif [ -n "$(find . -name "*.py" -type f | sed -n '1p; 1q')" ]; then
42
67
  echo "python"
43
- elif [[ -n "$(find . -name "*.go" -type f | head -n 1)" ]]; then
68
+ elif [ -n "$(find . -name "*.go" -type f | sed -n '1p; 1q')" ]; then
44
69
  echo "go"
45
- elif [[ -n "$(find . -name "*.kt" -type f | head -n 1)" ]]; then
70
+ elif [ -n "$(find . -name "*.kt" -type f | sed -n '1p; 1q')" ]; then
46
71
  echo "kotlin"
47
- elif [[ -n "$(find . -name "*.java" -type f | head -n 1)" ]]; then
72
+ elif [ -n "$(find . -name "*.java" -type f | sed -n '1p; 1q')" ]; then
48
73
  echo "java"
49
- elif [[ -n "$(find . -name "*.dart" -type f | head -n 1)" ]]; then
50
- if grep -q "flutter:" "pubspec.yaml" 2>/dev/null || [[ -f ".flutter" ]]; then
74
+ elif [ -n "$(find . -name "*.dart" -type f | sed -n '1p; 1q')" ]; then
75
+ if grep -q "flutter:" "pubspec.yaml" 2>/dev/null || [ -f ".flutter" ]; then
51
76
  echo "flutter"
52
77
  else
53
78
  echo "dart"
54
79
  fi
55
- elif [[ -n "$(find . -name "*.swift" -type f | head -n 1)" ]]; then
80
+ elif [ -n "$(find . -name "*.swift" -type f | sed -n '1p; 1q')" ]; then
56
81
  echo "swift"
57
- elif [[ -n "$(find . -name "*.cpp" -o -name "*.cc" -o -name "*.c" -o -name "*.h" -type f | head -n 1)" ]]; then
82
+ elif [ -n "$(find . -name "*.cpp" -o -name "*.cc" -o -name "*.c" -o -name "*.h" -type f | sed -n '1p; 1q')" ]; then
58
83
  echo "cpp"
59
- elif [[ -n "$(find . -name "*.m" -o -name "*.mm" -type f | head -n 1)" ]]; then
84
+ elif [ -n "$(find . -name "*.m" -o -name "*.mm" -type f | sed -n '1p; 1q')" ]; then
60
85
  echo "objectivec"
61
- elif [[ -n "$(find . -name "*.sh" -type f | head -n 1)" ]]; then
86
+ elif [ -n "$(find . -name "*.sh" -type f | sed -n '1p; 1q')" ]; then
62
87
  echo "shell"
63
- elif [[ -n "$(find . -name "*.ps1" -type f -not -path "./.git/*" | head -n 1)" ]]; then
88
+ elif [ -n "$(find . -name "*.ps1" -type f -not -path "./.git/*" | sed -n '1p; 1q')" ]; then
64
89
  echo "powershell"
65
90
  else
66
91
  echo "unknown"
@@ -73,23 +98,22 @@ route_to_adapter() {
73
98
  local lang
74
99
  lang=$(detect_project_lang)
75
100
 
76
- # Source the appropriate adapter
77
- if [[ -f "githooks/adapters/${lang}.sh" ]]; then
78
- # shellcheck source=githooks/adapters/"${lang}".sh
79
- source "githooks/adapters/${lang}.sh"
80
-
81
- # Execute the requested action
82
- case "$action" in
83
- "static_analysis") run_static_analysis ;;
84
- "lint") run_lint ;;
85
- "tests") run_tests ;;
86
- "coverage") run_coverage ;;
87
- *) return 1 ;;
88
- esac
89
- elif [[ -f "./githooks/adapters/${lang}.sh" ]]; then
90
- # Alternative: source with ./ prefix
91
- # shellcheck source=./githooks/adapters/"${lang}".sh
92
- source "./githooks/adapters/${lang}.sh"
101
+ local adapter_file=""
102
+
103
+ # 3-tier resolution matching pre-commit ADAPTER_DIR logic
104
+ # Tier 1: Global flat layout (~/.config/xp-gate/adapters/lang.sh)
105
+ if [ -f "$HOME/.config/xp-gate/adapters/${lang}.sh" ]; then
106
+ adapter_file="$HOME/.config/xp-gate/adapters/${lang}.sh"
107
+ # Tier 2: Project nested layout (githooks/adapters/lang.sh)
108
+ elif [ -f "githooks/adapters/${lang}.sh" ]; then
109
+ adapter_file="githooks/adapters/${lang}.sh"
110
+ elif [ -f "./githooks/adapters/${lang}.sh" ]; then
111
+ adapter_file="./githooks/adapters/${lang}.sh"
112
+ fi
113
+
114
+ if [ -n "$adapter_file" ]; then
115
+ # shellcheck source=./githooks/adapters/lang.sh
116
+ source "$adapter_file"
93
117
 
94
118
  # Execute the requested action
95
119
  case "$action" in
@@ -100,7 +124,7 @@ route_to_adapter() {
100
124
  *) return 1 ;;
101
125
  esac
102
126
  else
103
- echo "No adapter found for language: $lang"
127
+ echo "No adapter found for language: $lang (searched: ~/.config/xp-gate/adapters/, githooks/adapters/)"
104
128
  return 1
105
129
  fi
106
130
  }
@@ -130,7 +154,7 @@ require_tool() {
130
154
  fi
131
155
 
132
156
  echo "❌ BLOCKED - Required tool '$tool_name' not available for $gate_name"
133
- if [[ -n "$install_hint" ]]; then
157
+ if [ -n "$install_hint" ]; then
134
158
  echo " Install: $install_hint"
135
159
  fi
136
160
  echo " Per QUALITY-GATES-CODE-OF-CONDUCT.md: tool unavailable = BLOCK, not SKIP"
@@ -142,20 +166,20 @@ detect_iac_project() {
142
166
  local has_iac=false
143
167
 
144
168
  # Check for Terraform files
145
- if [[ -n "$(find . -maxdepth 2 -name "*.tf" -not -path "./.git/*" 2>/dev/null | head -1)" ]]; then
169
+ if [ -n "$(find . -maxdepth 2 -name "*.tf" -not -path "./.git/*" 2>/dev/null | sed -n '1p; 1q')" ]; then
146
170
  has_iac=true
147
171
  fi
148
172
 
149
173
  # Check for Kubernetes manifests (YAML with apiVersion/kind)
150
- if [[ -n "$(find . -maxdepth 2 \( -name "*.yaml" -o -name "*.yml" \) -not -path "./.git/*" 2>/dev/null | head -1)" ]]; then
151
- local yaml_file=$(find . -maxdepth 2 \( -name "*.yaml" -o -name "*.yml" \) -not -path "./.git/*" 2>/dev/null | head -1)
174
+ if [ -n "$(find . -maxdepth 2 \( -name "*.yaml" -o -name "*.yml" \) -not -path "./.git/*" 2>/dev/null | sed -n '1p; 1q')" ]; then
175
+ local yaml_file=$(find . -maxdepth 2 \( -name "*.yaml" -o -name "*.yml" \) -not -path "./.git/*" 2>/dev/null | sed -n '1p; 1q')
152
176
  if grep -qE "^(apiVersion|kind):" "$yaml_file" 2>/dev/null; then
153
177
  has_iac=true
154
178
  fi
155
179
  fi
156
180
 
157
181
  # Check for Dockerfiles
158
- if [[ -n "$(find . -maxdepth 2 -name "Dockerfile" -o -name "*.dockerfile" -not -path "./.git/*" 2>/dev/null | head -1)" ]]; then
182
+ if [ -n "$(find . -maxdepth 2 -name "Dockerfile" -o -name "*.dockerfile" -not -path "./.git/*" 2>/dev/null | sed -n '1p; 1q')" ]; then
159
183
  has_iac=true
160
184
  fi
161
185
 
@@ -168,9 +192,9 @@ detect_iac_project() {
168
192
 
169
193
  # Stryker 9.x config files (new format, takes priority)
170
194
  detect_mutation_testable() {
171
- if [[ -f "stryker.config.mjs" ]] || [[ -f "stryker.config.js" ]] || \
172
- [[ -f "stryker.config.cjs" ]] || [[ -f "stryker.config.json" ]]; then
173
- if [[ -f "package.json" ]] && grep -qE '"@stryker-mutator[^"]*"' package.json 2>/dev/null; then
195
+ if [ -f "stryker.config.mjs" ] || [ -f "stryker.config.js" ] || \
196
+ [ -f "stryker.config.cjs" ] || [ -f "stryker.config.json" ]; then
197
+ if [ -f "package.json" ] && grep -qE '"@stryker-mutator[^"]*"' package.json 2>/dev/null; then
174
198
  return 0
175
199
  fi
176
200
  if command -v npx >/dev/null 2>&1 && npx --no-install stryker --version >/dev/null 2>&1; then
@@ -179,8 +203,8 @@ detect_mutation_testable() {
179
203
  fi
180
204
 
181
205
  # Legacy Stryker config files (backwards compatibility)
182
- if [[ -f "stryker.conf.json" ]] || [[ -f "stryker.prepush.conf.json" ]]; then
183
- if [[ -f "package.json" ]] && grep -qE '"@stryker-mutator[^"]*"' package.json 2>/dev/null; then
206
+ if [ -f "stryker.conf.json" ] || [ -f "stryker.prepush.conf.json" ]; then
207
+ if [ -f "package.json" ] && grep -qE '"@stryker-mutator[^"]*"' package.json 2>/dev/null; then
184
208
  return 0
185
209
  fi
186
210
  if command -v npx >/dev/null 2>&1 && npx --no-install stryker --version >/dev/null 2>&1; then
@@ -189,4 +213,60 @@ detect_mutation_testable() {
189
213
  fi
190
214
 
191
215
  return 1
192
- }
216
+ }
217
+ # Detect if a Python project has mutmut installed and configured
218
+ detect_python_mutation_testable() {
219
+ # Check for mutmut installation
220
+ if command -v mutmut >/dev/null 2>&1; then
221
+ return 0
222
+ fi
223
+ # Check for pip-installed mutmut in current venv
224
+ if command -v pip >/dev/null 2>&1 || command -v pip3 >/dev/null 2>&1; then
225
+ if pip show mutmut >/dev/null 2>&1; then
226
+ return 0
227
+ fi
228
+ if pip3 show mutmut >/dev/null 2>&1; then
229
+ return 0
230
+ fi
231
+ fi
232
+ return 1
233
+ }
234
+ # Detect if a Go project has gomutants installed and configured
235
+ detect_go_mutation_testable() {
236
+ # Check for gomutants CLI
237
+ if command -v gomutants >/dev/null 2>&1; then
238
+ return 0
239
+ fi
240
+ # Check GOPATH/bin/gomutants if go.mod exists (default GOPATH via go env when unset)
241
+ if [ -f "go.mod" ]; then
242
+ local go_gopath="${GOPATH:-$(go env GOPATH 2>/dev/null)}"
243
+ if [ -n "$go_gopath" ] && [ -x "$go_gopath/bin/gomutants" ]; then
244
+ return 0
245
+ fi
246
+ fi
247
+ return 1
248
+ }
249
+ # Detect if a Java/Kotlin project has PITest (pitest-maven or pitest-gradle) configured
250
+ detect_pitest_testable() {
251
+ # Check for Maven + pitest-maven plugin
252
+ if command -v mvn >/dev/null 2>&1 && [ -f "pom.xml" ]; then
253
+ if grep -q "pitest-maven" pom.xml 2>/dev/null; then
254
+ return 0
255
+ fi
256
+ fi
257
+ # Check for Gradle + pitest plugin
258
+ if [ -f "build.gradle" ] || [ -f "build.gradle.kts" ]; then
259
+ local gradle_cmd=""
260
+ if [ -f "gradlew" ] && [ -x "gradlew" ]; then
261
+ gradle_cmd="./gradlew"
262
+ elif command -v gradle >/dev/null 2>&1; then
263
+ gradle_cmd="gradle"
264
+ fi
265
+ if [ -n "$gradle_cmd" ]; then
266
+ if grep -qE "info\.solidsoft\.pitest|info\.solidsoft\.gradle\.pitest" build.gradle build.gradle.kts 2>/dev/null; then
267
+ return 0
268
+ fi
269
+ fi
270
+ fi
271
+ return 1
272
+ }
@@ -1,9 +1,9 @@
1
1
  # SRC/MOCK-POLICY KNOWLEDGE BASE
2
2
 
3
- **Generated:** 2026-06-30
4
- **Commit:** f1117d4
3
+ **Generated:** 2026-07-01
4
+ **Commit:** bd129b3
5
5
  **Branch:** main
6
- **Version:** 0.11.3.0
6
+ **Version:** 0.12.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-30
4
- **Commit:** f1117d4
3
+ **Generated:** 2026-07-01
4
+ **Commit:** bd129b3
5
5
  **Branch:** main
6
- **Version:** 0.11.3.0
6
+ **Version:** 0.12.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.