@boyingliu01/xp-gate 0.18.5 → 0.19.1

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 +114 -35
  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 +4 -4
  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 +3 -3
  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 +12 -11
  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 +4 -4
  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 +3 -3
  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 +12 -11
  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 +4 -4
  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 +3 -3
  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 +14 -16
  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 +4 -4
  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 +3 -3
  223. package/principles/AGENTS.md +3 -3
  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 +12 -11
  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 +4 -4
  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 +3 -3
  254. package/sprint-gate.sh +291 -0
@@ -13,7 +13,6 @@ import os from 'os';
13
13
 
14
14
  // Types mirroring DiscoveredSprint interface
15
15
  interface DiscoveredSprint {
16
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
17
16
  state: any;
18
17
  sourcePath: string;
19
18
  worktreeExists: boolean;
@@ -14,7 +14,22 @@ let TMP_DIR;
14
14
  let cwdSpy;
15
15
 
16
16
  function git(cmd, dir) {
17
- return execSync(`git ${cmd}`, { cwd: dir || TMP_DIR, encoding: 'utf8', stdio: ['pipe', 'pipe', 'pipe'] });
17
+ const gitEnv = { ...process.env };
18
+ for (const name of [
19
+ 'GIT_ALTERNATE_OBJECT_DIRECTORIES', 'GIT_CONFIG', 'GIT_CONFIG_PARAMETERS',
20
+ 'GIT_CONFIG_COUNT', 'GIT_OBJECT_DIRECTORY', 'GIT_DIR', 'GIT_WORK_TREE',
21
+ 'GIT_IMPLICIT_WORK_TREE', 'GIT_GRAFT_FILE', 'GIT_INDEX_FILE',
22
+ 'GIT_NO_REPLACE_OBJECTS', 'GIT_REPLACE_REF_BASE', 'GIT_PREFIX',
23
+ 'GIT_SHALLOW_FILE', 'GIT_COMMON_DIR',
24
+ ]) {
25
+ delete gitEnv[name];
26
+ }
27
+ return execSync(`git ${cmd}`, {
28
+ cwd: dir || TMP_DIR,
29
+ env: gitEnv,
30
+ encoding: 'utf8',
31
+ stdio: ['pipe', 'pipe', 'pipe'],
32
+ });
18
33
  }
19
34
 
20
35
  function writeSprintState(state, dir) {
@@ -181,4 +196,88 @@ describe('sprint-status --rework-check (#369)', () => {
181
196
  expect(code).toBe(0);
182
197
  expect(output).toContain('No closed sprints within 1 day');
183
198
  });
199
+
200
+ test('(e) --dir takes precedence over inherited Git hook repository variables', async () => {
201
+ const targetDir = fs.mkdtempSync(path.join(os.tmpdir(), 'rework-target-'));
202
+ const outerDir = fs.mkdtempSync(path.join(os.tmpdir(), 'rework-outer-'));
203
+
204
+ for (const dir of [targetDir, outerDir]) {
205
+ git('init', dir);
206
+ git('config user.email "test@test.com"', dir);
207
+ git('config user.name "Test"', dir);
208
+ }
209
+ makeCommit('feat: target initial', targetDir);
210
+ makeCommit('fix: target bug', targetDir);
211
+ makeCommit('fix: outer one', outerDir);
212
+ makeCommit('fix: outer two', outerDir);
213
+ makeCommit('fix: outer three', outerDir);
214
+
215
+ writeSprintState({
216
+ _schema_version: 1,
217
+ id: 'sprint-hook-env',
218
+ task_description: 'Hook environment sprint',
219
+ phase: 6,
220
+ status: 'completed',
221
+ started_at: daysAgo(9),
222
+ isolation: { worktree_path: null, branch: 'main' },
223
+ phase_history: [],
224
+ metrics: { completed_at: daysAgo(2), total_sprint_commits: 10 },
225
+ }, targetDir);
226
+ cwdSpy.mockReturnValue(targetDir);
227
+
228
+ const previousGitDir = process.env.GIT_DIR;
229
+ const previousWorkTree = process.env.GIT_WORK_TREE;
230
+ process.env.GIT_DIR = path.join(outerDir, '.git');
231
+ process.env.GIT_WORK_TREE = outerDir;
232
+ const logSpy = vi.spyOn(console, 'log').mockImplementation(() => {});
233
+ try {
234
+ const code = await handleSprintStatus([
235
+ '--rework-check', '--window-days', '7', '--dir', targetDir,
236
+ ]);
237
+ const output = logSpy.mock.calls.map(c => c.join(' ')).join('\n');
238
+
239
+ expect(code).toBe(0);
240
+ expect(output).toContain('10.0%');
241
+ expect(output).toContain('1 fix / 10 total commits');
242
+ } finally {
243
+ logSpy.mockRestore();
244
+ if (previousGitDir === undefined) delete process.env.GIT_DIR;
245
+ else process.env.GIT_DIR = previousGitDir;
246
+ if (previousWorkTree === undefined) delete process.env.GIT_WORK_TREE;
247
+ else process.env.GIT_WORK_TREE = previousWorkTree;
248
+ fs.rmSync(targetDir, { recursive: true, force: true });
249
+ fs.rmSync(outerDir, { recursive: true, force: true });
250
+ }
251
+ });
252
+
253
+ test('(f) completed_at cannot inject shell commands into the Git log query', async () => {
254
+ const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'rework-injection-'));
255
+ const markerFile = path.join(dir, 'injection-marker');
256
+ cwdSpy.mockReturnValue(dir);
257
+ git('init', dir);
258
+ git('config user.email "test@test.com"', dir);
259
+ git('config user.name "Test"', dir);
260
+ makeCommit('fix: target bug', dir);
261
+
262
+ writeSprintState({
263
+ _schema_version: 1,
264
+ id: 'sprint-injection',
265
+ phase: 6,
266
+ status: 'completed',
267
+ metrics: {
268
+ completed_at: `Thu, 06 Aug 2026 00:00:00 GMT ("; touch ${markerFile}; #)`,
269
+ total_sprint_commits: 1,
270
+ },
271
+ }, dir);
272
+
273
+ const logSpy = vi.spyOn(console, 'log').mockImplementation(() => {});
274
+ try {
275
+ const code = await handleSprintStatus(['--rework-check', '--window-days', '3650', '--dir', dir]);
276
+ expect(code).toBe(0);
277
+ expect(fs.existsSync(markerFile)).toBe(false);
278
+ } finally {
279
+ logSpy.mockRestore();
280
+ fs.rmSync(dir, { recursive: true, force: true });
281
+ }
282
+ });
184
283
  });
