@su-record/vibe 3.2.14 → 3.2.15

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 (294) hide show
  1. package/dist/infra/lib/codex-proxy.d.ts +126 -0
  2. package/dist/infra/lib/codex-proxy.d.ts.map +1 -1
  3. package/dist/infra/lib/codex-proxy.js +16 -16
  4. package/dist/infra/lib/codex-proxy.js.map +1 -1
  5. package/dist/infra/lib/figma/audit.d.ts.map +1 -1
  6. package/dist/infra/lib/figma/audit.js +0 -2
  7. package/dist/infra/lib/figma/audit.js.map +1 -1
  8. package/dist/infra/lib/figma/extract.d.ts +6 -1
  9. package/dist/infra/lib/figma/extract.d.ts.map +1 -1
  10. package/dist/infra/lib/figma/extract.js +17 -15
  11. package/dist/infra/lib/figma/extract.js.map +1 -1
  12. package/dist/infra/lib/figma/types.d.ts +103 -0
  13. package/dist/infra/lib/figma/types.d.ts.map +1 -1
  14. package/dist/infra/lib/figma/types.js +8 -0
  15. package/dist/infra/lib/figma/types.js.map +1 -1
  16. package/hooks/scripts/clone-extract.js +16 -1
  17. package/hooks/scripts/code-check.js +12 -5
  18. package/hooks/scripts/lib/dispatcher.js +43 -3
  19. package/package.json +10 -8
  20. package/dist/__tests__/agent-model-sync.test.d.ts +0 -2
  21. package/dist/__tests__/agent-model-sync.test.d.ts.map +0 -1
  22. package/dist/__tests__/agent-model-sync.test.js +0 -71
  23. package/dist/__tests__/agent-model-sync.test.js.map +0 -1
  24. package/dist/__tests__/architecture.test.d.ts +0 -2
  25. package/dist/__tests__/architecture.test.d.ts.map +0 -1
  26. package/dist/__tests__/architecture.test.js +0 -207
  27. package/dist/__tests__/architecture.test.js.map +0 -1
  28. package/dist/__tests__/engines-contract.test.d.ts +0 -2
  29. package/dist/__tests__/engines-contract.test.d.ts.map +0 -1
  30. package/dist/__tests__/engines-contract.test.js +0 -72
  31. package/dist/__tests__/engines-contract.test.js.map +0 -1
  32. package/dist/__tests__/instruction-drift.test.d.ts +0 -2
  33. package/dist/__tests__/instruction-drift.test.d.ts.map +0 -1
  34. package/dist/__tests__/instruction-drift.test.js +0 -116
  35. package/dist/__tests__/instruction-drift.test.js.map +0 -1
  36. package/dist/__tests__/skill-namespace.test.d.ts +0 -2
  37. package/dist/__tests__/skill-namespace.test.d.ts.map +0 -1
  38. package/dist/__tests__/skill-namespace.test.js +0 -60
  39. package/dist/__tests__/skill-namespace.test.js.map +0 -1
  40. package/dist/__tests__/stakes-contract.test.d.ts +0 -2
  41. package/dist/__tests__/stakes-contract.test.d.ts.map +0 -1
  42. package/dist/__tests__/stakes-contract.test.js +0 -173
  43. package/dist/__tests__/stakes-contract.test.js.map +0 -1
  44. package/dist/__tests__/stuck-semantics.test.d.ts +0 -2
  45. package/dist/__tests__/stuck-semantics.test.d.ts.map +0 -1
  46. package/dist/__tests__/stuck-semantics.test.js +0 -161
  47. package/dist/__tests__/stuck-semantics.test.js.map +0 -1
  48. package/dist/__tests__/wiring-integrity.test.d.ts +0 -2
  49. package/dist/__tests__/wiring-integrity.test.d.ts.map +0 -1
  50. package/dist/__tests__/wiring-integrity.test.js +0 -160
  51. package/dist/__tests__/wiring-integrity.test.js.map +0 -1
  52. package/dist/cli/commands/info.test.d.ts +0 -2
  53. package/dist/cli/commands/info.test.d.ts.map +0 -1
  54. package/dist/cli/commands/info.test.js +0 -74
  55. package/dist/cli/commands/info.test.js.map +0 -1
  56. package/dist/cli/commands/upgrade.test.d.ts +0 -2
  57. package/dist/cli/commands/upgrade.test.d.ts.map +0 -1
  58. package/dist/cli/commands/upgrade.test.js +0 -99
  59. package/dist/cli/commands/upgrade.test.js.map +0 -1
  60. package/dist/cli/design/design-md-parser.test.d.ts +0 -2
  61. package/dist/cli/design/design-md-parser.test.d.ts.map +0 -1
  62. package/dist/cli/design/design-md-parser.test.js +0 -149
  63. package/dist/cli/design/design-md-parser.test.js.map +0 -1
  64. package/dist/cli/detect.characterization.test.d.ts +0 -7
  65. package/dist/cli/detect.characterization.test.d.ts.map +0 -1
  66. package/dist/cli/detect.characterization.test.js +0 -294
  67. package/dist/cli/detect.characterization.test.js.map +0 -1
  68. package/dist/cli/postinstall/fs-utils.test.d.ts +0 -2
  69. package/dist/cli/postinstall/fs-utils.test.d.ts.map +0 -1
  70. package/dist/cli/postinstall/fs-utils.test.js +0 -259
  71. package/dist/cli/postinstall/fs-utils.test.js.map +0 -1
  72. package/dist/cli/setup/CodexHooks.test.d.ts +0 -2
  73. package/dist/cli/setup/CodexHooks.test.d.ts.map +0 -1
  74. package/dist/cli/setup/CodexHooks.test.js +0 -52
  75. package/dist/cli/setup/CodexHooks.test.js.map +0 -1
  76. package/dist/cli/setup/GlobalInstaller.test.d.ts +0 -2
  77. package/dist/cli/setup/GlobalInstaller.test.d.ts.map +0 -1
  78. package/dist/cli/setup/GlobalInstaller.test.js +0 -16
  79. package/dist/cli/setup/GlobalInstaller.test.js.map +0 -1
  80. package/dist/cli/utils/cli-detector.test.d.ts +0 -2
  81. package/dist/cli/utils/cli-detector.test.d.ts.map +0 -1
  82. package/dist/cli/utils/cli-detector.test.js +0 -52
  83. package/dist/cli/utils/cli-detector.test.js.map +0 -1
  84. package/dist/infra/lib/SkillFrontmatter.test.d.ts +0 -2
  85. package/dist/infra/lib/SkillFrontmatter.test.d.ts.map +0 -1
  86. package/dist/infra/lib/SkillFrontmatter.test.js +0 -60
  87. package/dist/infra/lib/SkillFrontmatter.test.js.map +0 -1
  88. package/dist/infra/lib/__tests__/AutomationLevel.test.d.ts +0 -2
  89. package/dist/infra/lib/__tests__/AutomationLevel.test.d.ts.map +0 -1
  90. package/dist/infra/lib/__tests__/AutomationLevel.test.js +0 -297
  91. package/dist/infra/lib/__tests__/AutomationLevel.test.js.map +0 -1
  92. package/dist/infra/lib/__tests__/ComponentRegistry.test.d.ts +0 -2
  93. package/dist/infra/lib/__tests__/ComponentRegistry.test.d.ts.map +0 -1
  94. package/dist/infra/lib/__tests__/ComponentRegistry.test.js +0 -99
  95. package/dist/infra/lib/__tests__/ComponentRegistry.test.js.map +0 -1
  96. package/dist/infra/lib/__tests__/DecisionTracer.test.d.ts +0 -2
  97. package/dist/infra/lib/__tests__/DecisionTracer.test.d.ts.map +0 -1
  98. package/dist/infra/lib/__tests__/DecisionTracer.test.js +0 -274
  99. package/dist/infra/lib/__tests__/DecisionTracer.test.js.map +0 -1
  100. package/dist/infra/lib/__tests__/InteractiveCheckpoint.test.d.ts +0 -2
  101. package/dist/infra/lib/__tests__/InteractiveCheckpoint.test.d.ts.map +0 -1
  102. package/dist/infra/lib/__tests__/InteractiveCheckpoint.test.js +0 -350
  103. package/dist/infra/lib/__tests__/InteractiveCheckpoint.test.js.map +0 -1
  104. package/dist/infra/lib/__tests__/LoopBreaker.test.d.ts +0 -2
  105. package/dist/infra/lib/__tests__/LoopBreaker.test.d.ts.map +0 -1
  106. package/dist/infra/lib/__tests__/LoopBreaker.test.js +0 -340
  107. package/dist/infra/lib/__tests__/LoopBreaker.test.js.map +0 -1
  108. package/dist/infra/lib/__tests__/VerificationLoop.test.d.ts +0 -2
  109. package/dist/infra/lib/__tests__/VerificationLoop.test.d.ts.map +0 -1
  110. package/dist/infra/lib/__tests__/VerificationLoop.test.js +0 -486
  111. package/dist/infra/lib/__tests__/VerificationLoop.test.js.map +0 -1
  112. package/dist/infra/lib/config/GlobalConfigManager.test.d.ts +0 -2
  113. package/dist/infra/lib/config/GlobalConfigManager.test.d.ts.map +0 -1
  114. package/dist/infra/lib/config/GlobalConfigManager.test.js +0 -9
  115. package/dist/infra/lib/config/GlobalConfigManager.test.js.map +0 -1
  116. package/dist/infra/lib/embedding/__tests__/EmbeddingProvider.test.d.ts +0 -2
  117. package/dist/infra/lib/embedding/__tests__/EmbeddingProvider.test.d.ts.map +0 -1
  118. package/dist/infra/lib/embedding/__tests__/EmbeddingProvider.test.js +0 -43
  119. package/dist/infra/lib/embedding/__tests__/EmbeddingProvider.test.js.map +0 -1
  120. package/dist/infra/lib/embedding/__tests__/VectorStore.test.d.ts +0 -2
  121. package/dist/infra/lib/embedding/__tests__/VectorStore.test.d.ts.map +0 -1
  122. package/dist/infra/lib/embedding/__tests__/VectorStore.test.js +0 -94
  123. package/dist/infra/lib/embedding/__tests__/VectorStore.test.js.map +0 -1
  124. package/dist/infra/lib/embedding/__tests__/cosine.test.d.ts +0 -2
  125. package/dist/infra/lib/embedding/__tests__/cosine.test.d.ts.map +0 -1
  126. package/dist/infra/lib/embedding/__tests__/cosine.test.js +0 -73
  127. package/dist/infra/lib/embedding/__tests__/cosine.test.js.map +0 -1
  128. package/dist/infra/lib/evolution/__tests__/deprecation.test.d.ts +0 -2
  129. package/dist/infra/lib/evolution/__tests__/deprecation.test.d.ts.map +0 -1
  130. package/dist/infra/lib/evolution/__tests__/deprecation.test.js +0 -251
  131. package/dist/infra/lib/evolution/__tests__/deprecation.test.js.map +0 -1
  132. package/dist/infra/lib/evolution/__tests__/eval.test.d.ts +0 -2
  133. package/dist/infra/lib/evolution/__tests__/eval.test.d.ts.map +0 -1
  134. package/dist/infra/lib/evolution/__tests__/eval.test.js +0 -603
  135. package/dist/infra/lib/evolution/__tests__/eval.test.js.map +0 -1
  136. package/dist/infra/lib/evolution/__tests__/generation.test.d.ts +0 -2
  137. package/dist/infra/lib/evolution/__tests__/generation.test.d.ts.map +0 -1
  138. package/dist/infra/lib/evolution/__tests__/generation.test.js +0 -396
  139. package/dist/infra/lib/evolution/__tests__/generation.test.js.map +0 -1
  140. package/dist/infra/lib/evolution/__tests__/insight.test.d.ts +0 -2
  141. package/dist/infra/lib/evolution/__tests__/insight.test.d.ts.map +0 -1
  142. package/dist/infra/lib/evolution/__tests__/insight.test.js +0 -204
  143. package/dist/infra/lib/evolution/__tests__/insight.test.js.map +0 -1
  144. package/dist/infra/lib/evolution/__tests__/integration.test.d.ts +0 -2
  145. package/dist/infra/lib/evolution/__tests__/integration.test.d.ts.map +0 -1
  146. package/dist/infra/lib/evolution/__tests__/integration.test.js +0 -206
  147. package/dist/infra/lib/evolution/__tests__/integration.test.js.map +0 -1
  148. package/dist/infra/lib/evolution/__tests__/lifecycle.test.d.ts +0 -2
  149. package/dist/infra/lib/evolution/__tests__/lifecycle.test.d.ts.map +0 -1
  150. package/dist/infra/lib/evolution/__tests__/lifecycle.test.js +0 -350
  151. package/dist/infra/lib/evolution/__tests__/lifecycle.test.js.map +0 -1
  152. package/dist/infra/lib/evolution/__tests__/parity.test.d.ts +0 -2
  153. package/dist/infra/lib/evolution/__tests__/parity.test.d.ts.map +0 -1
  154. package/dist/infra/lib/evolution/__tests__/parity.test.js +0 -319
  155. package/dist/infra/lib/evolution/__tests__/parity.test.js.map +0 -1
  156. package/dist/infra/lib/llm/auth/AuthProfileManager.test.d.ts +0 -5
  157. package/dist/infra/lib/llm/auth/AuthProfileManager.test.d.ts.map +0 -1
  158. package/dist/infra/lib/llm/auth/AuthProfileManager.test.js +0 -168
  159. package/dist/infra/lib/llm/auth/AuthProfileManager.test.js.map +0 -1
  160. package/dist/infra/lib/llm/auth/TokenRefresher.test.d.ts +0 -2
  161. package/dist/infra/lib/llm/auth/TokenRefresher.test.d.ts.map +0 -1
  162. package/dist/infra/lib/llm/auth/TokenRefresher.test.js +0 -120
  163. package/dist/infra/lib/llm/auth/TokenRefresher.test.js.map +0 -1
  164. package/dist/infra/lib/llm/timeout.test.d.ts +0 -2
  165. package/dist/infra/lib/llm/timeout.test.d.ts.map +0 -1
  166. package/dist/infra/lib/llm/timeout.test.js +0 -50
  167. package/dist/infra/lib/llm/timeout.test.js.map +0 -1
  168. package/dist/infra/lib/llm-availability.test.d.ts +0 -2
  169. package/dist/infra/lib/llm-availability.test.d.ts.map +0 -1
  170. package/dist/infra/lib/llm-availability.test.js +0 -37
  171. package/dist/infra/lib/llm-availability.test.js.map +0 -1
  172. package/dist/infra/lib/memory/KnowledgeGraph.test.d.ts +0 -2
  173. package/dist/infra/lib/memory/KnowledgeGraph.test.d.ts.map +0 -1
  174. package/dist/infra/lib/memory/KnowledgeGraph.test.js +0 -189
  175. package/dist/infra/lib/memory/KnowledgeGraph.test.js.map +0 -1
  176. package/dist/infra/lib/memory/MemorySearch.test.d.ts +0 -2
  177. package/dist/infra/lib/memory/MemorySearch.test.d.ts.map +0 -1
  178. package/dist/infra/lib/memory/MemorySearch.test.js +0 -149
  179. package/dist/infra/lib/memory/MemorySearch.test.js.map +0 -1
  180. package/dist/infra/lib/memory/MemoryStorage.test.d.ts +0 -2
  181. package/dist/infra/lib/memory/MemoryStorage.test.d.ts.map +0 -1
  182. package/dist/infra/lib/memory/MemoryStorage.test.js +0 -198
  183. package/dist/infra/lib/memory/MemoryStorage.test.js.map +0 -1
  184. package/dist/infra/lib/memory/SessionRAGRetriever.test.d.ts +0 -2
  185. package/dist/infra/lib/memory/SessionRAGRetriever.test.d.ts.map +0 -1
  186. package/dist/infra/lib/memory/SessionRAGRetriever.test.js +0 -180
  187. package/dist/infra/lib/memory/SessionRAGRetriever.test.js.map +0 -1
  188. package/dist/infra/lib/memory/SessionRAGStore.test.d.ts +0 -2
  189. package/dist/infra/lib/memory/SessionRAGStore.test.d.ts.map +0 -1
  190. package/dist/infra/lib/memory/SessionRAGStore.test.js +0 -326
  191. package/dist/infra/lib/memory/SessionRAGStore.test.js.map +0 -1
  192. package/dist/infra/lib/memory/__tests__/InMemoryStorage.test.d.ts +0 -2
  193. package/dist/infra/lib/memory/__tests__/InMemoryStorage.test.d.ts.map +0 -1
  194. package/dist/infra/lib/memory/__tests__/InMemoryStorage.test.js +0 -114
  195. package/dist/infra/lib/memory/__tests__/InMemoryStorage.test.js.map +0 -1
  196. package/dist/infra/lib/memory/__tests__/ReflectionStore.test.d.ts +0 -2
  197. package/dist/infra/lib/memory/__tests__/ReflectionStore.test.d.ts.map +0 -1
  198. package/dist/infra/lib/memory/__tests__/ReflectionStore.test.js +0 -196
  199. package/dist/infra/lib/memory/__tests__/ReflectionStore.test.js.map +0 -1
  200. package/dist/infra/lib/memory/index.test.d.ts +0 -2
  201. package/dist/infra/lib/memory/index.test.d.ts.map +0 -1
  202. package/dist/infra/lib/memory/index.test.js +0 -23
  203. package/dist/infra/lib/memory/index.test.js.map +0 -1
  204. package/dist/infra/lib/telemetry/SkillTelemetry.test.d.ts +0 -2
  205. package/dist/infra/lib/telemetry/SkillTelemetry.test.d.ts.map +0 -1
  206. package/dist/infra/lib/telemetry/SkillTelemetry.test.js +0 -124
  207. package/dist/infra/lib/telemetry/SkillTelemetry.test.js.map +0 -1
  208. package/dist/infra/lib/ui-ux/Bm25Engine.test.d.ts +0 -2
  209. package/dist/infra/lib/ui-ux/Bm25Engine.test.d.ts.map +0 -1
  210. package/dist/infra/lib/ui-ux/Bm25Engine.test.js +0 -159
  211. package/dist/infra/lib/ui-ux/Bm25Engine.test.js.map +0 -1
  212. package/dist/infra/lib/ui-ux/CsvDataLoader.test.d.ts +0 -2
  213. package/dist/infra/lib/ui-ux/CsvDataLoader.test.d.ts.map +0 -1
  214. package/dist/infra/lib/ui-ux/CsvDataLoader.test.js +0 -141
  215. package/dist/infra/lib/ui-ux/CsvDataLoader.test.js.map +0 -1
  216. package/dist/infra/lib/ui-ux/DesignSystemGenerator.test.d.ts +0 -2
  217. package/dist/infra/lib/ui-ux/DesignSystemGenerator.test.d.ts.map +0 -1
  218. package/dist/infra/lib/ui-ux/DesignSystemGenerator.test.js +0 -370
  219. package/dist/infra/lib/ui-ux/DesignSystemGenerator.test.js.map +0 -1
  220. package/dist/infra/lib/ui-ux/SearchService.test.d.ts +0 -2
  221. package/dist/infra/lib/ui-ux/SearchService.test.d.ts.map +0 -1
  222. package/dist/infra/lib/ui-ux/SearchService.test.js +0 -215
  223. package/dist/infra/lib/ui-ux/SearchService.test.js.map +0 -1
  224. package/dist/test-helpers/index.d.ts +0 -36
  225. package/dist/test-helpers/index.d.ts.map +0 -1
  226. package/dist/test-helpers/index.js +0 -85
  227. package/dist/test-helpers/index.js.map +0 -1
  228. package/dist/test-helpers/index.test.d.ts +0 -2
  229. package/dist/test-helpers/index.test.d.ts.map +0 -1
  230. package/dist/test-helpers/index.test.js +0 -92
  231. package/dist/test-helpers/index.test.js.map +0 -1
  232. package/dist/tests/curated-release-notes.test.d.ts +0 -2
  233. package/dist/tests/curated-release-notes.test.d.ts.map +0 -1
  234. package/dist/tests/curated-release-notes.test.js +0 -160
  235. package/dist/tests/curated-release-notes.test.js.map +0 -1
  236. package/dist/tools/convention/analyzeComplexity.test.d.ts +0 -2
  237. package/dist/tools/convention/analyzeComplexity.test.d.ts.map +0 -1
  238. package/dist/tools/convention/analyzeComplexity.test.js +0 -207
  239. package/dist/tools/convention/analyzeComplexity.test.js.map +0 -1
  240. package/dist/tools/convention/validateCodeQuality.test.d.ts +0 -2
  241. package/dist/tools/convention/validateCodeQuality.test.d.ts.map +0 -1
  242. package/dist/tools/convention/validateCodeQuality.test.js +0 -230
  243. package/dist/tools/convention/validateCodeQuality.test.js.map +0 -1
  244. package/dist/tools/loop/validateLoopDefinition.test.d.ts +0 -14
  245. package/dist/tools/loop/validateLoopDefinition.test.d.ts.map +0 -1
  246. package/dist/tools/loop/validateLoopDefinition.test.js +0 -229
  247. package/dist/tools/loop/validateLoopDefinition.test.js.map +0 -1
  248. package/dist/tools/spec/executionPacket.test.d.ts +0 -2
  249. package/dist/tools/spec/executionPacket.test.d.ts.map +0 -1
  250. package/dist/tools/spec/executionPacket.test.js +0 -374
  251. package/dist/tools/spec/executionPacket.test.js.map +0 -1
  252. package/dist/tools/spec/prdParser.test.d.ts +0 -6
  253. package/dist/tools/spec/prdParser.test.d.ts.map +0 -1
  254. package/dist/tools/spec/prdParser.test.js +0 -332
  255. package/dist/tools/spec/prdParser.test.js.map +0 -1
  256. package/dist/tools/spec/specGenerator.test.d.ts +0 -2
  257. package/dist/tools/spec/specGenerator.test.d.ts.map +0 -1
  258. package/dist/tools/spec/specGenerator.test.js +0 -130
  259. package/dist/tools/spec/specGenerator.test.js.map +0 -1
  260. package/dist/tools/spec/traceabilityMatrix.path-resolution.test.d.ts +0 -10
  261. package/dist/tools/spec/traceabilityMatrix.path-resolution.test.d.ts.map +0 -1
  262. package/dist/tools/spec/traceabilityMatrix.path-resolution.test.js +0 -120
  263. package/dist/tools/spec/traceabilityMatrix.path-resolution.test.js.map +0 -1
  264. package/dist/tools/spec/traceabilityMatrix.test.d.ts +0 -6
  265. package/dist/tools/spec/traceabilityMatrix.test.d.ts.map +0 -1
  266. package/dist/tools/spec/traceabilityMatrix.test.js +0 -359
  267. package/dist/tools/spec/traceabilityMatrix.test.js.map +0 -1
  268. package/hooks/scripts/__tests__/.vibe/command-log.txt +0 -3
  269. package/hooks/scripts/__tests__/anchor-inbox.test.js +0 -119
  270. package/hooks/scripts/__tests__/auto-test-debounce.test.js +0 -145
  271. package/hooks/scripts/__tests__/clone-behaviors.test.js +0 -152
  272. package/hooks/scripts/__tests__/clone-merge-responsive.test.js +0 -106
  273. package/hooks/scripts/__tests__/code-check-detectors.test.js +0 -213
  274. package/hooks/scripts/__tests__/code-check-false-positive.test.js +0 -74
  275. package/hooks/scripts/__tests__/code-check-memory-write.test.js +0 -81
  276. package/hooks/scripts/__tests__/codex-hook-adapter.test.js +0 -64
  277. package/hooks/scripts/__tests__/curation-index.test.js +0 -157
  278. package/hooks/scripts/__tests__/dispatcher-inprocess.test.js +0 -99
  279. package/hooks/scripts/__tests__/figma-extract.test.js +0 -338
  280. package/hooks/scripts/__tests__/fixtures/seq-harness.js +0 -13
  281. package/hooks/scripts/__tests__/fixtures/seq-step.js +0 -22
  282. package/hooks/scripts/__tests__/hook-payload.test.js +0 -29
  283. package/hooks/scripts/__tests__/llm-orchestrate-antigravity.test.js +0 -66
  284. package/hooks/scripts/__tests__/loop-ledger.test.js +0 -321
  285. package/hooks/scripts/__tests__/post-edit-dispatcher.test.js +0 -139
  286. package/hooks/scripts/__tests__/pre-tool-guard.test.js +0 -523
  287. package/hooks/scripts/__tests__/recipe-extractor.test.js +0 -244
  288. package/hooks/scripts/__tests__/run-ledger-verify-required.test.js +0 -151
  289. package/hooks/scripts/__tests__/run-ledger.test.js +0 -481
  290. package/hooks/scripts/__tests__/scope-from-spec.test.js +0 -215
  291. package/hooks/scripts/__tests__/sentinel-guard.test.js +0 -263
  292. package/hooks/scripts/__tests__/step-counter.test.js +0 -459
  293. package/hooks/scripts/__tests__/stop-dispatcher-sequential.test.js +0 -74
  294. package/hooks/scripts/__tests__/utils-npm-root.test.js +0 -99
