@boyingliu01/xp-gate 0.18.4 → 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 (248) hide show
  1. package/adapter-common.sh +27 -0
  2. package/adapters/powershell.sh +85 -33
  3. package/adapters/typescript.sh +8 -8
  4. package/build-integrity/__tests__/import-resolver.test.ts +22 -0
  5. package/build-integrity/__tests__/orchestrator.test.ts +1 -2
  6. package/build-integrity/gate-10.ts +7 -0
  7. package/gate-12-file-hygiene.sh +230 -0
  8. package/hooks/adapter-common.sh +27 -0
  9. package/hooks/gate-10.sh +105 -0
  10. package/hooks/lib/validate-code-walkthrough.cjs +125 -0
  11. package/hooks/pre-commit +130 -33
  12. package/hooks/pre-push +15 -91
  13. package/lib/__tests__/check-version.test.js +67 -46
  14. package/lib/__tests__/doctor.test.js +7 -6
  15. package/lib/__tests__/gate-runner.test.js +267 -0
  16. package/lib/__tests__/init.test.js +32 -0
  17. package/lib/__tests__/npm-package.test.js +13 -0
  18. package/lib/__tests__/phase-transition.test.js +604 -12
  19. package/lib/__tests__/sprint-discovery.test.ts +0 -1
  20. package/lib/__tests__/sprint-status-rework.test.js +100 -1
  21. package/lib/__tests__/update-hooks.test.js +143 -10
  22. package/lib/__tests__/upgrade-exec.test.js +73 -45
  23. package/lib/__tests__/upgrade-skill-sync.test.js +1 -1
  24. package/lib/__tests__/upgrade.test.js +12 -11
  25. package/lib/doctor.js +2 -1
  26. package/lib/gate-runner.js +68 -23
  27. package/lib/init.js +34 -5
  28. package/lib/install-skill.js +1 -0
  29. package/lib/mattpocock-license-verdict.json +1 -1
  30. package/lib/phase-transition.js +203 -23
  31. package/lib/shared-paths.js +2 -0
  32. package/lib/sprint-status.js +15 -4
  33. package/lib/test-alignment.ts +3 -13
  34. package/lib/update-hooks.js +129 -15
  35. package/mock-policy/AGENTS.md +5 -5
  36. package/mock-policy/__tests__/gate-m3.test.ts +0 -5
  37. package/mock-policy/__tests__/scope-scanner.test.ts +8 -2
  38. package/mutation/AGENTS.md +4 -4
  39. package/mutation/__tests__/mutmut-runner.test.ts +443 -2
  40. package/mutation/__tests__/update-baseline.test.ts +0 -2
  41. package/mutation/runners/mutmut-runner.ts +67 -17
  42. package/package.json +9 -3
  43. package/plugins/claude-code/.claude-plugin/plugin.json +1 -1
  44. package/plugins/claude-code/skills/clipboard-vision/SKILL.md +47 -0
  45. package/plugins/claude-code/skills/clipboard-vision/__tests__/clipboard-vision-distribution.test.cjs +65 -0
  46. package/plugins/claude-code/skills/clipboard-vision/__tests__/clipboard-vision-security.test.cjs +185 -0
  47. package/plugins/claude-code/skills/clipboard-vision/__tests__/clipboard-vision.Tests.ps1 +138 -0
  48. package/plugins/claude-code/skills/clipboard-vision/__tests__/clipboard-vision.test.cjs +273 -0
  49. package/plugins/claude-code/skills/clipboard-vision/describe-clipboard.ps1 +115 -0
  50. package/plugins/claude-code/skills/clipboard-vision/describe-clipboard.sh +161 -0
  51. package/plugins/claude-code/skills/clipboard-vision/scripts/clipboard-vision.ps1 +86 -0
  52. package/plugins/claude-code/skills/clipboard-vision/scripts/clipboard-vision.sh +137 -0
  53. package/plugins/claude-code/skills/delphi-review/.delphi-config.json.example +24 -27
  54. package/plugins/claude-code/skills/delphi-review/AGENTS.md +13 -12
  55. package/plugins/claude-code/skills/delphi-review/INSTALL.md +15 -17
  56. package/plugins/claude-code/skills/delphi-review/SKILL.md +30 -29
  57. package/plugins/claude-code/skills/delphi-review/opencode.json.delphi.example +6 -10
  58. package/plugins/claude-code/skills/delphi-review/references/code-walkthrough.md +33 -49
  59. package/plugins/claude-code/skills/delphi-review/references/requirements.md +66 -54
  60. package/plugins/claude-code/skills/delphi-review/references/round-templates.md +7 -7
  61. package/plugins/claude-code/skills/sprint-flow/AGENTS.md +5 -5
  62. package/plugins/claude-code/skills/sprint-flow/SKILL.md +4 -4
  63. package/plugins/claude-code/skills/sprint-flow/references/components/skill-invocations.md +2 -2
  64. package/plugins/claude-code/skills/sprint-flow/references/force-levels.md +37 -35
  65. package/plugins/claude-code/skills/sprint-flow/references/orchestration-rules.md +1 -1
  66. package/plugins/claude-code/skills/sprint-flow/references/phase-1-prep.md +1 -1
  67. package/plugins/claude-code/skills/sprint-flow/references/phase-2-design.md +24 -14
  68. package/plugins/claude-code/skills/sprint-flow/references/phase-4-verify.md +6 -6
  69. package/plugins/claude-code/skills/sprint-flow/references/phase-overview.md +2 -2
  70. package/plugins/claude-code/skills/test-specification-alignment/AGENTS.md +4 -4
  71. package/plugins/dsh/README.md +25 -0
  72. package/plugins/dsh/cordis.patch.yml +7 -0
  73. package/plugins/dsh/package.json +70 -0
  74. package/plugins/dsh/scripts/prepack.cjs +82 -0
  75. package/plugins/dsh/skills/admin-template-guidelines/SKILL.md +113 -0
  76. package/plugins/dsh/skills/admin-template-guidelines/references/rule-1.md +91 -0
  77. package/plugins/dsh/skills/admin-template-guidelines/references/rule-2.md +155 -0
  78. package/plugins/dsh/skills/admin-template-guidelines/references/rule-3.md +70 -0
  79. package/plugins/dsh/skills/admin-template-guidelines/references/rule-4.md +154 -0
  80. package/plugins/dsh/skills/admin-template-guidelines/references/rule-5.md +126 -0
  81. package/plugins/dsh/skills/admin-template-guidelines/references/rule-6.md +180 -0
  82. package/plugins/dsh/skills/batch-grill-me/SKILL.md +15 -0
  83. package/plugins/dsh/skills/clipboard-vision/SKILL.md +47 -0
  84. package/plugins/dsh/skills/clipboard-vision/__tests__/clipboard-vision-distribution.test.cjs +65 -0
  85. package/plugins/dsh/skills/clipboard-vision/__tests__/clipboard-vision-security.test.cjs +185 -0
  86. package/plugins/dsh/skills/clipboard-vision/__tests__/clipboard-vision.Tests.ps1 +138 -0
  87. package/plugins/dsh/skills/clipboard-vision/__tests__/clipboard-vision.test.cjs +273 -0
  88. package/plugins/dsh/skills/clipboard-vision/describe-clipboard.ps1 +115 -0
  89. package/plugins/dsh/skills/clipboard-vision/describe-clipboard.sh +161 -0
  90. package/plugins/dsh/skills/clipboard-vision/scripts/clipboard-vision.ps1 +86 -0
  91. package/plugins/dsh/skills/clipboard-vision/scripts/clipboard-vision.sh +137 -0
  92. package/plugins/dsh/skills/delphi-review/.delphi-config.json.example +70 -0
  93. package/plugins/dsh/skills/delphi-review/AGENTS.md +67 -0
  94. package/plugins/dsh/skills/delphi-review/INSTALL.md +150 -0
  95. package/plugins/dsh/skills/delphi-review/SKILL.md +559 -0
  96. package/plugins/dsh/skills/delphi-review/hooks.md +19 -0
  97. package/plugins/dsh/skills/delphi-review/opencode.json.delphi.example +52 -0
  98. package/plugins/dsh/skills/delphi-review/references/code-walkthrough.md +470 -0
  99. package/plugins/dsh/skills/delphi-review/references/orchestrator-dispatch.md +64 -0
  100. package/plugins/dsh/skills/delphi-review/references/requirements.md +402 -0
  101. package/plugins/dsh/skills/delphi-review/references/round-templates.md +134 -0
  102. package/plugins/dsh/skills/delphi-review/scripts/verify-consensus.sh +43 -0
  103. package/plugins/dsh/skills/delphi-review/tools.md +29 -0
  104. package/plugins/dsh/skills/domain-modeling/ADR-FORMAT.md +64 -0
  105. package/plugins/dsh/skills/domain-modeling/CONTEXT-FORMAT.md +60 -0
  106. package/plugins/dsh/skills/domain-modeling/SKILL.md +74 -0
  107. package/plugins/dsh/skills/grill-with-docs/SKILL.md +7 -0
  108. package/plugins/dsh/skills/grilling/SKILL.md +12 -0
  109. package/plugins/dsh/skills/improve-codebase-architecture/SKILL.md +156 -0
  110. package/plugins/dsh/skills/ralph-loop/SKILL.md +198 -0
  111. package/plugins/dsh/skills/ralph-loop/references/components.md +113 -0
  112. package/plugins/dsh/skills/ralph-loop/references/phase-2-build-ralph.md +89 -0
  113. package/plugins/dsh/skills/ralph-loop/references/workflow-details.md +99 -0
  114. package/plugins/dsh/skills/ralph-loop/templates/progress-log.md +36 -0
  115. package/plugins/dsh/skills/sprint-flow/AGENTS.md +103 -0
  116. package/plugins/dsh/skills/sprint-flow/SKILL.md +411 -0
  117. package/plugins/dsh/skills/sprint-flow/__tests__/sprint-flow.test.cjs +457 -0
  118. package/plugins/dsh/skills/sprint-flow/hooks.md +18 -0
  119. package/plugins/dsh/skills/sprint-flow/references/components/memory.md +87 -0
  120. package/plugins/dsh/skills/sprint-flow/references/components/middleware.md +75 -0
  121. package/plugins/dsh/skills/sprint-flow/references/components/skill-invocations.md +105 -0
  122. package/plugins/dsh/skills/sprint-flow/references/components/system-prompt.md +27 -0
  123. package/plugins/dsh/skills/sprint-flow/references/components/tool-descriptions.md +95 -0
  124. package/plugins/dsh/skills/sprint-flow/references/force-levels.md +204 -0
  125. package/plugins/dsh/skills/sprint-flow/references/orchestration-rules.md +236 -0
  126. package/plugins/dsh/skills/sprint-flow/references/phase-1-prep.md +294 -0
  127. package/plugins/dsh/skills/sprint-flow/references/phase-2-design.md +273 -0
  128. package/plugins/dsh/skills/sprint-flow/references/phase-3-build.md +348 -0
  129. package/plugins/dsh/skills/sprint-flow/references/phase-4-verify.md +225 -0
  130. package/plugins/dsh/skills/sprint-flow/references/phase-5-ship.md +222 -0
  131. package/plugins/dsh/skills/sprint-flow/references/phase-6-close.md +266 -0
  132. package/plugins/dsh/skills/sprint-flow/references/phase-overview.md +486 -0
  133. package/plugins/dsh/skills/sprint-flow/scripts/verify-sprint-state.sh +38 -0
  134. package/plugins/dsh/skills/sprint-flow/templates/auto-estimate-learning-log.md +136 -0
  135. package/plugins/dsh/skills/sprint-flow/templates/auto-estimate-output-template.md +110 -0
  136. package/plugins/dsh/skills/sprint-flow/templates/emergent-issues-template.md +120 -0
  137. package/plugins/dsh/skills/sprint-flow/templates/pain-document-template.md +115 -0
  138. package/plugins/dsh/skills/sprint-flow/templates/sprint-progress-template.md +140 -0
  139. package/plugins/dsh/skills/sprint-flow/templates/sprint-summary-template.md +121 -0
  140. package/plugins/dsh/skills/sprint-flow/tools.md +29 -0
  141. package/plugins/dsh/skills/test-driven-development/SKILL.md +371 -0
  142. package/plugins/dsh/skills/test-driven-development/testing-anti-patterns.md +299 -0
  143. package/plugins/dsh/skills/test-specification-alignment/AGENTS.md +56 -0
  144. package/plugins/dsh/skills/test-specification-alignment/SKILL.md +205 -0
  145. package/plugins/dsh/skills/test-specification-alignment/references/alignment-verification-algorithm.md +493 -0
  146. package/plugins/dsh/skills/test-specification-alignment/references/phase2-constraint-enforcement.md +431 -0
  147. package/plugins/dsh/skills/test-specification-alignment/references/specification-format.md +348 -0
  148. package/plugins/dsh/skills/to-issues/SKILL.md +277 -0
  149. package/plugins/dsh/src/command.test.ts +102 -0
  150. package/plugins/dsh/src/command.ts +75 -0
  151. package/plugins/dsh/src/contract.test.ts +62 -0
  152. package/plugins/dsh/src/gate-runner.test.ts +100 -0
  153. package/plugins/dsh/src/gate-runner.ts +58 -0
  154. package/plugins/dsh/src/index.test.ts +97 -0
  155. package/plugins/dsh/src/index.ts +94 -0
  156. package/plugins/dsh/src/skills.test.ts +58 -0
  157. package/plugins/dsh/tsconfig.build.json +13 -0
  158. package/plugins/dsh/tsconfig.json +19 -0
  159. package/plugins/dsh/vitest.config.ts +8 -0
  160. package/plugins/opencode/package.json +1 -1
  161. package/plugins/opencode/scripts/prepack.cjs +1 -0
  162. package/plugins/opencode/skills/clipboard-vision/SKILL.md +47 -0
  163. package/plugins/opencode/skills/clipboard-vision/__tests__/clipboard-vision-distribution.test.cjs +65 -0
  164. package/plugins/opencode/skills/clipboard-vision/__tests__/clipboard-vision-security.test.cjs +185 -0
  165. package/plugins/opencode/skills/clipboard-vision/__tests__/clipboard-vision.Tests.ps1 +138 -0
  166. package/plugins/opencode/skills/clipboard-vision/__tests__/clipboard-vision.test.cjs +273 -0
  167. package/plugins/opencode/skills/clipboard-vision/describe-clipboard.ps1 +115 -0
  168. package/plugins/opencode/skills/clipboard-vision/describe-clipboard.sh +161 -0
  169. package/plugins/opencode/skills/clipboard-vision/scripts/clipboard-vision.ps1 +86 -0
  170. package/plugins/opencode/skills/clipboard-vision/scripts/clipboard-vision.sh +137 -0
  171. package/plugins/opencode/skills/delphi-review/.delphi-config.json.example +24 -27
  172. package/plugins/opencode/skills/delphi-review/AGENTS.md +13 -12
  173. package/plugins/opencode/skills/delphi-review/INSTALL.md +15 -17
  174. package/plugins/opencode/skills/delphi-review/SKILL.md +30 -29
  175. package/plugins/opencode/skills/delphi-review/opencode.json.delphi.example +6 -10
  176. package/plugins/opencode/skills/delphi-review/references/code-walkthrough.md +33 -49
  177. package/plugins/opencode/skills/delphi-review/references/requirements.md +66 -54
  178. package/plugins/opencode/skills/delphi-review/references/round-templates.md +7 -7
  179. package/plugins/opencode/skills/sprint-flow/AGENTS.md +5 -5
  180. package/plugins/opencode/skills/sprint-flow/SKILL.md +4 -4
  181. package/plugins/opencode/skills/sprint-flow/references/components/skill-invocations.md +2 -2
  182. package/plugins/opencode/skills/sprint-flow/references/force-levels.md +37 -35
  183. package/plugins/opencode/skills/sprint-flow/references/orchestration-rules.md +1 -1
  184. package/plugins/opencode/skills/sprint-flow/references/phase-1-prep.md +1 -1
  185. package/plugins/opencode/skills/sprint-flow/references/phase-2-design.md +24 -14
  186. package/plugins/opencode/skills/sprint-flow/references/phase-4-verify.md +6 -6
  187. package/plugins/opencode/skills/sprint-flow/references/phase-overview.md +2 -2
  188. package/plugins/opencode/skills/test-specification-alignment/AGENTS.md +4 -4
  189. package/plugins/qoder/plugin.json +1 -1
  190. package/plugins/qoder/skills/clipboard-vision/SKILL.md +47 -0
  191. package/plugins/qoder/skills/clipboard-vision/__tests__/clipboard-vision-distribution.test.cjs +65 -0
  192. package/plugins/qoder/skills/clipboard-vision/__tests__/clipboard-vision-security.test.cjs +185 -0
  193. package/plugins/qoder/skills/clipboard-vision/__tests__/clipboard-vision.Tests.ps1 +138 -0
  194. package/plugins/qoder/skills/clipboard-vision/__tests__/clipboard-vision.test.cjs +273 -0
  195. package/plugins/qoder/skills/clipboard-vision/clipboard-vision/SKILL.md +47 -0
  196. package/plugins/qoder/skills/clipboard-vision/clipboard-vision/__tests__/clipboard-vision-distribution.test.cjs +65 -0
  197. package/plugins/qoder/skills/clipboard-vision/clipboard-vision/__tests__/clipboard-vision-security.test.cjs +185 -0
  198. package/plugins/qoder/skills/clipboard-vision/clipboard-vision/__tests__/clipboard-vision.Tests.ps1 +138 -0
  199. package/plugins/qoder/skills/clipboard-vision/clipboard-vision/__tests__/clipboard-vision.test.cjs +273 -0
  200. package/plugins/qoder/skills/clipboard-vision/clipboard-vision/scripts/clipboard-vision.ps1 +86 -0
  201. package/plugins/qoder/skills/clipboard-vision/clipboard-vision/scripts/clipboard-vision.sh +137 -0
  202. package/plugins/qoder/skills/clipboard-vision/scripts/clipboard-vision.ps1 +86 -0
  203. package/plugins/qoder/skills/clipboard-vision/scripts/clipboard-vision.sh +137 -0
  204. package/plugins/qoder/skills/delphi-review/.delphi-config.json.example +3 -11
  205. package/plugins/qoder/skills/delphi-review/AGENTS.md +15 -17
  206. package/plugins/qoder/skills/delphi-review/INSTALL.md +12 -12
  207. package/plugins/qoder/skills/delphi-review/SKILL.md +58 -25
  208. package/plugins/qoder/skills/delphi-review/opencode.json.delphi.example +1 -1
  209. package/plugins/qoder/skills/delphi-review/references/code-walkthrough.md +33 -49
  210. package/plugins/qoder/skills/sprint-flow/AGENTS.md +5 -5
  211. package/plugins/qoder/skills/sprint-flow/SKILL.md +2 -2
  212. package/plugins/qoder/skills/sprint-flow/references/force-levels.md +38 -35
  213. package/plugins/qoder/skills/sprint-flow/references/phase-1-prep.md +1 -1
  214. package/plugins/qoder/skills/sprint-flow/references/phase-2-design.md +48 -6
  215. package/plugins/qoder/skills/sprint-flow/references/phase-4-verify.md +5 -6
  216. package/plugins/qoder/skills/test-specification-alignment/AGENTS.md +4 -4
  217. package/principles/AGENTS.md +4 -4
  218. package/scripts/__tests__/sync-package-content.test.js +30 -0
  219. package/scripts/delphi-external-review.cjs +194 -63
  220. package/scripts/sync-package-content.js +9 -2
  221. package/skills/clipboard-vision/SKILL.md +47 -0
  222. package/skills/clipboard-vision/__tests__/clipboard-vision-distribution.test.cjs +65 -0
  223. package/skills/clipboard-vision/__tests__/clipboard-vision-security.test.cjs +185 -0
  224. package/skills/clipboard-vision/__tests__/clipboard-vision.Tests.ps1 +138 -0
  225. package/skills/clipboard-vision/__tests__/clipboard-vision.test.cjs +273 -0
  226. package/skills/clipboard-vision/describe-clipboard.ps1 +115 -0
  227. package/skills/clipboard-vision/describe-clipboard.sh +161 -0
  228. package/skills/clipboard-vision/scripts/clipboard-vision.ps1 +86 -0
  229. package/skills/clipboard-vision/scripts/clipboard-vision.sh +137 -0
  230. package/skills/delphi-review/.delphi-config.json.example +24 -27
  231. package/skills/delphi-review/AGENTS.md +13 -12
  232. package/skills/delphi-review/INSTALL.md +15 -17
  233. package/skills/delphi-review/SKILL.md +30 -29
  234. package/skills/delphi-review/opencode.json.delphi.example +6 -10
  235. package/skills/delphi-review/references/code-walkthrough.md +33 -49
  236. package/skills/delphi-review/references/requirements.md +66 -54
  237. package/skills/delphi-review/references/round-templates.md +7 -7
  238. package/skills/sprint-flow/AGENTS.md +5 -5
  239. package/skills/sprint-flow/SKILL.md +4 -4
  240. package/skills/sprint-flow/references/components/skill-invocations.md +2 -2
  241. package/skills/sprint-flow/references/force-levels.md +37 -35
  242. package/skills/sprint-flow/references/orchestration-rules.md +1 -1
  243. package/skills/sprint-flow/references/phase-1-prep.md +1 -1
  244. package/skills/sprint-flow/references/phase-2-design.md +24 -14
  245. package/skills/sprint-flow/references/phase-4-verify.md +6 -6
  246. package/skills/sprint-flow/references/phase-overview.md +2 -2
  247. package/skills/test-specification-alignment/AGENTS.md +4 -4
  248. package/sprint-gate.sh +291 -0