@@ -3,9 +3,9 @@
3
3
  * @intent Verify updateHooks correctly syncs hook files with proper backup, dry-run, scope, and detection behavior
4
4
  * @covers AC-265-01, AC-265-02, AC-265-03, AC-265-04, AC-265-05
5
5
  */
6
- const fs = require('fs');
7
- const path = require('path');
8
- const os = require('os');
6
+ const fs = require('node:fs');
7
+ const path = require('node:path');
8
+ const os = require('node:os');
9
9
 
10
10
  describe('updateHooks', () => {
11
11
  let tmpHome;
@@ -14,7 +14,6 @@ describe('updateHooks', () => {
14
14
  let originalHome;
15
15
  let logSpy;
16
16
  let warnSpy;
17
- let errorSpy;
18
17
 
19
18
  beforeEach(() => {
20
19
  originalHome = process.env.HOME;
@@ -25,7 +24,6 @@ describe('updateHooks', () => {
25
24
  vi.resetModules();
26
25
  logSpy = vi.spyOn(console, 'log').mockImplementation(() => {});
27
26
  warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {});
28
- errorSpy = vi.spyOn(console, 'error').mockImplementation(() => {});
29
27
  });
30
28
 
31
29
  afterEach(() => {
@@ -39,17 +37,26 @@ describe('updateHooks', () => {
39
37
  /** Helper: create a fake package structure under tmpPackage */
40
38
  function createPackageSource(overrides = {}) {
41
39
  const hooksDir = path.join(tmpPackage, 'hooks');
40
+ const hooksLibDir = path.join(hooksDir, 'lib');
42
41
  const adaptersDir = path.join(tmpPackage, 'adapters');
43
- fs.mkdirSync(hooksDir, { recursive: true });
42
+ fs.mkdirSync(hooksLibDir, { recursive: true });
44
43
  fs.mkdirSync(adaptersDir, { recursive: true });
45
44
 
46
45
  fs.writeFileSync(path.join(hooksDir, 'pre-commit'), overrides['hooks/pre-commit'] || '#!/bin/bash\necho "hook-v2"');
47
46
  fs.writeFileSync(path.join(hooksDir, 'pre-push'), overrides['hooks/pre-push'] || '#!/bin/bash\necho "push-v2"');
47
+ fs.writeFileSync(path.join(hooksLibDir, 'now-ms.sh'), overrides['hooks/lib/now-ms.sh'] || 'now_ms() { echo 123; }');
48
48
  fs.writeFileSync(path.join(tmpPackage, 'adapter-common.sh'), overrides['adapter-common.sh'] || '#!/bin/bash\necho "adapter-common-v2"');
49
49
  fs.writeFileSync(path.join(adaptersDir, 'typescript.sh'), overrides['adapters/typescript.sh'] || '#!/bin/bash\necho "ts-v2"');
50
50
  fs.writeFileSync(path.join(adaptersDir, 'python.sh'), overrides['adapters/python.sh'] || '#!/bin/bash\necho "py-v2"');
51
51
  fs.writeFileSync(path.join(tmpPackage, 'gate-3.sh'), overrides['gate-3.sh'] || '#!/bin/bash\necho "gate3-v2"');
52
52
  fs.writeFileSync(path.join(tmpPackage, 'gate-4.sh'), overrides['gate-4.sh'] || '#!/bin/bash\necho "gate4-v2"');
53
+
54
+ // Quality gate modules (principles, mutation, mock-policy, build-integrity)
55
+ ['principles', 'build-integrity'].forEach(module => {
56
+ const moduleDir = path.join(tmpPackage, module);
57
+ fs.mkdirSync(moduleDir, { recursive: true });
58
+ fs.writeFileSync(path.join(moduleDir, 'index.ts'), overrides[`${module}/index.ts`] || `export const ${module} = 1;`);
59
+ });
53
60
  }
54
61
 
55
62
  /** Helper: get fresh updateHooks module */
@@ -145,16 +152,16 @@ describe('updateHooks', () => {
145
152
  expect(fs.readFileSync(path.join(dest, 'gate-4.sh'), 'utf8')).toContain('gate4-v2');
146
153
  });
147
154
 
148
- it('copies sprint-gate.sh alongside gate-*.sh scripts (#335)', () => {
155
+ it('copies gate-12-file-hygiene.sh to the adapters directory', () => {
149
156
  createPackageSource();
150
- fs.writeFileSync(path.join(tmpPackage, 'sprint-gate.sh'), '#!/bin/bash\necho "sprint-gate-v1"');
157
+ fs.writeFileSync(path.join(tmpPackage, 'gate-12-file-hygiene.sh'), '#!/bin/bash\necho "hygiene-v1"');
151
158
  const mod = getModule();
152
- const dest = path.join(tmpProject, 'gates-sprint');
159
+ const dest = path.join(tmpProject, 'gates-hygiene');
153
160
  fs.mkdirSync(dest, { recursive: true });
154
161
 
155
162
  mod.copyGateScripts(tmpPackage, dest, false, true);
156
163
 
157
- expect(fs.readFileSync(path.join(dest, 'sprint-gate.sh'), 'utf8')).toContain('sprint-gate-v1');
164
+ expect(fs.readFileSync(path.join(dest, 'gate-12-file-hygiene.sh'), 'utf8')).toContain('hygiene-v1');
158
165
  });
159
166
 
160
167
  it('does not copy non-gate files', () => {
@@ -167,6 +174,7 @@ describe('updateHooks', () => {
167
174
  mod.copyGateScripts(tmpPackage, dest, false, true);
168
175
 
169
176
  expect(fs.existsSync(path.join(dest, 'some-other.sh'))).toBe(false);
177
+ expect(fs.existsSync(path.join(dest, 'sprint-gate.sh'))).toBe(false);
170
178
  });
171
179
 
172
180
  it('handles missing source directory gracefully', () => {
@@ -181,6 +189,73 @@ describe('updateHooks', () => {
181
189
  });
182
190
  });
183
191
 
192
+ // ===== copySprintGate tests =====
193
+
194
+ describe('copySprintGate', () => {
195
+ it('copies sprint-gate.sh to the hooks directory', () => {
196
+ createPackageSource();
197
+ fs.writeFileSync(path.join(tmpPackage, 'sprint-gate.sh'), '#!/bin/bash\necho "sprint-gate-v1"');
198
+ const mod = getModule();
199
+ const hooksDest = path.join(tmpProject, 'hooks-dest');
200
+ fs.mkdirSync(hooksDest, { recursive: true });
201
+
202
+ mod.copySprintGate(tmpPackage, hooksDest, false, true);
203
+
204
+ expect(fs.readFileSync(path.join(hooksDest, 'sprint-gate.sh'), 'utf8')).toContain('sprint-gate-v1');
205
+ });
206
+
207
+ it('skips silently when sprint-gate.sh is absent from package root', () => {
208
+ createPackageSource();
209
+ const mod = getModule();
210
+ const hooksDest = path.join(tmpProject, 'hooks-no-sprint');
211
+ fs.mkdirSync(hooksDest, { recursive: true });
212
+
213
+ mod.copySprintGate(tmpPackage, hooksDest, false, true);
214
+
215
+ expect(fs.existsSync(path.join(hooksDest, 'sprint-gate.sh'))).toBe(false);
216
+ });
217
+ });
218
+
219
+ // ===== copyModules tests =====
220
+
221
+ describe('copyModules', () => {
222
+ it('copies quality gate module directories to the modules destination', () => {
223
+ createPackageSource();
224
+ const mod = getModule();
225
+ const modulesDest = path.join(tmpProject, 'modules-dest');
226
+ fs.mkdirSync(modulesDest, { recursive: true });
227
+
228
+ mod.copyModules(tmpPackage, modulesDest, false, true);
229
+
230
+ expect(fs.readFileSync(path.join(modulesDest, 'principles', 'index.ts'), 'utf8')).toContain('principles');
231
+ expect(fs.readFileSync(path.join(modulesDest, 'build-integrity', 'index.ts'), 'utf8')).toContain('build-integrity');
232
+ });
233
+
234
+ it('skips modules absent from the package root', () => {
235
+ createPackageSource();
236
+ const mod = getModule();
237
+ const modulesDest = path.join(tmpProject, 'modules-dest-skip');
238
+ fs.mkdirSync(modulesDest, { recursive: true });
239
+ fs.rmSync(path.join(tmpPackage, 'mutation'), { recursive: true, force: true });
240
+
241
+ mod.copyModules(tmpPackage, modulesDest, false, true);
242
+
243
+ expect(fs.existsSync(path.join(modulesDest, 'mutation'))).toBe(false);
244
+ expect(fs.existsSync(path.join(modulesDest, 'principles'))).toBe(true);
245
+ });
246
+
247
+ it('does not write files in dry-run mode', () => {
248
+ createPackageSource();
249
+ const mod = getModule();
250
+ const modulesDest = path.join(tmpProject, 'modules-dest-dry');
251
+ fs.mkdirSync(modulesDest, { recursive: true });
252
+
253
+ mod.copyModules(tmpPackage, modulesDest, true, true);
254
+
255
+ expect(fs.existsSync(path.join(modulesDest, 'principles', 'index.ts'))).toBe(false);
256
+ });
257
+ });
258
+
184
259
  // ===== atomicCopyFile tests =====
185
260
 
186
261
  describe('atomicCopyFile', () => {
@@ -403,6 +478,47 @@ describe('updateHooks', () => {
403
478
  mod.getPackageRoot = origGetPackageRoot;
404
479
  });
405
480
 
481
+ it('copies hooks/lib/now-ms.sh during a normal update', () => {
482
+ createPackageSource();
483
+ const hooksDest = path.join(tmpProject, '.git', 'hooks');
484
+ fs.mkdirSync(hooksDest, { recursive: true });
485
+ vi.spyOn(process, 'cwd').mockReturnValue(tmpProject);
486
+ const mod = getModule();
487
+ mod.getPackageRoot = () => tmpPackage;
488
+
489
+ const result = mod.updateHooks({
490
+ global: false,
491
+ force: true,
492
+ dryRun: false,
493
+ noBackup: true,
494
+ scope: 'hooks',
495
+ });
496
+
497
+ expect(result).toBe(0);
498
+ expect(fs.readFileSync(path.join(hooksDest, 'lib', 'now-ms.sh'), 'utf8')).toContain('echo 123');
499
+ });
500
+
501
+ it('does not write hooks/lib/now-ms.sh during a dry run', () => {
502
+ createPackageSource();
503
+ const hooksDest = path.join(tmpProject, '.git', 'hooks');
504
+ fs.mkdirSync(hooksDest, { recursive: true });
505
+ vi.spyOn(process, 'cwd').mockReturnValue(tmpProject);
506
+ const mod = getModule();
507
+ mod.getPackageRoot = () => tmpPackage;
508
+
509
+ const result = mod.updateHooks({
510
+ global: false,
511
+ force: true,
512
+ dryRun: true,
513
+ noBackup: true,
514
+ scope: 'hooks',
515
+ });
516
+
517
+ expect(result).toBe(0);
518
+ expect(logSpy).toHaveBeenCalledWith(' would update: lib/now-ms.sh');
519
+ expect(fs.existsSync(path.join(hooksDest, 'lib', 'now-ms.sh'))).toBe(false);
520
+ });
521
+
406
522
  it('returns 1 when local modifications detected and no --force', () => {
407
523
  createPackageSource();
408
524
  const hooksDest = path.join(tmpProject, '.git', 'hooks');
@@ -447,6 +563,8 @@ describe('updateHooks', () => {
447
563
  });
448
564
  expect(result).toBe(0);
449
565
  expect(fs.readFileSync(path.join(hooksDest, 'pre-commit'), 'utf8')).toContain('hook-v2');
566
+ // scope 'all' also re-deploys quality-gate modules (repairs existing installs)
567
+ expect(fs.readFileSync(path.join(tmpProject, '.xp-gate', 'modules', 'build-integrity', 'index.ts'), 'utf8')).toContain('build-integrity');
450
568
 
451
569
  mod.getPackageRoot = origGetPackageRoot;
452
570
  });
@@ -589,4 +707,19 @@ describe('updateHooks', () => {
589
707
  mod.getPackageRoot = origGetPackageRoot;
590
708
  });
591
709
  });
710
+
711
+ describe('warnMissingModuleDeps', () => {
712
+ it('warns when global module runtime deps are absent', () => {
713
+ const mod = getModule();
714
+ mod.warnMissingModuleDeps(true, false);
715
+ expect(warnSpy).toHaveBeenCalledWith(expect.stringContaining('setup-global'));
716
+ });
717
+
718
+ it('does not warn for local installs or dry-runs', () => {
719
+ const mod = getModule();
720
+ mod.warnMissingModuleDeps(false, false);
721
+ mod.warnMissingModuleDeps(true, true);
722
+ expect(warnSpy).not.toHaveBeenCalled();
723
+ });
724
+ });
592
725
  });
@@ -13,8 +13,56 @@
13
13
  */
14
14
 
15
15
  const { EventEmitter } = require('events');
16
+ const fs = require('fs');
17
+ const os = require('os');
18
+ const path = require('path');
16
19
 
17
20
  describe('upgrade.js --apply execSync path', () => {
21
+ let cacheDir;
22
+ let savedCacheDir;
23
+ let savedHttpsGet;
24
+ let savedSpawn;
25
+ let savedReadFileSync;
26
+
27
+ beforeEach(() => {
28
+ savedCacheDir = process.env.XP_GATE_CACHE_DIR;
29
+ savedHttpsGet = require('https').get;
30
+ savedSpawn = require('child_process').spawn;
31
+ savedReadFileSync = fs.readFileSync;
32
+ cacheDir = undefined;
33
+ cacheDir = fs.mkdtempSync(path.join(os.tmpdir(), 'upgrade-cache-'));
34
+ process.env.XP_GATE_CACHE_DIR = cacheDir;
35
+ });
36
+
37
+ afterEach(() => {
38
+ if (savedCacheDir === undefined) delete process.env.XP_GATE_CACHE_DIR;
39
+ else process.env.XP_GATE_CACHE_DIR = savedCacheDir;
40
+ require('https').get = savedHttpsGet;
41
+ require('child_process').spawn = savedSpawn;
42
+ fs.readFileSync = savedReadFileSync;
43
+ if (cacheDir) fs.rmSync(cacheDir, { recursive: true, force: true });
44
+ });
45
+
46
+ function installFakeHttpsGet(latestVersion) {
47
+ const https = require('https');
48
+ const saved = https.get;
49
+ https.get = (_url, options, callbackArg) => {
50
+ const callback = typeof options === 'function' ? options : callbackArg;
51
+ const request = new EventEmitter();
52
+ request.destroy = () => undefined;
53
+ if (!callback) return request;
54
+ const response = new EventEmitter();
55
+ response.statusCode = 200;
56
+ callback(response);
57
+ response.emit('data', JSON.stringify({ latest: latestVersion }));
58
+ response.emit('end');
59
+ return request;
60
+ };
61
+ return () => {
62
+ https.get = saved;
63
+ };
64
+ }
65
+
18
66
  function evictCache() {
19
67
  const resolved = require.resolve('../upgrade');
20
68
  const cvResolved = require.resolve('../check-version');
@@ -38,39 +86,36 @@ describe('upgrade.js --apply execSync path', () => {
38
86
  return ee;
39
87
  }
40
88
 
41
- function withMockedEnv(latestVersion, spawnImpl, fn) {
42
- const fs = require('fs');
43
- const os = require('os');
44
- const cpPath = require('path').join(os.homedir(), '.xp-gate', 'version-cache.json');
89
+ async function withMockedEnv(latestVersion, spawnImpl, fn) {
90
+ const cpPath = path.join(cacheDir, 'version-cache.json');
45
91
  if (fs.existsSync(cpPath)) {
46
92
  try { fs.unlinkSync(cpPath); } catch { }
47
93
  }
48
94
  evictCache();
49
95
  const cp = require('child_process');
50
- const https = require('https');
51
- const saved = { spawn: cp.spawn, httpsGet: https.get };
96
+ const updateSkillPath = require.resolve('../update-skill');
97
+ const saved = {
98
+ spawn: cp.spawn,
99
+ updateSkillCache: require.cache[updateSkillPath],
100
+ };
52
101
  cp.spawn = spawnImpl;
53
- const body = JSON.stringify({ latest: latestVersion });
54
- https.get = (_url, _opts, cb) => {
55
- const callback = typeof _opts === 'function' ? _opts : cb;
56
- if (!callback) return { on: () => undefined, destroy: () => undefined };
57
- const mockRes = {
58
- statusCode: 200,
59
- on: (evt, handler) => {
60
- if (evt === 'data') handler(body);
61
- if (evt === 'end') handler();
62
- return mockRes;
63
- },
64
- };
65
- callback(mockRes);
66
- return { on: () => undefined, destroy: () => undefined };
102
+ require.cache[updateSkillPath] = {
103
+ id: updateSkillPath,
104
+ filename: updateSkillPath,
105
+ loaded: true,
106
+ exports: { updateSkill: vi.fn(async () => 0) },
107
+ children: [],
108
+ paths: [],
67
109
  };
110
+ const restoreHttpsGet = installFakeHttpsGet(latestVersion);
68
111
  try {
69
112
  const m = require('../upgrade');
70
- return fn(m);
113
+ return await fn(m);
71
114
  } finally {
72
115
  cp.spawn = saved.spawn;
73
- https.get = saved.httpsGet;
116
+ restoreHttpsGet();
117
+ if (saved.updateSkillCache) require.cache[updateSkillPath] = saved.updateSkillCache;
118
+ else delete require.cache[updateSkillPath];
74
119
  }
75
120
  }
76
121
 
@@ -134,17 +179,14 @@ describe('upgrade.js --apply execSync path', () => {
134
179
  }, 10000);
135
180
 
136
181
  // ── withMockedEnv variant: also mocks getLocalVersion() to return null ──
137
- function withMockedEnvNoLocal(latestVersion, spawnImpl, fn) {
138
- const fs = require('fs');
139
- const os = require('os');
140
- const cpPath = require('path').join(os.homedir(), '.xp-gate', 'version-cache.json');
182
+ async function withMockedEnvNoLocal(latestVersion, spawnImpl, fn) {
183
+ const cpPath = path.join(cacheDir, 'version-cache.json');
141
184
  if (fs.existsSync(cpPath)) {
142
185
  try { fs.unlinkSync(cpPath); } catch { }
143
186
  }
144
187
  evictCache();
145
188
  const cp = require('child_process');
146
- const https = require('https');
147
- const saved = { spawn: cp.spawn, httpsGet: https.get, fsReadFileSync: fs.readFileSync };
189
+ const saved = { spawn: cp.spawn, fsReadFileSync: fs.readFileSync };
148
190
  cp.spawn = spawnImpl;
149
191
  // Make getLocalVersion() return null by making fs.readFileSync throw for package.json
150
192
  fs.readFileSync = (filePath, encoding) => {
@@ -155,27 +197,13 @@ describe('upgrade.js --apply execSync path', () => {
155
197
  }
156
198
  return saved.fsReadFileSync.call(fs, filePath, encoding);
157
199
  };
158
- const body = JSON.stringify({ latest: latestVersion });
159
- https.get = (_url, _opts, cb) => {
160
- const callback = typeof _opts === 'function' ? _opts : cb;
161
- if (!callback) return { on: () => undefined, destroy: () => undefined };
162
- const mockRes = {
163
- statusCode: 200,
164
- on: (evt, handler) => {
165
- if (evt === 'data') handler(body);
166
- if (evt === 'end') handler();
167
- return mockRes;
168
- },
169
- };
170
- callback(mockRes);
171
- return { on: () => undefined, destroy: () => undefined };
172
- };
200
+ const restoreHttpsGet = installFakeHttpsGet(latestVersion);
173
201
  try {
174
202
  const m = require('../upgrade');
175
- return fn(m);
203
+ return await fn(m);
176
204
  } finally {
177
205
  cp.spawn = saved.spawn;
178
- https.get = saved.httpsGet;
206
+ restoreHttpsGet();
179
207
  fs.readFileSync = saved.fsReadFileSync;
180
208
  }
181
209
  }
@@ -57,7 +57,7 @@ describe('doctor skill version check — #332', () => {
57
57
 
58
58
  // AC-332-05: diagnoseInstalledSkills compares installed skills against package-bundled skills
59
59
  it('AC-332-05: diagnoseInstalledSkills reads config installedSkills and package skills dir', () => {
60
- const source = fs.readFileSync(require.resolve('../doctor.js'), 'utf8');
60
+ const source = fs.readFileSync(require.resolve('../doctor-tui.js'), 'utf8');
61
61
  expect(source).toContain('installedSkills');
62
62
  expect(source).toContain('diagnoseInstalledSkills');
63
63
  expect(source).toContain('skills');
@@ -18,8 +18,9 @@ function capture() {
18
18
  }
19
19
 
20
20
  describe('upgrade.js — REQ-001-02', () => {
21
- let mod;
22
21
  let r;
22
+ let cacheDir;
23
+ let savedCacheDir;
23
24
 
24
25
  function evictLibCache() {
25
26
  const resolved = require.resolve('../upgrade');
@@ -33,20 +34,22 @@ describe('upgrade.js — REQ-001-02', () => {
33
34
  }
34
35
 
35
36
  beforeEach(() => {
36
- // Clear disk cache so checkUpgrade doesn't see stale data from
37
- // upgrade-exec.test.js's mock (which wrote version-cache.json).
38
- const fs = require('fs');
39
- const os = require('os');
40
- const cpPath = require('path').join(os.homedir(), '.xp-gate', 'version-cache.json');
41
- if (fs.existsSync(cpPath)) { try { fs.unlinkSync(cpPath); } catch { } }
37
+ savedCacheDir = process.env.XP_GATE_CACHE_DIR;
38
+ r = undefined;
39
+ cacheDir = undefined;
40
+ cacheDir = fs.mkdtempSync(path.join(require('os').tmpdir(), 'upgrade-test-cache-'));
41
+ process.env.XP_GATE_CACHE_DIR = cacheDir;
42
42
  evictLibCache();
43
43
  vi.resetModules();
44
44
  r = capture();
45
- mod = require('../upgrade');
45
+ require('../upgrade');
46
46
  });
47
47
 
48
48
  afterEach(() => {
49
- r.restore();
49
+ r?.restore();
50
+ if (savedCacheDir === undefined) delete process.env.XP_GATE_CACHE_DIR;
51
+ else process.env.XP_GATE_CACHE_DIR = savedCacheDir;
52
+ if (cacheDir) fs.rmSync(cacheDir, { recursive: true, force: true });
50
53
  });
51
54
 
52
55
  // AC-002-01 through AC-002-06: tested in upgrade-exec.test.js (isolated, no real network)
@@ -73,5 +76,3 @@ describe('upgrade.js — REQ-001-02', () => {
73
76
  });
74
77
  });
75
78
  });
76
-
77
-
package/lib/doctor.js CHANGED
@@ -275,6 +275,7 @@ const EXPECTED_GATE_SCRIPTS = [
275
275
  'gate-7.sh',
276
276
  'gate-8.sh',
277
277
  'gate-9.sh',
278
+ 'gate-10.sh',
278
279
  ];
279
280
 
280
281
  async function checkAdapters(checks, mode, gitDirPromise) {
@@ -713,7 +714,7 @@ function fixMissingAdapters(mode, srcDir, adaptersDir) {
713
714
 
714
715
  /**
715
716
  * Restore missing gate scripts from package root to adapters directory.
716
- * Gate scripts (gate-3.sh through gate-9.sh) are stored in the package root,
717
+ * Gate scripts (gate-3.sh through gate-10.sh) are stored in the package root,
717
718
  * not in the adapters subdirectory.
718
719
  */
719
720
  function fixMissingGateScripts(srcDir, adaptersDir) {