@@ -1,459 +0,0 @@
1
- /**
2
- * step-counter.js — Phase 1 + Phase 2 테스트
3
- *
4
- * 검증 범위:
5
- * - 책임 1) current-run.json steps 증가 (회귀 테스트)
6
- * - 책임 2) current-run.jsonl append (Phase 1)
7
- * - 책임 3) error_category 분류기 + 3-fail detector → anti-pattern md (Phase 2)
8
- * - 책임 간 독립성, hot-path 안정성
9
- *
10
- * 패턴: 훅 스크립트 공통의 execFileSync 방식.
11
- */
12
- import { describe, it, expect, beforeEach } from 'vitest';
13
- import { spawnSync } from 'child_process';
14
- import fs from 'fs';
15
- import os from 'os';
16
- import path from 'path';
17
- import { fileURLToPath } from 'url';
18
-
19
- const __dirname = path.dirname(fileURLToPath(import.meta.url));
20
- const SCRIPT = path.resolve(__dirname, '..', 'step-counter.js');
21
-
22
- /**
23
- * 격리된 임시 프로젝트에서 step-counter 를 1회 실행.
24
- * stdin 으로 PostToolUse payload 전달.
25
- */
26
- function runCounter({ payload, projectDir }) {
27
- const result = spawnSync('node', [SCRIPT], {
28
- input: payload ? JSON.stringify(payload) : '',
29
- encoding: 'utf-8',
30
- timeout: 5000,
31
- env: {
32
- ...process.env,
33
- CLAUDE_PROJECT_DIR: projectDir,
34
- // VIBE_HOOK_DEPTH 미설정 — 재귀 가드 영향 없음
35
- },
36
- });
37
- return result;
38
- }
39
-
40
- function makeTmpProject() {
41
- const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'vibe-step-counter-'));
42
- // .vibe/metrics/ 는 step-counter 가 직접 생성하도록 미리 만들지 않음
43
- return dir;
44
- }
45
-
46
- function readJson(p) {
47
- return JSON.parse(fs.readFileSync(p, 'utf-8'));
48
- }
49
-
50
- function readJsonl(p) {
51
- return fs.readFileSync(p, 'utf-8').split('\n').filter(Boolean).map((l) => JSON.parse(l));
52
- }
53
-
54
- describe('step-counter PostToolUse hook', () => {
55
- let projectDir;
56
- let runJson;
57
- let runJsonl;
58
-
59
- beforeEach(() => {
60
- projectDir = makeTmpProject();
61
- runJson = path.join(projectDir, '.vibe', 'metrics', 'current-run.json');
62
- runJsonl = path.join(projectDir, '.vibe', 'metrics', 'current-run.jsonl');
63
- });
64
-
65
- // ───────── 책임 1 회귀 ─────────
66
- describe('책임 1: current-run.json 카운터', () => {
67
- it('첫 호출 시 steps=1, startedAt 채움', () => {
68
- const r = runCounter({
69
- payload: { tool_name: 'Bash', tool_input: { command: 'ls' }, tool_response: { is_error: false } },
70
- projectDir,
71
- });
72
- expect(r.status).toBe(0);
73
- const data = readJson(runJson);
74
- expect(data.steps).toBe(1);
75
- expect(data.startedAt).toMatch(/^\d{4}-\d{2}-\d{2}T/);
76
- });
77
-
78
- it('연속 호출 시 steps 누적 (10이벤트 재작성 주기 고려)', () => {
79
- // 10이벤트마다 재작성 — 10회 호출 후에는 steps=10 이 보장됨
80
- for (let i = 0; i < 10; i++) {
81
- runCounter({
82
- payload: { tool_name: 'Bash', tool_input: { command: 'echo hi' }, tool_response: {} },
83
- projectDir,
84
- });
85
- }
86
- const data = readJson(runJson);
87
- expect(data.steps).toBe(10);
88
- });
89
-
90
- it('손상된 JSON 이 있어도 새로 시작 (10회 후 재작성으로 확인)', () => {
91
- fs.mkdirSync(path.join(projectDir, '.vibe', 'metrics'), { recursive: true });
92
- fs.writeFileSync(runJson, '{ broken json');
93
- // 10회 호출해야 재작성 주기 도달
94
- for (let i = 0; i < 10; i++) {
95
- runCounter({
96
- payload: { tool_name: 'Bash', tool_input: {}, tool_response: {} },
97
- projectDir,
98
- });
99
- }
100
- const data = readJson(runJson);
101
- expect(data.steps).toBe(10);
102
- expect(data.startedAt).toMatch(/^\d{4}-\d{2}-\d{2}T/);
103
- });
104
- });
105
-
106
- // ───────── 책임 2 신규 ─────────
107
- describe('책임 2: current-run.jsonl 로깅', () => {
108
- it('툴콜 1회 = jsonl 1라인', () => {
109
- runCounter({
110
- payload: { tool_name: 'Edit', tool_input: { file_path: 'src/foo.ts' }, tool_response: {} },
111
- projectDir,
112
- });
113
- const lines = readJsonl(runJsonl);
114
- expect(lines).toHaveLength(1);
115
- expect(lines[0].tool).toBe('Edit');
116
- expect(lines[0].ok).toBe(true);
117
- expect(lines[0].target_file).toBe('src/foo.ts');
118
- expect(lines[0].error_category).toBeNull();
119
- expect(lines[0].ts).toMatch(/^\d{4}-\d{2}-\d{2}T/);
120
- });
121
-
122
- it('tool_response.is_error=true 면 ok=false', () => {
123
- runCounter({
124
- payload: { tool_name: 'Bash', tool_input: { command: 'false' }, tool_response: { is_error: true } },
125
- projectDir,
126
- });
127
- const [line] = readJsonl(runJsonl);
128
- expect(line.ok).toBe(false);
129
- });
130
-
131
- it('절대 경로 file_path 를 프로젝트 상대 경로로 정규화', () => {
132
- const abs = path.join(projectDir, 'src', 'bar.ts');
133
- runCounter({
134
- payload: { tool_name: 'Write', tool_input: { file_path: abs }, tool_response: {} },
135
- projectDir,
136
- });
137
- const [line] = readJsonl(runJsonl);
138
- expect(line.target_file).toBe('src/bar.ts');
139
- });
140
-
141
- it('file_path 없는 툴콜은 target_file=null', () => {
142
- runCounter({
143
- payload: { tool_name: 'Bash', tool_input: { command: 'pwd' }, tool_response: {} },
144
- projectDir,
145
- });
146
- const [line] = readJsonl(runJsonl);
147
- expect(line.target_file).toBeNull();
148
- });
149
-
150
- it('tool_name 없으면 jsonl 라인 안 씀 (steps 는 증가)', () => {
151
- runCounter({ payload: {}, projectDir });
152
- expect(fs.existsSync(runJsonl)).toBe(false);
153
- expect(readJson(runJson).steps).toBe(1);
154
- });
155
-
156
- it('연속 호출 시 jsonl 누적 (Read는 조기 종료 — 기록 안 됨)', () => {
157
- // Read 는 READ_ONLY_TOOLS 조기 종료 → jsonl 미기록
158
- runCounter({
159
- payload: { tool_name: 'Read', tool_input: { file_path: 'a.ts' }, tool_response: {} },
160
- projectDir,
161
- });
162
- runCounter({
163
- payload: { tool_name: 'Edit', tool_input: { file_path: 'a.ts' }, tool_response: {} },
164
- projectDir,
165
- });
166
- const lines = readJsonl(runJsonl);
167
- expect(lines).toHaveLength(1);
168
- expect(lines[0].tool).toBe('Edit');
169
- });
170
- });
171
-
172
- // ───────── 독립성 ─────────
173
- describe('두 책임의 독립성', () => {
174
- it('payload 가 비어 stdin 무효여도 카운터는 증가', () => {
175
- const r = runCounter({ payload: null, projectDir });
176
- expect(r.status).toBe(0);
177
- expect(readJson(runJson).steps).toBe(1);
178
- });
179
- });
180
-
181
- // ───────── Phase 2: error_category 분류 ─────────
182
- describe('책임 3a: error_category 분류기', () => {
183
- it('TypeError of undefined → nullability', () => {
184
- runCounter({
185
- payload: {
186
- tool_name: 'Bash',
187
- tool_input: { command: 'node x.js' },
188
- tool_response: { is_error: true, error: "TypeError: Cannot read properties of undefined (reading 'foo')" },
189
- },
190
- projectDir,
191
- });
192
- const [line] = readJsonl(runJsonl);
193
- expect(line.error_category).toBe('nullability');
194
- });
195
-
196
- it('TS2345 → type-narrow', () => {
197
- runCounter({
198
- payload: {
199
- tool_name: 'Bash',
200
- tool_input: { command: 'tsc' },
201
- tool_response: { is_error: true, error: "src/x.ts(3,4): error TS2345: Argument of type 'string' is not assignable to parameter of type 'number'." },
202
- },
203
- projectDir,
204
- });
205
- const [line] = readJsonl(runJsonl);
206
- expect(line.error_category).toBe('type-narrow');
207
- });
208
-
209
- it('ECONNREFUSED → integration', () => {
210
- runCounter({
211
- payload: {
212
- tool_name: 'Bash',
213
- tool_input: { command: 'curl' },
214
- tool_response: { is_error: true, error: 'connect ECONNREFUSED 127.0.0.1:5432' },
215
- },
216
- projectDir,
217
- });
218
- const [line] = readJsonl(runJsonl);
219
- expect(line.error_category).toBe('integration');
220
- });
221
-
222
- it('알 수 없는 에러 → other', () => {
223
- runCounter({
224
- payload: {
225
- tool_name: 'Bash',
226
- tool_input: { command: 'x' },
227
- tool_response: { is_error: true, error: 'some unrecognized failure mode 12345' },
228
- },
229
- projectDir,
230
- });
231
- const [line] = readJsonl(runJsonl);
232
- expect(line.error_category).toBe('other');
233
- });
234
-
235
- it('성공 툴콜은 error_category=null', () => {
236
- runCounter({
237
- payload: { tool_name: 'Bash', tool_input: { command: 'ls' }, tool_response: { is_error: false } },
238
- projectDir,
239
- });
240
- const [line] = readJsonl(runJsonl);
241
- expect(line.ok).toBe(true);
242
- expect(line.error_category).toBeNull();
243
- });
244
- });
245
-
246
- // ───────── Phase 2: 3-fail detector ─────────
247
- describe('책임 3b: 3-fail detector → anti-pattern md', () => {
248
- function failBash(command, errorText, projectDir) {
249
- runCounter({
250
- payload: {
251
- tool_name: 'Bash',
252
- tool_input: { command },
253
- tool_response: { is_error: true, error: errorText },
254
- },
255
- projectDir,
256
- });
257
- }
258
-
259
- function listAntiPatterns(projectDir) {
260
- const dir = path.join(projectDir, '.vibe', 'anti-patterns');
261
- if (!fs.existsSync(dir)) return [];
262
- return fs.readdirSync(dir).filter((f) => f.endsWith('.md'));
263
- }
264
-
265
- it('같은 (file, category) 3회 누적 시 anti-pattern md 생성', () => {
266
- const err = "TypeError: Cannot read properties of undefined (reading 'x')";
267
- // file_path 없는 Bash 실패 3회 → target_file=null, category=nullability
268
- for (let i = 0; i < 3; i++) failBash(`run-${i}`, err, projectDir);
269
- const files = listAntiPatterns(projectDir);
270
- expect(files).toHaveLength(1);
271
- expect(files[0]).toMatch(/^nullability__global__\d{8}\.md$/);
272
- });
273
-
274
- it('2회만 누적되면 생성 안 함', () => {
275
- const err = 'connect ECONNREFUSED foo';
276
- for (let i = 0; i < 2; i++) failBash(`x-${i}`, err, projectDir);
277
- expect(listAntiPatterns(projectDir)).toHaveLength(0);
278
- });
279
-
280
- it('3회지만 카테고리 다르면 생성 안 함', () => {
281
- failBash('a', "Cannot read properties of undefined", projectDir); // nullability
282
- failBash('b', "ECONNREFUSED", projectDir); // integration
283
- failBash('c', "TS2345 not assignable", projectDir); // type-narrow
284
- expect(listAntiPatterns(projectDir)).toHaveLength(0);
285
- });
286
-
287
- it('동일 (file, category) 4회 — md 1개만 (dedup)', () => {
288
- const err = "TypeError: Cannot read properties of undefined";
289
- for (let i = 0; i < 4; i++) failBash(`r-${i}`, err, projectDir);
290
- expect(listAntiPatterns(projectDir)).toHaveLength(1);
291
- });
292
-
293
- it('frontmatter 가 schema 충족', () => {
294
- const err = "TypeError: Cannot read properties of null";
295
- for (let i = 0; i < 3; i++) failBash(`r-${i}`, err, projectDir);
296
- const [filename] = listAntiPatterns(projectDir);
297
- const content = fs.readFileSync(path.join(projectDir, '.vibe', 'anti-patterns', filename), 'utf-8');
298
- expect(content).toMatch(/^---\n/);
299
- expect(content).toMatch(/^slug: nullability__global__\d{8}$/m);
300
- expect(content).toMatch(/^type: anti-pattern$/m);
301
- expect(content).toMatch(/^root-cause-tag: nullability$/m);
302
- expect(content).toMatch(/^trigger-signature: "/m);
303
- expect(content).toMatch(/^fail-count: 3$/m);
304
- expect(content).toMatch(/^suggested-stop: "/m);
305
- expect(content).toMatch(/^created: \d{4}-\d{2}-\d{2}$/m);
306
- });
307
-
308
- it('파일 경로 있는 실패는 file slug 사용', () => {
309
- const filePath = 'src/cli/foo.ts';
310
- const err = "TypeError: Cannot read properties of undefined";
311
- for (let i = 0; i < 3; i++) {
312
- runCounter({
313
- payload: {
314
- tool_name: 'Edit',
315
- tool_input: { file_path: filePath },
316
- tool_response: { is_error: true, error: err },
317
- },
318
- projectDir,
319
- });
320
- }
321
- const [filename] = listAntiPatterns(projectDir);
322
- expect(filename).toMatch(/^nullability__src-cli-foo-ts__\d{8}\.md$/);
323
- });
324
-
325
- it('윈도우 외 실패는 카운트 안 됨', () => {
326
- const err = "TypeError: Cannot read properties of undefined";
327
- // 같은 카테고리 실패 2회
328
- failBash('a', err, projectDir);
329
- failBash('b', err, projectDir);
330
- // 성공 툴콜로 윈도우 채움 (10줄 이상) — Bash 를 사용 (Read 는 조기 종료로 jsonl 미기록)
331
- for (let i = 0; i < 10; i++) {
332
- runCounter({
333
- payload: { tool_name: 'Bash', tool_input: { command: `echo ${i}` }, tool_response: {} },
334
- projectDir,
335
- });
336
- }
337
- // 마지막 실패 1회 — 윈도우(10줄) 안에는 이 실패 + 직전 성공만 있으므로 트립 안 함
338
- failBash('c', err, projectDir);
339
- expect(listAntiPatterns(projectDir)).toHaveLength(0);
340
- });
341
- });
342
-
343
- // ───────── 차단 금지 ─────────
344
- describe('hot path 안정성', () => {
345
- it('항상 exit 0', () => {
346
- const r = runCounter({
347
- payload: { tool_name: 'Bash', tool_input: { command: 'x' }, tool_response: {} },
348
- projectDir,
349
- });
350
- expect(r.status).toBe(0);
351
- });
352
-
353
- it('잘못된 stdin JSON 도 차단 안 함', () => {
354
- const r = spawnSync('node', [SCRIPT], {
355
- input: 'not json at all',
356
- encoding: 'utf-8',
357
- timeout: 5000,
358
- env: { ...process.env, CLAUDE_PROJECT_DIR: projectDir },
359
- });
360
- expect(r.status).toBe(0);
361
- });
362
- });
363
-
364
- // ───────── 읽기 전용 도구 조기 종료 (defense-in-depth) ─────────
365
- describe('읽기 전용 도구 조기 종료', () => {
366
- const READ_ONLY = ['Read', 'Grep', 'Glob', 'WebFetch', 'WebSearch', 'TodoWrite', 'ToolSearch', 'ListMcpResourcesTool'];
367
-
368
- it.each(READ_ONLY)('%s 호출 시 파일 미생성 + exit 0', (toolName) => {
369
- const r = runCounter({
370
- payload: { tool_name: toolName, tool_input: { file_path: 'src/foo.ts' }, tool_response: {} },
371
- projectDir,
372
- });
373
- expect(r.status).toBe(0);
374
- // jsonl에 기록하지 않아야 함
375
- const jsonlPath = path.join(projectDir, '.vibe', 'metrics', 'current-run.jsonl');
376
- expect(fs.existsSync(jsonlPath)).toBe(false);
377
- // current-run.json 도 생성하지 않아야 함
378
- const jsonPath = path.join(projectDir, '.vibe', 'metrics', 'current-run.json');
379
- expect(fs.existsSync(jsonPath)).toBe(false);
380
- });
381
- });
382
-
383
- // ───────── 쓰기 스로틀 ─────────
384
- describe('쓰기 스로틀: jsonl 항상 append, json 조건부 재작성', () => {
385
- it('첫 번째 이벤트는 current-run.json 생성 (파일 없음 → 스로틀 없음)', () => {
386
- runCounter({
387
- payload: { tool_name: 'Bash', tool_input: { command: 'x' }, tool_response: {} },
388
- projectDir,
389
- });
390
- expect(fs.existsSync(runJson)).toBe(true);
391
- expect(readJson(runJson).steps).toBe(1);
392
- });
393
-
394
- it('10이벤트마다 강제 재작성: 10회 후 steps=jsonl라인수=10', () => {
395
- for (let i = 0; i < 10; i++) {
396
- runCounter({
397
- payload: { tool_name: 'Edit', tool_input: { file_path: 'a.ts' }, tool_response: {} },
398
- projectDir,
399
- });
400
- }
401
- // 10번째 이벤트에서 강제 재작성 (10%10===0) — steps = jsonl 라인 수 = 10
402
- const data = readJson(runJson);
403
- expect(data.steps).toBe(10);
404
- // jsonl 도 10줄
405
- const lines = readJsonl(runJsonl);
406
- expect(lines).toHaveLength(10);
407
- });
408
-
409
- it('jsonl 은 스로틀 무관 항상 즉시 append', () => {
410
- for (let i = 0; i < 5; i++) {
411
- runCounter({
412
- payload: { tool_name: 'Bash', tool_input: { command: `cmd-${i}` }, tool_response: {} },
413
- projectDir,
414
- });
415
- }
416
- const lines = readJsonl(runJsonl);
417
- expect(lines).toHaveLength(5);
418
- });
419
-
420
- it('3-fail 감지는 jsonl 기반 — json 스로틀 후에도 동작', () => {
421
- const err = "TypeError: Cannot read properties of undefined (reading 'x')";
422
- // 3회 실패 (jsonl에 즉시 기록되므로 감지 가능)
423
- for (let i = 0; i < 3; i++) {
424
- runCounter({
425
- payload: {
426
- tool_name: 'Bash',
427
- tool_input: { command: `run-${i}` },
428
- tool_response: { is_error: true, error: err },
429
- },
430
- projectDir,
431
- });
432
- }
433
- const apDir = path.join(projectDir, '.vibe', 'anti-patterns');
434
- const files = fs.existsSync(apDir) ? fs.readdirSync(apDir).filter(f => f.endsWith('.md')) : [];
435
- expect(files).toHaveLength(1);
436
- });
437
- });
438
-
439
- // ───────── A2/D2: 64KB 초과 stdin 페이로드 (구 단일 버퍼 리더 절단 버그 회귀) ─────────
440
- describe('stdin 64KB 초과 페이로드 — 절단 없이 처리', () => {
441
- it('페이로드가 64KB를 넘어도 JSON 전체를 읽어 target_file을 정확히 기록', () => {
442
- // 구현이 64KB 단일 버퍼로 되돌아가면 JSON이 중간에 잘려 파싱 실패 →
443
- // stdinPayload=null → jsonl 라인 자체가 기록되지 않는다.
444
- const padding = 'x'.repeat(80000); // 총 페이로드 > 64KB 보장
445
- runCounter({
446
- payload: {
447
- tool_name: 'Edit',
448
- tool_input: { file_path: 'src/foo.ts', padding },
449
- tool_response: {},
450
- },
451
- projectDir,
452
- });
453
- const lines = readJsonl(runJsonl);
454
- expect(lines).toHaveLength(1);
455
- expect(lines[0].target_file).toBe('src/foo.ts');
456
- expect(lines[0].tool).toBe('Edit');
457
- });
458
- });
459
- });
@@ -1,74 +0,0 @@
1
- /**
2
- * dispatch() 순차 실행 계약 회귀 테스트
3
- *
4
- * stop-dispatcher.js 는 "auto-commit 의 git cascade 와 겹쳐 프로세스 폭주" 를 막기 위해
5
- * 순차 실행을 계약으로 문서화한다(stop-dispatcher.js 헤더). 그러나 dispatch() 가
6
- * Promise.all 로 구현돼 있어 실제로는 병렬이었다 — 이 테스트가 그 회귀를 막는다.
7
- *
8
- * 검증 대상은 내부 구현이 아니라 관측 가능한 실행 구간 겹침이다.
9
- */
10
- import { describe, it, expect, beforeEach, afterEach } from 'vitest';
11
- import { execFileSync } from 'child_process';
12
- import fs from 'fs';
13
- import os from 'os';
14
- import path from 'path';
15
- import { fileURLToPath } from 'url';
16
-
17
- const __dirname = path.dirname(fileURLToPath(import.meta.url));
18
- const FIXTURES = path.resolve(__dirname, 'fixtures');
19
- const HARNESS = path.join(FIXTURES, 'seq-harness.js');
20
-
21
- let traceDir;
22
- let tracePath;
23
-
24
- beforeEach(() => {
25
- traceDir = fs.mkdtempSync(path.join(os.tmpdir(), 'vibe-seq-'));
26
- tracePath = path.join(traceDir, 'trace.log');
27
- fs.writeFileSync(tracePath, '');
28
- });
29
-
30
- afterEach(() => {
31
- fs.rmSync(traceDir, { recursive: true, force: true });
32
- });
33
-
34
- function runHarness() {
35
- execFileSync('node', [HARNESS], {
36
- encoding: 'utf-8',
37
- input: JSON.stringify({ tool_name: 'Stop' }),
38
- timeout: 20000,
39
- env: {
40
- ...process.env,
41
- VIBE_SEQ_TRACE: tracePath,
42
- VIBE_SEQ_HOLD_MS: '120',
43
- // config.json 이 없는 디렉터리 → 모든 step enabled
44
- CLAUDE_PROJECT_DIR: traceDir,
45
- },
46
- });
47
- return fs.readFileSync(tracePath, 'utf-8').trim().split('\n').filter(Boolean);
48
- }
49
-
50
- describe('dispatch() 순차 실행 계약', () => {
51
- it('스텝 실행 구간이 겹치지 않는다 (start 직후 항상 같은 스텝의 end)', () => {
52
- const trace = runHarness();
53
-
54
- expect(trace).toHaveLength(6);
55
- for (let i = 0; i < trace.length; i += 2) {
56
- const [startName, startMark] = trace[i].split(':');
57
- const [endName, endMark] = trace[i + 1].split(':');
58
- expect(startMark).toBe('start');
59
- expect(endMark).toBe('end');
60
- // 병렬이면 여기서 다른 스텝의 start 가 끼어든다
61
- expect(endName).toBe(startName);
62
- }
63
- });
64
-
65
- it('선언된 순서(a → b → c)를 그대로 지킨다', () => {
66
- const trace = runHarness();
67
-
68
- expect(trace).toEqual([
69
- 'a:start', 'a:end',
70
- 'b:start', 'b:end',
71
- 'c:start', 'c:end',
72
- ]);
73
- });
74
- });
@@ -1,99 +0,0 @@
1
- /**
2
- * utils.js — getGlobalNpmPath() 파일 캐시 테스트
3
- *
4
- * 검증 범위:
5
- * - L2 파일 캐시 히트 (TTL 내)
6
- * - L2 파일 캐시 만료 (TTL 초과)
7
- * - 캐시 파일 손상 시 fail-open (execSync 재실행)
8
- * - 캐시 파일 없음 시 execSync 실행 후 파일 저장
9
- *
10
- * 격리 전략: 각 테스트는 별도 임시 디렉토리를 캐시 경로로 사용한다.
11
- * NPM_ROOT_CACHE_FILE 환경 변수를 통해 경로를 주입한다.
12
- */
13
- import { describe, it, expect } from 'vitest';
14
- import { spawnSync } from 'child_process';
15
- import fs from 'fs';
16
- import os from 'os';
17
- import path from 'path';
18
- import { fileURLToPath, pathToFileURL } from 'url';
19
-
20
- const __dirname = path.dirname(fileURLToPath(import.meta.url));
21
- // Windows 절대경로(C:\...)는 ESM import 지정자가 될 수 없으므로 file:// URL로 변환
22
- const UTILS_URL = pathToFileURL(path.resolve(__dirname, '..', 'utils.js')).href;
23
-
24
- function makeTempCacheFile() {
25
- const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'vibe-npm-root-test-'));
26
- return path.join(dir, 'npm-root.json');
27
- }
28
-
29
- /**
30
- * utils.js 의 getGlobalNpmPath() 를 별도 프로세스에서 실행.
31
- * VIBE_NPM_ROOT_CACHE_FILE 환경 변수로 캐시 파일 경로를 주입한다.
32
- */
33
- function runGetNpmRoot(cacheFilePath) {
34
- return spawnSync(process.execPath, ['--input-type=module', '--eval',
35
- `import { getGlobalNpmPath } from '${UTILS_URL}';
36
- process.stdout.write(getGlobalNpmPath() || '');`
37
- ], {
38
- encoding: 'utf-8',
39
- timeout: 10000,
40
- env: {
41
- ...process.env,
42
- VIBE_NPM_ROOT_CACHE_FILE: cacheFilePath,
43
- },
44
- });
45
- }
46
-
47
- describe('utils.js — getGlobalNpmPath() npm-root 파일 캐시', () => {
48
- it('캐시 파일 없으면 execSync 실행 후 캐시 파일 생성', () => {
49
- const cacheFile = makeTempCacheFile();
50
- // 캐시 파일이 없는 상태에서 시작
51
- expect(fs.existsSync(cacheFile)).toBe(false);
52
-
53
- const result = runGetNpmRoot(cacheFile);
54
- expect(result.status).toBe(0);
55
- expect(result.stdout.trim()).toBeTruthy();
56
-
57
- // 캐시 파일이 생성되어야 함
58
- expect(fs.existsSync(cacheFile)).toBe(true);
59
- const cached = JSON.parse(fs.readFileSync(cacheFile, 'utf-8'));
60
- expect(cached.npmRoot).toBe(result.stdout.trim());
61
- expect(typeof cached.savedAt).toBe('number');
62
- });
63
-
64
- it('유효한 캐시 파일이 있으면 execSync 없이 캐시값 반환', () => {
65
- const cacheFile = makeTempCacheFile();
66
- const fakeRoot = '/fake/npm/root/for/test';
67
- // 유효한 캐시 미리 작성
68
- fs.writeFileSync(cacheFile, JSON.stringify({ npmRoot: fakeRoot, savedAt: Date.now() }), { mode: 0o600 });
69
-
70
- const result = runGetNpmRoot(cacheFile);
71
- expect(result.status).toBe(0);
72
- expect(result.stdout.trim()).toBe(fakeRoot);
73
- });
74
-
75
- it('TTL 초과 캐시는 무효화 — execSync 재실행', () => {
76
- const cacheFile = makeTempCacheFile();
77
- const staleRoot = '/stale/path/should/not/be/used';
78
- const expiredAt = Date.now() - (25 * 60 * 60 * 1000); // 25시간 전
79
- fs.writeFileSync(cacheFile, JSON.stringify({ npmRoot: staleRoot, savedAt: expiredAt }), { mode: 0o600 });
80
-
81
- const result = runGetNpmRoot(cacheFile);
82
- expect(result.status).toBe(0);
83
- // stale 값이 아닌 실제 npm root 가 반환되어야 함
84
- expect(result.stdout.trim()).not.toBe(staleRoot);
85
- expect(result.stdout.trim()).toBeTruthy();
86
- });
87
-
88
- it('손상된 캐시 파일 — fail-open (execSync 실행)', () => {
89
- const cacheFile = makeTempCacheFile();
90
- fs.writeFileSync(cacheFile, '{ broken json :::');
91
-
92
- const result = runGetNpmRoot(cacheFile);
93
- expect(result.status).toBe(0);
94
- expect(result.stdout.trim()).toBeTruthy(); // 실제 경로 반환
95
- // 손상된 파일 때문에 프로세스가 crash 나지 않아야 함
96
- const stderr = result.stderr || '';
97
- expect(stderr).not.toMatch(/^Error:/m);
98
- });
99
- });