@boyingliu01/xp-gate 0.18.5 → 0.19.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 (254) hide show
  1. package/adapter-common.sh +27 -0
  2. package/adapters/gate-3.sh +0 -0
  3. package/adapters/gate-4.sh +0 -0
  4. package/adapters/gate-7.sh +0 -0
  5. package/adapters/gate-8.sh +0 -0
  6. package/adapters/gate-9.sh +0 -0
  7. package/adapters/powershell.sh +85 -33
  8. package/adapters/typescript.sh +8 -8
  9. package/build-integrity/__tests__/import-resolver.test.ts +22 -0
  10. package/build-integrity/__tests__/orchestrator.test.ts +1 -2
  11. package/build-integrity/gate-10.ts +7 -0
  12. package/gate-12-file-hygiene.sh +230 -0
  13. package/hooks/adapter-common.sh +27 -0
  14. package/hooks/gate-10.sh +105 -0
  15. package/hooks/gate-12-file-hygiene.sh +0 -0
  16. package/hooks/lib/validate-code-walkthrough.cjs +125 -0
  17. package/hooks/pre-commit +130 -33
  18. package/hooks/pre-push +15 -91
  19. package/lib/__tests__/check-version.test.js +67 -46
  20. package/lib/__tests__/doctor.test.js +7 -6
  21. package/lib/__tests__/gate-runner.test.js +267 -0
  22. package/lib/__tests__/init.test.js +32 -0
  23. package/lib/__tests__/npm-package.test.js +13 -0
  24. package/lib/__tests__/phase-transition.test.js +604 -12
  25. package/lib/__tests__/sprint-discovery.test.ts +0 -1
  26. package/lib/__tests__/sprint-status-rework.test.js +100 -1
  27. package/lib/__tests__/update-hooks.test.js +143 -10
  28. package/lib/__tests__/upgrade-exec.test.js +73 -45
  29. package/lib/__tests__/upgrade-skill-sync.test.js +1 -1
  30. package/lib/__tests__/upgrade.test.js +12 -11
  31. package/lib/doctor.js +2 -1
  32. package/lib/gate-runner.js +68 -23
  33. package/lib/init.js +34 -5
  34. package/lib/install-skill.js +1 -0
  35. package/lib/mattpocock-license-verdict.json +1 -1
  36. package/lib/phase-transition.js +203 -23
  37. package/lib/shared-paths.js +2 -0
  38. package/lib/sprint-status.js +15 -4
  39. package/lib/test-alignment.ts +3 -13
  40. package/lib/update-hooks.js +129 -15
  41. package/mock-policy/AGENTS.md +5 -5
  42. package/mock-policy/__tests__/gate-m3.test.ts +0 -5
  43. package/mock-policy/__tests__/scope-scanner.test.ts +8 -2
  44. package/mutation/AGENTS.md +4 -4
  45. package/mutation/__tests__/mutmut-runner.test.ts +443 -2
  46. package/mutation/__tests__/update-baseline.test.ts +0 -2
  47. package/mutation/runners/mutmut-runner.ts +67 -17
  48. package/package.json +9 -3
  49. package/plugins/claude-code/.claude-plugin/plugin.json +1 -1
  50. package/plugins/claude-code/skills/clipboard-vision/SKILL.md +47 -0
  51. package/plugins/claude-code/skills/clipboard-vision/__tests__/clipboard-vision-distribution.test.cjs +65 -0
  52. package/plugins/claude-code/skills/clipboard-vision/__tests__/clipboard-vision-security.test.cjs +185 -0
  53. package/plugins/claude-code/skills/clipboard-vision/__tests__/clipboard-vision.Tests.ps1 +138 -0
  54. package/plugins/claude-code/skills/clipboard-vision/__tests__/clipboard-vision.test.cjs +273 -0
  55. package/plugins/claude-code/skills/clipboard-vision/describe-clipboard.ps1 +115 -0
  56. package/plugins/claude-code/skills/clipboard-vision/describe-clipboard.sh +161 -0
  57. package/plugins/claude-code/skills/clipboard-vision/scripts/clipboard-vision.ps1 +86 -0
  58. package/plugins/claude-code/skills/clipboard-vision/scripts/clipboard-vision.sh +137 -0
  59. package/plugins/claude-code/skills/delphi-review/.delphi-config.json.example +24 -27
  60. package/plugins/claude-code/skills/delphi-review/AGENTS.md +13 -12
  61. package/plugins/claude-code/skills/delphi-review/INSTALL.md +15 -17
  62. package/plugins/claude-code/skills/delphi-review/SKILL.md +30 -29
  63. package/plugins/claude-code/skills/delphi-review/opencode.json.delphi.example +6 -10
  64. package/plugins/claude-code/skills/delphi-review/references/code-walkthrough.md +33 -49
  65. package/plugins/claude-code/skills/delphi-review/references/requirements.md +66 -54
  66. package/plugins/claude-code/skills/delphi-review/references/round-templates.md +7 -7
  67. package/plugins/claude-code/skills/sprint-flow/AGENTS.md +5 -5
  68. package/plugins/claude-code/skills/sprint-flow/SKILL.md +4 -4
  69. package/plugins/claude-code/skills/sprint-flow/references/components/skill-invocations.md +2 -2
  70. package/plugins/claude-code/skills/sprint-flow/references/force-levels.md +37 -35
  71. package/plugins/claude-code/skills/sprint-flow/references/orchestration-rules.md +1 -1
  72. package/plugins/claude-code/skills/sprint-flow/references/phase-1-prep.md +1 -1
  73. package/plugins/claude-code/skills/sprint-flow/references/phase-2-design.md +24 -14
  74. package/plugins/claude-code/skills/sprint-flow/references/phase-4-verify.md +6 -6
  75. package/plugins/claude-code/skills/sprint-flow/references/phase-overview.md +2 -2
  76. package/plugins/claude-code/skills/test-specification-alignment/AGENTS.md +4 -4
  77. package/plugins/dsh/README.md +25 -0
  78. package/plugins/dsh/cordis.patch.yml +7 -0
  79. package/plugins/dsh/package.json +70 -0
  80. package/plugins/dsh/scripts/prepack.cjs +82 -0
  81. package/plugins/dsh/skills/admin-template-guidelines/SKILL.md +113 -0
  82. package/plugins/dsh/skills/admin-template-guidelines/references/rule-1.md +91 -0
  83. package/plugins/dsh/skills/admin-template-guidelines/references/rule-2.md +155 -0
  84. package/plugins/dsh/skills/admin-template-guidelines/references/rule-3.md +70 -0
  85. package/plugins/dsh/skills/admin-template-guidelines/references/rule-4.md +154 -0
  86. package/plugins/dsh/skills/admin-template-guidelines/references/rule-5.md +126 -0
  87. package/plugins/dsh/skills/admin-template-guidelines/references/rule-6.md +180 -0
  88. package/plugins/dsh/skills/batch-grill-me/SKILL.md +15 -0
  89. package/plugins/dsh/skills/clipboard-vision/SKILL.md +47 -0
  90. package/plugins/dsh/skills/clipboard-vision/__tests__/clipboard-vision-distribution.test.cjs +65 -0
  91. package/plugins/dsh/skills/clipboard-vision/__tests__/clipboard-vision-security.test.cjs +185 -0
  92. package/plugins/dsh/skills/clipboard-vision/__tests__/clipboard-vision.Tests.ps1 +138 -0
  93. package/plugins/dsh/skills/clipboard-vision/__tests__/clipboard-vision.test.cjs +273 -0
  94. package/plugins/dsh/skills/clipboard-vision/describe-clipboard.ps1 +115 -0
  95. package/plugins/dsh/skills/clipboard-vision/describe-clipboard.sh +161 -0
  96. package/plugins/dsh/skills/clipboard-vision/scripts/clipboard-vision.ps1 +86 -0
  97. package/plugins/dsh/skills/clipboard-vision/scripts/clipboard-vision.sh +137 -0
  98. package/plugins/dsh/skills/delphi-review/.delphi-config.json.example +70 -0
  99. package/plugins/dsh/skills/delphi-review/AGENTS.md +67 -0
  100. package/plugins/dsh/skills/delphi-review/INSTALL.md +150 -0
  101. package/plugins/dsh/skills/delphi-review/SKILL.md +559 -0
  102. package/plugins/dsh/skills/delphi-review/hooks.md +19 -0
  103. package/plugins/dsh/skills/delphi-review/opencode.json.delphi.example +52 -0
  104. package/plugins/dsh/skills/delphi-review/references/code-walkthrough.md +470 -0
  105. package/plugins/dsh/skills/delphi-review/references/orchestrator-dispatch.md +64 -0
  106. package/plugins/dsh/skills/delphi-review/references/requirements.md +402 -0
  107. package/plugins/dsh/skills/delphi-review/references/round-templates.md +134 -0
  108. package/plugins/dsh/skills/delphi-review/scripts/verify-consensus.sh +43 -0
  109. package/plugins/dsh/skills/delphi-review/tools.md +29 -0
  110. package/plugins/dsh/skills/domain-modeling/ADR-FORMAT.md +64 -0
  111. package/plugins/dsh/skills/domain-modeling/CONTEXT-FORMAT.md +60 -0
  112. package/plugins/dsh/skills/domain-modeling/SKILL.md +74 -0
  113. package/plugins/dsh/skills/grill-with-docs/SKILL.md +7 -0
  114. package/plugins/dsh/skills/grilling/SKILL.md +12 -0
  115. package/plugins/dsh/skills/improve-codebase-architecture/SKILL.md +156 -0
  116. package/plugins/dsh/skills/ralph-loop/SKILL.md +198 -0
  117. package/plugins/dsh/skills/ralph-loop/references/components.md +113 -0
  118. package/plugins/dsh/skills/ralph-loop/references/phase-2-build-ralph.md +89 -0
  119. package/plugins/dsh/skills/ralph-loop/references/workflow-details.md +99 -0
  120. package/plugins/dsh/skills/ralph-loop/templates/progress-log.md +36 -0
  121. package/plugins/dsh/skills/sprint-flow/AGENTS.md +103 -0
  122. package/plugins/dsh/skills/sprint-flow/SKILL.md +411 -0
  123. package/plugins/dsh/skills/sprint-flow/__tests__/sprint-flow.test.cjs +457 -0
  124. package/plugins/dsh/skills/sprint-flow/hooks.md +18 -0
  125. package/plugins/dsh/skills/sprint-flow/references/components/memory.md +87 -0
  126. package/plugins/dsh/skills/sprint-flow/references/components/middleware.md +75 -0
  127. package/plugins/dsh/skills/sprint-flow/references/components/skill-invocations.md +105 -0
  128. package/plugins/dsh/skills/sprint-flow/references/components/system-prompt.md +27 -0
  129. package/plugins/dsh/skills/sprint-flow/references/components/tool-descriptions.md +95 -0
  130. package/plugins/dsh/skills/sprint-flow/references/force-levels.md +204 -0
  131. package/plugins/dsh/skills/sprint-flow/references/orchestration-rules.md +236 -0
  132. package/plugins/dsh/skills/sprint-flow/references/phase-1-prep.md +294 -0
  133. package/plugins/dsh/skills/sprint-flow/references/phase-2-design.md +273 -0
  134. package/plugins/dsh/skills/sprint-flow/references/phase-3-build.md +348 -0
  135. package/plugins/dsh/skills/sprint-flow/references/phase-4-verify.md +225 -0
  136. package/plugins/dsh/skills/sprint-flow/references/phase-5-ship.md +222 -0
  137. package/plugins/dsh/skills/sprint-flow/references/phase-6-close.md +266 -0
  138. package/plugins/dsh/skills/sprint-flow/references/phase-overview.md +486 -0
  139. package/plugins/dsh/skills/sprint-flow/scripts/verify-sprint-state.sh +38 -0
  140. package/plugins/dsh/skills/sprint-flow/templates/auto-estimate-learning-log.md +136 -0
  141. package/plugins/dsh/skills/sprint-flow/templates/auto-estimate-output-template.md +110 -0
  142. package/plugins/dsh/skills/sprint-flow/templates/emergent-issues-template.md +120 -0
  143. package/plugins/dsh/skills/sprint-flow/templates/pain-document-template.md +115 -0
  144. package/plugins/dsh/skills/sprint-flow/templates/sprint-progress-template.md +140 -0
  145. package/plugins/dsh/skills/sprint-flow/templates/sprint-summary-template.md +121 -0
  146. package/plugins/dsh/skills/sprint-flow/tools.md +29 -0
  147. package/plugins/dsh/skills/test-driven-development/SKILL.md +371 -0
  148. package/plugins/dsh/skills/test-driven-development/testing-anti-patterns.md +299 -0
  149. package/plugins/dsh/skills/test-specification-alignment/AGENTS.md +56 -0
  150. package/plugins/dsh/skills/test-specification-alignment/SKILL.md +205 -0
  151. package/plugins/dsh/skills/test-specification-alignment/references/alignment-verification-algorithm.md +493 -0
  152. package/plugins/dsh/skills/test-specification-alignment/references/phase2-constraint-enforcement.md +431 -0
  153. package/plugins/dsh/skills/test-specification-alignment/references/specification-format.md +348 -0
  154. package/plugins/dsh/skills/to-issues/SKILL.md +277 -0
  155. package/plugins/dsh/src/command.test.ts +102 -0
  156. package/plugins/dsh/src/command.ts +75 -0
  157. package/plugins/dsh/src/contract.test.ts +62 -0
  158. package/plugins/dsh/src/gate-runner.test.ts +100 -0
  159. package/plugins/dsh/src/gate-runner.ts +58 -0
  160. package/plugins/dsh/src/index.test.ts +97 -0
  161. package/plugins/dsh/src/index.ts +94 -0
  162. package/plugins/dsh/src/skills.test.ts +58 -0
  163. package/plugins/dsh/tsconfig.build.json +13 -0
  164. package/plugins/dsh/tsconfig.json +19 -0
  165. package/plugins/dsh/vitest.config.ts +8 -0
  166. package/plugins/opencode/package.json +1 -1
  167. package/plugins/opencode/scripts/prepack.cjs +1 -0
  168. package/plugins/opencode/skills/clipboard-vision/SKILL.md +47 -0
  169. package/plugins/opencode/skills/clipboard-vision/__tests__/clipboard-vision-distribution.test.cjs +65 -0
  170. package/plugins/opencode/skills/clipboard-vision/__tests__/clipboard-vision-security.test.cjs +185 -0
  171. package/plugins/opencode/skills/clipboard-vision/__tests__/clipboard-vision.Tests.ps1 +138 -0
  172. package/plugins/opencode/skills/clipboard-vision/__tests__/clipboard-vision.test.cjs +273 -0
  173. package/plugins/opencode/skills/clipboard-vision/describe-clipboard.ps1 +115 -0
  174. package/plugins/opencode/skills/clipboard-vision/describe-clipboard.sh +161 -0
  175. package/plugins/opencode/skills/clipboard-vision/scripts/clipboard-vision.ps1 +86 -0
  176. package/plugins/opencode/skills/clipboard-vision/scripts/clipboard-vision.sh +137 -0
  177. package/plugins/opencode/skills/delphi-review/.delphi-config.json.example +24 -27
  178. package/plugins/opencode/skills/delphi-review/AGENTS.md +13 -12
  179. package/plugins/opencode/skills/delphi-review/INSTALL.md +15 -17
  180. package/plugins/opencode/skills/delphi-review/SKILL.md +30 -29
  181. package/plugins/opencode/skills/delphi-review/opencode.json.delphi.example +6 -10
  182. package/plugins/opencode/skills/delphi-review/references/code-walkthrough.md +33 -49
  183. package/plugins/opencode/skills/delphi-review/references/requirements.md +66 -54
  184. package/plugins/opencode/skills/delphi-review/references/round-templates.md +7 -7
  185. package/plugins/opencode/skills/sprint-flow/AGENTS.md +5 -5
  186. package/plugins/opencode/skills/sprint-flow/SKILL.md +4 -4
  187. package/plugins/opencode/skills/sprint-flow/references/components/skill-invocations.md +2 -2
  188. package/plugins/opencode/skills/sprint-flow/references/force-levels.md +37 -35
  189. package/plugins/opencode/skills/sprint-flow/references/orchestration-rules.md +1 -1
  190. package/plugins/opencode/skills/sprint-flow/references/phase-1-prep.md +1 -1
  191. package/plugins/opencode/skills/sprint-flow/references/phase-2-design.md +24 -14
  192. package/plugins/opencode/skills/sprint-flow/references/phase-4-verify.md +6 -6
  193. package/plugins/opencode/skills/sprint-flow/references/phase-overview.md +2 -2
  194. package/plugins/opencode/skills/test-specification-alignment/AGENTS.md +4 -4
  195. package/plugins/qoder/plugin.json +1 -1
  196. package/plugins/qoder/skills/clipboard-vision/SKILL.md +47 -0
  197. package/plugins/qoder/skills/clipboard-vision/__tests__/clipboard-vision-distribution.test.cjs +65 -0
  198. package/plugins/qoder/skills/clipboard-vision/__tests__/clipboard-vision-security.test.cjs +185 -0
  199. package/plugins/qoder/skills/clipboard-vision/__tests__/clipboard-vision.Tests.ps1 +138 -0
  200. package/plugins/qoder/skills/clipboard-vision/__tests__/clipboard-vision.test.cjs +273 -0
  201. package/plugins/qoder/skills/clipboard-vision/clipboard-vision/SKILL.md +47 -0
  202. package/plugins/qoder/skills/clipboard-vision/clipboard-vision/__tests__/clipboard-vision-distribution.test.cjs +65 -0
  203. package/plugins/qoder/skills/clipboard-vision/clipboard-vision/__tests__/clipboard-vision-security.test.cjs +185 -0
  204. package/plugins/qoder/skills/clipboard-vision/clipboard-vision/__tests__/clipboard-vision.Tests.ps1 +138 -0
  205. package/plugins/qoder/skills/clipboard-vision/clipboard-vision/__tests__/clipboard-vision.test.cjs +273 -0
  206. package/plugins/qoder/skills/clipboard-vision/clipboard-vision/scripts/clipboard-vision.ps1 +86 -0
  207. package/plugins/qoder/skills/clipboard-vision/clipboard-vision/scripts/clipboard-vision.sh +137 -0
  208. package/plugins/qoder/skills/clipboard-vision/scripts/clipboard-vision.ps1 +86 -0
  209. package/plugins/qoder/skills/clipboard-vision/scripts/clipboard-vision.sh +137 -0
  210. package/plugins/qoder/skills/delphi-review/.delphi-config.json.example +3 -11
  211. package/plugins/qoder/skills/delphi-review/AGENTS.md +15 -17
  212. package/plugins/qoder/skills/delphi-review/INSTALL.md +12 -12
  213. package/plugins/qoder/skills/delphi-review/SKILL.md +58 -25
  214. package/plugins/qoder/skills/delphi-review/opencode.json.delphi.example +1 -1
  215. package/plugins/qoder/skills/delphi-review/references/code-walkthrough.md +33 -49
  216. package/plugins/qoder/skills/sprint-flow/AGENTS.md +5 -5
  217. package/plugins/qoder/skills/sprint-flow/SKILL.md +2 -2
  218. package/plugins/qoder/skills/sprint-flow/references/force-levels.md +38 -35
  219. package/plugins/qoder/skills/sprint-flow/references/phase-1-prep.md +1 -1
  220. package/plugins/qoder/skills/sprint-flow/references/phase-2-design.md +48 -6
  221. package/plugins/qoder/skills/sprint-flow/references/phase-4-verify.md +5 -6
  222. package/plugins/qoder/skills/test-specification-alignment/AGENTS.md +4 -4
  223. package/principles/AGENTS.md +4 -4
  224. package/scripts/__tests__/sync-package-content.test.js +30 -0
  225. package/scripts/delphi-external-review.cjs +194 -63
  226. package/scripts/sync-package-content.js +9 -2
  227. package/skills/clipboard-vision/SKILL.md +47 -0
  228. package/skills/clipboard-vision/__tests__/clipboard-vision-distribution.test.cjs +65 -0
  229. package/skills/clipboard-vision/__tests__/clipboard-vision-security.test.cjs +185 -0
  230. package/skills/clipboard-vision/__tests__/clipboard-vision.Tests.ps1 +138 -0
  231. package/skills/clipboard-vision/__tests__/clipboard-vision.test.cjs +273 -0
  232. package/skills/clipboard-vision/describe-clipboard.ps1 +115 -0
  233. package/skills/clipboard-vision/describe-clipboard.sh +161 -0
  234. package/skills/clipboard-vision/scripts/clipboard-vision.ps1 +86 -0
  235. package/skills/clipboard-vision/scripts/clipboard-vision.sh +137 -0
  236. package/skills/delphi-review/.delphi-config.json.example +24 -27
  237. package/skills/delphi-review/AGENTS.md +13 -12
  238. package/skills/delphi-review/INSTALL.md +15 -17
  239. package/skills/delphi-review/SKILL.md +30 -29
  240. package/skills/delphi-review/opencode.json.delphi.example +6 -10
  241. package/skills/delphi-review/references/code-walkthrough.md +33 -49
  242. package/skills/delphi-review/references/requirements.md +66 -54
  243. package/skills/delphi-review/references/round-templates.md +7 -7
  244. package/skills/sprint-flow/AGENTS.md +5 -5
  245. package/skills/sprint-flow/SKILL.md +4 -4
  246. package/skills/sprint-flow/references/components/skill-invocations.md +2 -2
  247. package/skills/sprint-flow/references/force-levels.md +37 -35
  248. package/skills/sprint-flow/references/orchestration-rules.md +1 -1
  249. package/skills/sprint-flow/references/phase-1-prep.md +1 -1
  250. package/skills/sprint-flow/references/phase-2-design.md +24 -14
  251. package/skills/sprint-flow/references/phase-4-verify.md +6 -6
  252. package/skills/sprint-flow/references/phase-overview.md +2 -2
  253. package/skills/test-specification-alignment/AGENTS.md +4 -4
  254. package/sprint-gate.sh +291 -0