@@ -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
package/hooks/pre-push CHANGED
@@ -867,116 +867,47 @@ if [ ! -f "$RESULT_FILE" ]; then
867
867
  exit 1
868
868
  fi
869
869
 
870
- # Check jq availability (MANDATORY - zero degradation)
871
- if ! command -v jq &> /dev/null; then
870
+ # Check Node.js availability (MANDATORY - zero degradation)
871
+ if ! command -v node >/dev/null 2>&1; then
872
872
  echo ""
873
873
  echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
874
874
  echo " ❌ ENVIRONMENT ERROR - PUSH BLOCKED"
875
875
  echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
876
876
  echo ""
877
- echo "jq is NOT installed. Required for JSON validation."
877
+ echo "Node.js is NOT installed. Required for JSON validation."
878
878
  echo ""
879
- echo "Install jq:"
880
- echo " apt-get install jq (Debian/Ubuntu)"
881
- echo " brew install jq (macOS)"
882
- echo " choco install jq (Windows)"
883
- echo ""
884
- echo "Zero degradation principle: Cannot proceed without jq."
879
+ echo "Install Node.js 18 or newer, then retry the push."
885
880
  echo ""
886
881
  exit 1
887
882
  fi
888
883
 
889
- # Validate result file content
890
- # SHA self-reference paradox fix: if the result file was committed as part of
891
- # HEAD, its content references HEAD^ (the commit being reviewed), not HEAD.
892
- CURRENT_COMMIT=$(git rev-parse HEAD)
893
- if git diff-tree --no-commit-id --name-only -r HEAD 2>/dev/null | grep -qx "$RESULT_FILE"; then
894
- # Result file is part of the latest commit — compare against the reviewed commit
895
- EXPECTED_COMMIT=$(git rev-parse HEAD^ 2>/dev/null || echo "unknown")
896
- else
897
- # Result file is working-tree only — compare against current HEAD
898
- EXPECTED_COMMIT="$CURRENT_COMMIT"
899
- fi
884
+ # Walkthrough evidence must bind to the exact feature-branch HEAD being pushed.
885
+ EXPECTED_COMMIT=$(git rev-parse HEAD)
900
886
 
901
- # Check JSON validity
902
- if ! jq empty "$RESULT_FILE" 2>/dev/null; then
887
+ # Validate the complete evidence contract without interpolating JSON into shell.
888
+ HOOK_DIR=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)
889
+ MW_VALIDATOR="$HOOK_DIR/lib/validate-code-walkthrough.cjs"
890
+ if [ ! -f "$MW_VALIDATOR" ]; then
903
891
  echo ""
904
892
  echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
905
893
  echo " ❌ RESULT FILE INVALID - PUSH BLOCKED"
906
894
  echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
907
895
  echo ""
908
- echo "$RESULT_FILE is not valid JSON."
896
+ echo "Gate MW validator is missing: $MW_VALIDATOR"
909
897
  echo ""
910
898
  echo "Re-run: /delphi-review --mode code-walkthrough"
911
899
  echo ""
912
900
  exit 1
913
901
  fi
914
902
 
915
- RESULT_COMMIT=$(jq -r '.commit' "$RESULT_FILE")
916
- RESULT_VERDICT=$(jq -r '.verdict' "$RESULT_FILE")
917
- RESULT_EXPIRES=$(jq -r '.expires' "$RESULT_FILE")
918
- RESULT_BRANCH=$(jq -r '.branch' "$RESULT_FILE")
919
903
  CURRENT_TIME=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