@@ -0,0 +1,105 @@
1
+ # shellcheck shell=bash
2
+ # ============================================================================
3
+ # GATE 10: Semgrep SAST Security Scan
4
+ # Detects security vulnerabilities (SQL injection, XSS, etc.) in staged files
5
+ # Tool: semgrep -- https://semgrep.dev
6
+ # ============================================================================
7
+
8
+ 2>&1 echo ""
9
+ 2>&1 echo "→ Gate 10: Semgrep SAST Security Scan..."
10
+ GATE_10_START=$(gate_start_ms)
11
+
12
+ GATE_10_STATUS=""
13
+
14
+ # Semgrep availability check
15
+ SEMGREP_CMD=""
16
+ if command -v semgrep >/dev/null 2>&1; then
17
+ SEMGREP_CMD="semgrep"
18
+ elif [ -f "$HOME/.local/bin/semgrep" ]; then
19
+ SEMGREP_CMD="$HOME/.local/bin/semgrep"
20
+ fi
21
+
22
+ if [ -z "$SEMGREP_CMD" ]; then
23
+ echo " ⚠️ WARN - semgrep not installed — SAST scanning unavailable"
24
+ echo " Install: brew install semgrep (macOS) | pip install semgrep (Linux) | pip install semgrep (Windows)"
25
+ echo " Pre-cache rules: semgrep --config=p/security-audit"
26
+ echo " Gate 10: SAST Security (WARN, semgrep not installed)"
27
+ GATE_10_STATUS="WARN"
28
+ else
29
+ # Get staged files filtered to Semgrep-supported languages
30
+ SEMGREP_FILES=$(git diff --cached --name-only --diff-filter=ACM 2>/dev/null | grep -E '\.(ts|tsx|js|jsx|py|go|java|c|cpp|cs|rb|php|scala|swift)$' || true)
31
+
32
+ if [ -z "$SEMGREP_FILES" ]; then
33
+ echo " ⏭️ SKIPPED - SAST (no supported language files changed)"
34
+ GATE_10_STATUS="SKIP"
35
+ else
36
+ # Run semgrep with JSON output
37
+ # --config=p/security-audit: explicit security ruleset
38
+ # --json: machine-readable output
39
+ # --disable-version-check: skip network call
40
+ SEMGREP_OUTPUT=$("$SEMGREP_CMD" scan --config=p/security-audit --json --disable-version-check "$SEMGREP_FILES" 2>&1)
41
+ SEMGREP_EXIT=$?
42
+
43
+ if [ "$SEMGREP_EXIT" -eq 0 ]; then
44
+ echo " ✅ PASSED - No security vulnerabilities found."
45
+ GATE_10_STATUS="PASS"
46
+ elif [ "$SEMGREP_EXIT" -eq 1 ]; then
47
+ # Findings detected - parse JSON to categorize
48
+ CRITICAL_HIGH=$(echo "$SEMGREP_OUTPUT" | node -e "
49
+ let d='';process.stdin.on('data',c=>d+=c);process.stdin.on('end',()=>{
50
+ try{const data=JSON.parse(d);const results=data.results||[];
51
+ const count=results.filter(r=>{const s=(r.extra&&r.extra.severity||'').toUpperCase();return s==='CRITICAL'||s==='HIGH';}).length;
52
+ console.log(count);}catch(e){console.log('0');}
53
+ });" 2>/dev/null || echo "0")
54
+
55
+ MEDIUM_LOW=$(echo "$SEMGREP_OUTPUT" | node -e "
56
+ let d='';process.stdin.on('data',c=>d+=c);process.stdin.on('end',()=>{
57
+ try{const data=JSON.parse(d);const results=data.results||[];
58
+ const count=results.filter(r=>{const s=(r.extra&&r.extra.severity||'').toUpperCase();return s==='MEDIUM'||s==='LOW';}).length;
59
+ console.log(count);}catch(e){console.log('0');}
60
+ });" 2>/dev/null || echo "0")
61
+
62
+ # Extract top finding details
63
+ FINDING_DETAILS=$(echo "$SEMGREP_OUTPUT" | node -e "
64
+ let d='';process.stdin.on('data',c=>d+=c);process.stdin.on('end',()=>{
65
+ try{const data=JSON.parse(d);const results=data.results||[];
66
+ results.slice(0,5).forEach(r=>{const extra=r.extra||{};
67
+ const severity=(extra.severity||'UNKNOWN').toUpperCase();
68
+ const ruleId=r.check_id||'unknown';const path=r.path||'unknown';
69
+ const line=(r.start&&r.start.line)||'?';const msg=(extra.message||'').slice(0,80);
70
+ console.log(' ['+severity+'] '+ruleId);console.log(' '+path+':'+line+' → '+msg);console.log();});
71
+ }catch(e){console.log(' (Failed to parse semgrep output)');}
72
+ });" 2>/dev/null || echo " (Failed to parse semgrep output)")
73
+
74
+ if [ "$CRITICAL_HIGH" -gt 0 ]; then
75
+ echo ""
76
+ echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
77
+ echo " GATE 10: Semgrep Security Gate"
78
+ echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
79
+ echo " CRITICAL/HIGH: ${CRITICAL_HIGH} ❌ BLOCKED"
80
+ echo " MEDIUM/LOW: ${MEDIUM_LOW} ⚠️ warning"
81
+ echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
82
+ echo " ❌ BLOCKED — Critical/High vulnerability found"
83
+ echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
84
+ echo "$FINDING_DETAILS"
85
+ echo " Run 'semgrep scan --config=p/security-audit' to review all findings."
86
+ GATE_10_STATUS="FAIL"
87
+ exit 1
88
+ else
89
+ echo ""
90
+ echo " ✅ PASSED - No critical/high vulnerabilities"
91
+ if [ "$MEDIUM_LOW" -gt 0 ]; then
92
+ echo " ⚠️ ${MEDIUM_LOW} medium/low findings (warnings only)"
93
+ echo "$FINDING_DETAILS"
94
+ fi
95
+ GATE_10_STATUS="PASS"
96
+ fi
97
+ else
98
+ # semgrep runtime error (timeout, config error, etc.)
99
+ echo " ⚠️ semgrep exited with code ${SEMGREP_EXIT} — skipping gate"
100
+ echo " ⏭️ SKIPPED - SAST (semgrep runtime error)"
101
+ GATE_10_STATUS="SKIP"
102
+ fi
103
+ fi
104
+ fi
105
+ record_gate_audit "gate-10" "sast-security" "$GATE_10_STATUS" "0" "$GATE_10_START"
File without changes
@@ -0,0 +1,125 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+
4
+ const fs = require('node:fs');
5
+
6
+ const REQUIRED_ROLES = ['architecture', 'technical', 'feasibility'];
7
+ const VALIDITY_MS = 60 * 60 * 1000;
8
+
9
+ function fail(message) {
10
+ console.error(`Invalid code walkthrough evidence: ${message}`);
11
+ process.exit(1);
12
+ }
13
+
14
+ function isPlainObject(value) {
15
+ if (value === null || typeof value !== 'object' || Array.isArray(value)) return false;
16
+ const prototype = Object.getPrototypeOf(value);
17
+ return prototype === Object.prototype || prototype === null;
18
+ }
19
+
20
+ function parseTimestamp(value, field) {
21
+ const match = typeof value === 'string'
22
+ ? value.match(/^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})(?:\.(\d{1,3}))?Z$/)
23
+ : null;
24
+ if (!match) fail(`${field} must be a canonical UTC ISO-8601 timestamp.`);
25
+
26
+ const [, yearText, monthText, dayText, hourText, minuteText, secondText, fractionText] = match;
27
+ const year = Number(yearText);
28
+ const month = Number(monthText);
29
+ const day = Number(dayText);
30
+ const hour = Number(hourText);
31
+ const minute = Number(minuteText);
32
+ const second = Number(secondText);
33
+ const milliseconds = Number((fractionText || '').padEnd(3, '0') || 0);
34
+ const date = new Date(0);
35
+ date.setUTCFullYear(year, month - 1, day);
36
+ date.setUTCHours(hour, minute, second, milliseconds);
37
+
38
+ if (date.getUTCFullYear() !== year
39
+ || date.getUTCMonth() !== month - 1
40
+ || date.getUTCDate() !== day
41
+ || date.getUTCHours() !== hour
42
+ || date.getUTCMinutes() !== minute
43
+ || date.getUTCSeconds() !== second
44
+ || date.getUTCMilliseconds() !== milliseconds) {
45
+ fail(`${field} must contain a valid UTC calendar date and time.`);
46
+ }
47
+ return date.getTime();
48
+ }
49
+
50
+ function validateExpert(expert, index, roles, models) {
51
+ if (!isPlainObject(expert)) fail(`experts[${index}] must be a plain object.`);
52
+ if (!REQUIRED_ROLES.includes(expert.role)) fail(`experts[${index}].role is invalid.`);
53
+ if (roles.has(expert.role)) fail(`expert role ${expert.role} is duplicated.`);
54
+ roles.add(expert.role);
55
+
56
+ if (expert.verdict !== 'APPROVED') fail(`expert ${expert.role} must be APPROVED.`);
57
+ if (expert.result_type !== 'delphi_expert_result') {
58
+ fail(`expert ${expert.role} must be a successful Delphi expert result.`);
59
+ }
60
+ if (expert.error !== undefined || expert.fallback !== undefined) {
61
+ fail(`expert ${expert.role} contains an error or fallback marker.`);
62
+ }
63
+
64
+ if (typeof expert.requested_model !== 'string' || expert.requested_model.trim() === '') {
65
+ fail(`expert ${expert.role} requested_model must be non-empty.`);
66
+ }
67
+ const model = expert.requested_model.trim();
68
+ if (models.has(model)) fail(`requested_model ${model} is duplicated.`);
69
+ models.add(model);
70
+
71
+ if (expert.resolved_model !== null
72
+ && (typeof expert.resolved_model !== 'string' || expert.resolved_model.trim() === '')) {
73
+ fail(`expert ${expert.role} resolved_model must be non-empty or null.`);
74
+ }
75
+ }
76
+
77
+ function validateEvidence(evidence, expectedCommit, expectedBranch, now) {
78
+ if (!isPlainObject(evidence)) fail('top level must be a plain object.');
79
+ if (evidence.commit !== expectedCommit) fail('commit does not match HEAD.');
80
+ if (evidence.branch !== expectedBranch) fail('branch does not match the current branch.');
81
+ if (evidence.verdict !== 'APPROVED') fail('top-level verdict must be APPROVED.');
82
+
83
+ const timestamp = parseTimestamp(evidence.timestamp, 'timestamp');
84
+ const expires = parseTimestamp(evidence.expires, 'expires');
85
+ if (timestamp > now) fail('timestamp is in the future.');
86
+ if (expires <= now) fail('evidence has expired.');
87
+ if (expires - timestamp !== VALIDITY_MS) {
88
+ fail('expires must be exactly one hour after timestamp.');
89
+ }
90
+
91
+ if (typeof evidence.consensus_ratio !== 'number'
92
+ || !Number.isFinite(evidence.consensus_ratio)
93
+ || evidence.consensus_ratio < 0.90
94
+ || evidence.consensus_ratio > 1) {
95
+ fail('consensus_ratio must be a finite number from 0.90 through 1.');
96
+ }
97
+
98
+ if (!Array.isArray(evidence.experts) || evidence.experts.length !== 3) {
99
+ fail('experts must contain exactly three records.');
100
+ }
101
+ const roles = new Set();
102
+ const models = new Set();
103
+ evidence.experts.forEach((expert, index) => {
104
+ validateExpert(expert, index, roles, models);
105
+ });
106
+ if (roles.size !== REQUIRED_ROLES.length) fail('all required expert roles must be present.');
107
+ }
108
+
109
+ function main() {
110
+ const [file, expectedCommit, expectedBranch, nowValue] = process.argv.slice(2);
111
+ if (!file || !expectedCommit || !expectedBranch) {
112
+ fail('usage: validate-code-walkthrough.cjs <file> <commit> <branch> [now].');
113
+ }
114
+
115
+ let evidence;
116
+ try {
117
+ evidence = JSON.parse(fs.readFileSync(file, 'utf8'));
118
+ } catch {
119
+ fail('file must exist and contain valid JSON.');
120
+ }
121
+ const now = nowValue === undefined ? Date.now() : parseTimestamp(nowValue, 'current time');
122
+ validateEvidence(evidence, expectedCommit, expectedBranch, now);
123
+ }
124
+
125
+ main();
package/hooks/pre-commit CHANGED
@@ -14,7 +14,21 @@
14
14
  SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
15
15
  ADAPTER_DIR=""
16
16
  GLOBAL_ADAPTER_DIR="$HOME/.config/xp-gate/adapters"
17
- PROJECT_GITHOOKS="$(git rev-parse --show-toplevel 2>/dev/null)/githooks"
17
+
18
+ # BEGIN GIT CONTEXT FALLBACK
19
+ if ! declare -F run_without_git_context >/dev/null 2>&1; then
20
+ run_without_git_context() (
21
+ unset GIT_ALTERNATE_OBJECT_DIRECTORIES GIT_CONFIG GIT_CONFIG_PARAMETERS
22
+ unset GIT_CONFIG_COUNT GIT_OBJECT_DIRECTORY GIT_DIR GIT_WORK_TREE
23
+ unset GIT_IMPLICIT_WORK_TREE GIT_GRAFT_FILE GIT_INDEX_FILE
24
+ unset GIT_NO_REPLACE_OBJECTS GIT_REPLACE_REF_BASE GIT_PREFIX
25
+ unset GIT_SHALLOW_FILE GIT_COMMON_DIR
26
+ "$@"
27
+ )
28
+ fi
29
+ # END GIT CONTEXT FALLBACK
30
+
31
+ PROJECT_GITHOOKS="$(run_without_git_context git rev-parse --show-toplevel 2>/dev/null)/githooks"
18
32
  GATES_DIR="$SCRIPT_DIR/gates"
19
33
 
20
34
  if [ -f "$GLOBAL_ADAPTER_DIR/adapter-common.sh" ]; then
@@ -89,7 +103,7 @@ _quality_report_on_exit() {
89
103
  }
90
104
  trap '_quality_report_on_exit' EXIT
91
105
 
92
- PROJECT_ROOT="$(git rev-parse --show-toplevel 2>/dev/null || echo "$(cd "$SCRIPT_DIR/../.." 2>/dev/null || echo "$SCRIPT_DIR/..")" )"
106
+ PROJECT_ROOT="$(run_without_git_context git rev-parse --show-toplevel 2>/dev/null || echo "$(cd "$SCRIPT_DIR/../.." 2>/dev/null || echo "$SCRIPT_DIR/..")" )"
93
107
  if [[ -d "$PROJECT_ROOT/node_modules/.bin" ]]; then
94
108
  export PATH="$PROJECT_ROOT/node_modules/.bin:$PATH"
95
109
  fi
@@ -415,6 +429,17 @@ if [ -z "$CHANGED_FILES" ]; then
415
429
  exit 0
416
430
  fi
417
431
 
432
+ any_changed_files_match() {
433
+ local pattern
434
+
435
+ for pattern in "$@"; do
436
+ if printf '%s\n' "$CHANGED_FILES" | grep -qE -- "$pattern"; then
437
+ return 0
438
+ fi
439
+ done
440
+ return 1
441
+ }
442
+
418
443
  # ============================================================================
419
444
  # Multi-language detection (v0.15.0+): detect all languages from changed file
420
445
  # extensions, with fallback to manifest-based detection.
@@ -1387,6 +1412,14 @@ else
1387
1412
  # --- BLOCK path: New .ts/.tsx files without corresponding tests ---
1388
1413
  NEW_TS_FILES=$(git diff --cached --name-only --diff-filter=A | grep -E '\.(ts|tsx)$' | grep -v '\.d\.ts$' | grep -v '__tests__' | grep -v '\.test\.' | grep -v '\.spec\.' | grep -v '__snapshots__' | grep -v 'node_modules/' | grep -v '\.next/' | grep -v '\.nuxt/' | grep -v 'dist/' | grep -v 'build/' | grep -v '.turbo/' | grep -v '.cache/' | grep -Ev '(^|/)vitest\.config\.' | grep -Ev '(^|/)vite\.config\.' | grep -Ev '(^|/)jest\.config\.' | grep -Ev '(^|/)tsconfig\.' | grep -Ev '(^|/)eslint\.' | grep -Ev '(^|/)prettier\.' | grep -Ev '(^|/)tailwind\.' || true)
1389
1414
 
1415
+ # Strip PROJECT_SUBDIR prefix from new-TS-file paths when running in a subdirectory.
1416
+ # git diff returns repo-root relative paths, but after cd "$PROJECT_SUBDIR"
1417
+ # the test-pairing [ -f ] checks below need subdirectory-relative paths.
1418
+ # (Mirrors the v0.14.31+ Gate 5b fix at lines 1603-1608; Gate 5a was missing it.)
1419
+ if [ -n "$PROJECT_SUBDIR" ] && [ "$PROJECT_SUBDIR" != "." ] && [ -n "$NEW_TS_FILES" ]; then
1420
+ NEW_TS_FILES=$(echo "$NEW_TS_FILES" | sed "s|^${PROJECT_SUBDIR}/||")
1421
+ fi
1422
+
1390
1423
  if [ -n "$NEW_TS_FILES" ]; then
1391
1424
  BLOCKED_TS_FILES=0
1392
1425
  for src_file in $NEW_TS_FILES; do
@@ -1749,8 +1782,8 @@ else
1749
1782
  if [ "${CURRENT_LANG}" != "unknown" ] && [ -n "$ADAPTER_FILE" ]; then
1750
1783
  if source "$ADAPTER_FILE" 2>/dev/null; then
1751
1784
  # TypeScript: vitest with smart test selection
1752
- if [ "$CURRENT_LANG" = "typescript" ] && [ -f "package.json" ]; then
1753
- if npx vitest --version >/dev/null 2>&1; then
1785
+ if [ "$CURRENT_LANG" = "typescript" ] && { [ -f "package.json" ] || [ -f "$PROJECT_ROOT/package.json" ]; }; then
1786
+ if run_without_git_context npx vitest --version >/dev/null 2>&1; then
1754
1787
  # Count changed test files to decide strategy
1755
1788
  CHANGED_TEST_FILE_COUNT=$(echo "$CHANGED_TEST_FILES" | grep -c '.' || echo "0")
1756
1789
 
@@ -1766,7 +1799,7 @@ else
1766
1799
  if [ "$CHANGED_TEST_FILE_COUNT" -gt 20 ] 2>/dev/null; then
1767
1800
  # Too many test files changed → full run
1768
1801
  echo "Too many test files changed ($CHANGED_TEST_FILE_COUNT), running full test suite..."
1769
- TESTS_OUTPUT=$(npx vitest run --coverage 2>&1)
1802
+ TESTS_OUTPUT=$(run_without_git_context npx vitest run --coverage 2>&1)
1770
1803
  TESTS_EXIT_CODE=$?
1771
1804
  echo "$TESTS_OUTPUT" | head -60
1772
1805
  if [ "$TESTS_EXIT_CODE" -ne 0 ]; then
@@ -1781,7 +1814,7 @@ else
1781
1814
  SMART_TARGETS=$(echo "$CHANGED_TEST_FILES" | tr '\n' ' ')
1782
1815
  echo "Running ${CHANGED_TEST_FILE_COUNT} changed test file(s) with coverage..."
1783
1816
  echo " Target(s): $(echo "$CHANGED_TEST_FILES" | tr '\n' ' ')"
1784
- TESTS_OUTPUT=$(npx vitest run --coverage $SMART_TARGETS 2>&1)
1817
+ TESTS_OUTPUT=$(run_without_git_context npx vitest run --coverage $SMART_TARGETS 2>&1)
1785
1818
  TESTS_EXIT_CODE=$?
1786
1819
  echo "$TESTS_OUTPUT" | head -60
1787
1820
  if [ "$TESTS_EXIT_CODE" -ne 0 ]; then
@@ -1825,7 +1858,7 @@ else
1825
1858
  if [ -n "$RELATED_TESTS" ]; then
1826
1859
  echo " Found related test(s): $(echo "$RELATED_TESTS" | tr '\n' ' ')"
1827
1860
  PARTIAL_TEST_RUN=true # Mark as partial run for coverage check
1828
- TESTS_OUTPUT=$(npx vitest run --coverage $RELATED_TESTS 2>&1)
1861
+ TESTS_OUTPUT=$(run_without_git_context npx vitest run --coverage $RELATED_TESTS 2>&1)
1829
1862
  TESTS_EXIT_CODE=$?
1830
1863
  echo "$TESTS_OUTPUT" | head -60
1831
1864
  if [ "$TESTS_EXIT_CODE" -ne 0 ]; then
@@ -1838,7 +1871,7 @@ else
1838
1871
  else
1839
1872
  # No related tests found → full run is safest
1840
1873
  echo " No related test files found, running full test suite..."
1841
- TESTS_OUTPUT=$(npx vitest run --coverage 2>&1)
1874
+ TESTS_OUTPUT=$(run_without_git_context npx vitest run --coverage 2>&1)
1842
1875
  TESTS_EXIT_CODE=$?
1843
1876
  echo "$TESTS_OUTPUT" | head -60
1844
1877
  if [ "$TESTS_EXIT_CODE" -ne 0 ]; then
@@ -1857,7 +1890,7 @@ else
1857
1890
  fi
1858
1891
  else
1859
1892
  # Fallback: run_tests without coverage
1860
- TESTS_OUTPUT=$(run_tests 2>&1)
1893
+ TESTS_OUTPUT=$(run_without_git_context run_tests 2>&1)
1861
1894
  TESTS_EXIT_CODE=$?
1862
1895
  echo "$TESTS_OUTPUT" | head -50
1863
1896
  if [ "$TESTS_EXIT_CODE" -ne 0 ]; then
@@ -1868,15 +1901,21 @@ else
1868
1901
  fi
1869
1902
  else
1870
1903
  # Non-TypeScript: standard adapter test flow
1871
- TESTS_OUTPUT=$(run_tests 2>&1)
1872
- TESTS_EXIT_CODE=$?
1873
- echo "$TESTS_OUTPUT" | head -50
1874
- if [ "$TESTS_EXIT_CODE" -ne 0 ]; then
1875
- echo ""
1876
- echo "❌ BLOCKED - Tests FAILED"
1877
- exit 1
1878
- else
1879
- echo "✅ PASSED - Unit tests passed."
1904
+ # Skip for Python — tests already run with coverage in the coverage section below.
1905
+ # Running them here first without coverage doubles pre-commit time for no benefit.
1906
+ if [ "$CURRENT_LANG" = "python" ]; then
1907
+ echo "ℹ️ Python tests deferred to coverage section (single pass)."
1908
+ else
1909
+ TESTS_OUTPUT=$(run_without_git_context run_tests 2>&1)
1910
+ TESTS_EXIT_CODE=$?
1911
+ echo "$TESTS_OUTPUT" | head -50
1912
+ if [ "$TESTS_EXIT_CODE" -ne 0 ]; then
1913
+ echo ""
1914
+ echo "❌ BLOCKED - Tests FAILED"
1915
+ exit 1
1916
+ else
1917
+ echo "✅ PASSED - Unit tests passed."
1918
+ fi
1880
1919
  fi
1881
1920
  fi
1882
1921
  else
@@ -2056,6 +2095,45 @@ else
2056
2095
  echo "⚠️ coverage/lcov.info not found. Ensure dart test --coverage was run."
2057
2096
  fi
2058
2097
  ;;
2098
+ "powershell")
2099
+ COVERAGE_ENFORCED=true
2100
+ if [ -f "coverage.xml" ]; then
2101
+ COVERAGE_RESULT=$(node -e "
2102
+ try {
2103
+ const fs = require('fs');
2104
+ const xml = fs.readFileSync('coverage.xml', 'utf8');
2105
+ const counters = [...xml.matchAll(/<counter\b([^>]*)\/?\s*>/gi)]
2106
+ .map((match) => Object.fromEntries(
2107
+ [...match[1].matchAll(/([\w:-]+)\s*=\s*(['\"])(.*?)\2/g)]
2108
+ .map((attribute) => [attribute[1].toUpperCase(), attribute[3]])
2109
+ ))
2110
+ .filter((counter) => counter.TYPE === 'LINE');
2111
+ const reportCounter = counters.at(-1);
2112
+ const missed = Number(reportCounter?.MISSED);
2113
+ const covered = Number(reportCounter?.COVERED);
2114
+ const total = missed + covered;
2115
+ if (!Number.isFinite(missed) || !Number.isFinite(covered) || missed < 0 || covered < 0 || total <= 0) {
2116
+ throw new Error('invalid LINE counter');
2117
+ }
2118
+ const percentage = (covered / total) * 100;
2119
+ console.log((percentage < 80 ? 'below' : 'pass') + '|' + Math.round(percentage));
2120
+ } catch (error) { console.log('parse_error'); }
2121
+ " 2>/dev/null) || COVERAGE_RESULT="parse_error"
2122
+ if [ "$COVERAGE_RESULT" != "parse_error" ] && [ -n "$COVERAGE_RESULT" ]; then
2123
+ COVERAGE_STATUS=${COVERAGE_RESULT%%|*}
2124
+ COVERAGE_PERCENT=${COVERAGE_RESULT#*|}
2125
+ if [ "$COVERAGE_STATUS" = "below" ]; then
2126
+ echo "❌ BLOCKED - PowerShell coverage ${COVERAGE_PERCENT}% below 80% threshold"
2127
+ exit 1
2128
+ fi
2129
+ echo "PowerShell coverage: ${COVERAGE_PERCENT}% ✅ (≥ 80%)"
2130
+ else
2131
+ echo "⚠️ Could not parse PowerShell coverage from coverage.xml"
2132
+ fi
2133
+ else
2134
+ echo "⚠️ coverage.xml not found. Ensure Pester coverage was run."
2135
+ fi
2136
+ ;;
2059
2137
  *)
2060
2138
  # Default: attempt to parse coverage from common formats regardless of language
2061
2139
  COVERAGE_ENFORCED=true
@@ -2165,6 +2243,15 @@ elif has_project_lang "powershell" 2>/dev/null || [ "$PROJECT_LANG" = "powershel
2165
2243
  echo " ⏭️ SKIPPED - Architecture validation (no tool for PowerShell)"
2166
2244
 
2167
2245
  else
2246
+ run_archlint() (
2247
+ cd "$PROJECT_ROOT" || exit 1
2248
+ if [ "$ARCHLINT_KIND" = "npx" ]; then
2249
+ npx @archlinter/cli "$@"
2250
+ else
2251
+ "$ARCHLINT_BIN" "$@"
2252
+ fi
2253
+ )
2254
+
2168
2255
  # Check if architecture config exists (use $PROJECT_ROOT — hook may have cd'd to a subdir via ARCH-03)
2169
2256
  if [ -f "$PROJECT_ROOT/architecture.yaml" ] || [ -f "$PROJECT_ROOT/.architecturerc" ] || [ -f "$PROJECT_ROOT/.archlint.yaml" ]; then
2170
2257
  # Different architecture tools per language
@@ -2172,12 +2259,14 @@ else
2172
2259
  case "$CURRENT_LANG" in
2173
2260
  "typescript")
2174
2261
  # Try local npx install first (devDependencies), fall back to global
2175
- if npx --no-install @archlinter/cli --version >/dev/null 2>&1; then
2176
- ARCHLINT_CMD="npx --no-install @archlinter/cli"
2262
+ if [ -x "$PROJECT_ROOT/node_modules/.bin/archlint" ]; then
2263
+ ARCHLINT_KIND="binary"
2264
+ ARCHLINT_BIN="$PROJECT_ROOT/node_modules/.bin/archlint"
2177
2265
  elif command -v archlint >/dev/null 2>&1; then
2178
- ARCHLINT_CMD="archlint"
2179
- elif npx @archlinter/cli --version >/dev/null 2>&1; then
2180
- ARCHLINT_CMD="npx @archlinter/cli"
2266
+ ARCHLINT_KIND="binary"
2267
+ ARCHLINT_BIN="$(command -v archlint)"
2268
+ elif (cd "$PROJECT_ROOT" && npx @archlinter/cli --version) >/dev/null 2>&1; then
2269
+ ARCHLINT_KIND="npx"
2181
2270
  else
2182
2271
  echo " ❌ BLOCKED - archlint not installed"
2183
2272
  echo " Install with: npm install --save-dev @archlinter/cli"
@@ -2186,17 +2275,17 @@ else
2186
2275
  echo " Running archlint for TypeScript..."
2187
2276
  # Use diff mode with baseline for ratchet (block only new violations)
2188
2277
  # Fall back to raw scan if no baseline exists (new project, zero-tolerance)
2189
- if [ -f ".architecture-baseline.json" ]; then
2190
- ARCHLINT_OUTPUT=$($ARCHLINT_CMD diff .architecture-baseline.json --fail-on high 2>&1)
2278
+ if [ -f "$PROJECT_ROOT/.architecture-baseline.json" ]; then
2279
+ ARCHLINT_OUTPUT=$(run_archlint diff .architecture-baseline.json --fail-on high 2>&1)
2191
2280
  else
2192
- ARCHLINT_OUTPUT=$($ARCHLINT_CMD scan . -f table --quiet 2>&1)
2281
+ ARCHLINT_OUTPUT=$(run_archlint scan . -f table --quiet 2>&1)
2193
2282
  fi
2194
2283
  ARCHLINT_EXIT=$?
2195
2284
  echo "$ARCHLINT_OUTPUT" | tail -30
2196
2285
  if [ "$ARCHLINT_EXIT" -ne 0 ]; then
2197
2286
  echo ""
2198
2287
  echo "❌ BLOCKED - Architecture violations detected"
2199
- if [ -f ".architecture-baseline.json" ]; then
2288
+ if [ -f "$PROJECT_ROOT/.architecture-baseline.json" ]; then
2200
2289
  echo "New architecture violations found (baseline ratchet mode)."
2201
2290
  echo "Fix the new violations above, or update the baseline with: npx @archlinter/cli snapshot -o .architecture-baseline.json"
2202
2291
  else
@@ -2205,7 +2294,7 @@ else
2205
2294
  fi
2206
2295
  exit 1
2207
2296
  fi
2208
- if [ -f ".architecture-baseline.json" ]; then
2297
+ if [ -f "$PROJECT_ROOT/.architecture-baseline.json" ]; then
2209
2298
  echo " ✅ TypeScript architecture validation completed (baseline ratchet mode)."
2210
2299
  else
2211
2300
  echo " ✅ TypeScript architecture validation completed (zero-tolerance mode). no baseline found."
@@ -2276,7 +2365,7 @@ else
2276
2365
  echo " Required configuration file (.archlint.yaml / architecture.yaml) is NOT found."
2277
2366
  echo " Architecture Quality Gate requires explicit architecture constraints."
2278
2367
  echo ""
2279
- echo " Create .archlint.yaml with: npx archlint init --no-interactive"
2368
+ echo " Quick fix: create .archlint.yaml with: npx archlint init --no-interactive"
2280
2369
  echo " Or architecture.yaml for custom Clean Architecture layer definitions:"
2281
2370
  echo " layers:"
2282
2371
  echo " - name: api"
@@ -2451,13 +2540,20 @@ echo ""
2451
2540
 
2452
2541
  # ============================================================================
2453
2542
  # GATE 10: Semgrep SAST Security Scan
2454
- # Extracted to gate-10.sh for maintainability (sourced as gate-9.sh)
2543
+ # Extracted to gate-10.sh for maintainability
2455
2544
  # v0.14.3+ (Issue #312): Skip if no code files changed
2545
+ # v0.18.5 (Issue #397): Fix gate status variable collision — SAST now sets GATE_10_STATUS
2456
2546
  # ============================================================================
2457
2547
  if any_changed_files_match "\.(ts|tsx|js|jsx|py|go|java|kt|cpp|c|swift|m|dart|sh)$"; then
2458
- source "$GATE_DIR/gate-9.sh"
2548
+ if [ -f "$GATE_DIR/gate-10.sh" ]; then
2549
+ source "$GATE_DIR/gate-10.sh"
2550
+ else
2551
+ echo "⏭️ SKIPPED - SAST (gate-10.sh not found)"
2552
+ GATE_10_STATUS="SKIP"
2553
+ fi
2459
2554
  else
2460
2555
  echo "✅ PASSED - No code files changed, skipping SAST scan."
2556
+ GATE_10_STATUS="PASS"
2461
2557
  fi
2462
2558
  # ============================================================================
2463
2559
 
@@ -2561,7 +2657,7 @@ generate_quality_report() {
2561
2657
  # ── Branch-level quality status file ─────────────────────────────────────────────
2562
2658
  local STATUS_DIR=".xp-gate/quality-status"
2563
2659
  local REPORT_FILE="${STATUS_DIR}/${BRANCH}.json"
2564
- mkdir -p "$STATUS_DIR"
2660
+ mkdir -p "$(dirname "$REPORT_FILE")"
2565
2661
 
2566
2662
  cat > "$REPORT_FILE" << ENDJSON
2567
2663
  {
@@ -2714,5 +2810,6 @@ if [ -n "$_wp_marker" ] && [ "$_wp_marker" != "/xp-gate-precommit-marker" ]; the
2714
2810
  fi
2715
2811
 
2716
2812
  generate_quality_report
2813
+ _QUALITY_REPORT_DONE=1
2717
2814
 
2718
- exit 0
2815
+ exit 0