920
-
921
- # Branch match check (optional but recommended)
922
- if [ "$RESULT_BRANCH" != "$CURRENT_BRANCH" ]; then
923
- echo ""
924
- echo "⚠️ WARNING: Result file is for different branch"
925
- echo " Expected: $CURRENT_BRANCH"
926
- echo " Found: $RESULT_BRANCH"
927
- echo ""
928
- echo "Proceeding with commit verification..."
929
- fi
930
-
931
- # Commit match check
932
- if [ "$RESULT_COMMIT" != "$EXPECTED_COMMIT" ]; then
904
+ if ! VALIDATION_ERROR=$(node "$MW_VALIDATOR" "$RESULT_FILE" "$EXPECTED_COMMIT" "$CURRENT_BRANCH" "$CURRENT_TIME" 2>&1); then
933
905
  echo ""
934
906
  echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
935
907
  echo " ❌ RESULT FILE OUTDATED - PUSH BLOCKED"
936
908
  echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
937
909
  echo ""
938
- echo "Result file is for a different commit:"
939
- echo " Expected: $EXPECTED_COMMIT"
940
- echo " Found: $RESULT_COMMIT"
941
- echo ""
942
- echo "Re-run: /delphi-review --mode code-walkthrough"
943
- echo ""
944
- exit 1
945
- fi
946
-
947
- # Verdict check
948
- if [ "$RESULT_VERDICT" != "APPROVED" ]; then
949
- echo ""
950
- echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
951
- echo " ❌ GATE MW: CODE WALKTHROUGH NOT APPROVED - PUSH BLOCKED"
952
- echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
953
- echo ""
954
- echo "Verdict: $RESULT_VERDICT"
955
- echo ""
956
-
957
- # Show issues if available
958
- ISSUES=$(jq -r '.issues[] | "- [\(.severity)] \(.description)"' "$RESULT_FILE" 2>/dev/null)
959
- if [ -n "$ISSUES" ]; then
960
- echo "Issues found:"
961
- echo "$ISSUES"
962
- echo ""
963
- fi
964
-
965
- echo "Fix issues and re-run: /delphi-review --mode code-walkthrough"
966
- echo ""
967
- exit 1
968
- fi
969
-
970
- # Expiration check
971
- if [[ "$CURRENT_TIME" > "$RESULT_EXPIRES" ]]; then
972
- echo ""
973
- echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
974
- echo " ❌ RESULT FILE EXPIRED - PUSH BLOCKED"
975
- echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
976
- echo ""
977
- echo "Code walkthrough result has expired:"
978
- echo " Expired: $RESULT_EXPIRES"
979
- echo " Current: $CURRENT_TIME"
910
+ echo "$VALIDATION_ERROR"
980
911
  echo ""
981
912
  echo "Re-run: /delphi-review --mode code-walkthrough"
982
913
  echo ""
@@ -996,16 +927,9 @@ echo "Files changed: $FILES_CHANGED"
996
927
  echo "Lines: +$LINES_ADDED -$LINES_DELETED"
997
928
  echo ""
998
929
  echo "Code walkthrough result:"
999
- echo " Commit: $RESULT_COMMIT"
930
+ echo " Commit: $EXPECTED_COMMIT"
1000
931
  echo " Verdict: APPROVED"
1001
- echo " Expires: $RESULT_EXPIRES"
1002
-
1003
- # Show confidence if available
1004
- CONFIDENCE=$(jq -r '.confidence' "$RESULT_FILE" 2>/dev/null)
1005
- if [ -n "$CONFIDENCE" ] && [ "$CONFIDENCE" != "null" ]; then
1006
- echo " Confidence: $CONFIDENCE/10"
1007
- fi
1008
932
 
1009
933
  echo ""
1010
934
  echo "Proceeding with push..."
1011
- exit 0
935
+ exit 0