@su-record/vibe 3.2.13 → 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 (305) hide show
  1. package/CLAUDE.md +15 -14
  2. package/README.en.md +1 -1
  3. package/README.md +1 -1
  4. package/dist/cli/setup/ProjectSetup.d.ts +6 -0
  5. package/dist/cli/setup/ProjectSetup.d.ts.map +1 -1
  6. package/dist/cli/setup/ProjectSetup.js +19 -13
  7. package/dist/cli/setup/ProjectSetup.js.map +1 -1
  8. package/dist/infra/lib/codex-proxy.d.ts +126 -0
  9. package/dist/infra/lib/codex-proxy.d.ts.map +1 -1
  10. package/dist/infra/lib/codex-proxy.js +16 -16
  11. package/dist/infra/lib/codex-proxy.js.map +1 -1
  12. package/dist/infra/lib/figma/audit.d.ts.map +1 -1
  13. package/dist/infra/lib/figma/audit.js +0 -2
  14. package/dist/infra/lib/figma/audit.js.map +1 -1
  15. package/dist/infra/lib/figma/extract.d.ts +6 -1
  16. package/dist/infra/lib/figma/extract.d.ts.map +1 -1
  17. package/dist/infra/lib/figma/extract.js +17 -15
  18. package/dist/infra/lib/figma/extract.js.map +1 -1
  19. package/dist/infra/lib/figma/types.d.ts +103 -0
  20. package/dist/infra/lib/figma/types.d.ts.map +1 -1
  21. package/dist/infra/lib/figma/types.js +8 -0
  22. package/dist/infra/lib/figma/types.js.map +1 -1
  23. package/hooks/hooks.json +1 -1
  24. package/hooks/scripts/clone-extract.js +16 -1
  25. package/hooks/scripts/code-check.js +72 -55
  26. package/hooks/scripts/lib/anchor.js +74 -0
  27. package/hooks/scripts/lib/console-allow.js +66 -0
  28. package/hooks/scripts/lib/dispatcher.js +71 -19
  29. package/hooks/scripts/lib/inbox.js +60 -0
  30. package/hooks/scripts/loop-ledger.js +24 -1
  31. package/hooks/scripts/post-edit-dispatcher.js +4 -3
  32. package/hooks/scripts/post-edit.js +2 -2
  33. package/package.json +13 -9
  34. package/skills/vibe/SKILL.md +2 -1
  35. package/skills/vibe.clone/references/verification-loops.md +6 -3
  36. package/skills/vibe.loop/SKILL.md +7 -4
  37. package/skills/vibe.review/SKILL.md +5 -1
  38. package/skills/vibe.run/references/e2e-and-autofix.md +1 -1
  39. package/skills/vibe.run/references/process-steps.md +1 -1
  40. package/vibe/constitution.md +1 -1
  41. package/vibe/rules/loop-contract.md +40 -3
  42. package/vibe/rules/quality/checklist.md +3 -3
  43. package/vibe/templates/constitution-template.md +1 -1
  44. package/dist/__tests__/agent-model-sync.test.d.ts +0 -2
  45. package/dist/__tests__/agent-model-sync.test.d.ts.map +0 -1
  46. package/dist/__tests__/agent-model-sync.test.js +0 -71
  47. package/dist/__tests__/agent-model-sync.test.js.map +0 -1
  48. package/dist/__tests__/architecture.test.d.ts +0 -2
  49. package/dist/__tests__/architecture.test.d.ts.map +0 -1
  50. package/dist/__tests__/architecture.test.js +0 -207
  51. package/dist/__tests__/architecture.test.js.map +0 -1
  52. package/dist/__tests__/skill-namespace.test.d.ts +0 -2
  53. package/dist/__tests__/skill-namespace.test.d.ts.map +0 -1
  54. package/dist/__tests__/skill-namespace.test.js +0 -60
  55. package/dist/__tests__/skill-namespace.test.js.map +0 -1
  56. package/dist/__tests__/stakes-contract.test.d.ts +0 -2
  57. package/dist/__tests__/stakes-contract.test.d.ts.map +0 -1
  58. package/dist/__tests__/stakes-contract.test.js +0 -166
  59. package/dist/__tests__/stakes-contract.test.js.map +0 -1
  60. package/dist/__tests__/stuck-semantics.test.d.ts +0 -2
  61. package/dist/__tests__/stuck-semantics.test.d.ts.map +0 -1
  62. package/dist/__tests__/stuck-semantics.test.js +0 -109
  63. package/dist/__tests__/stuck-semantics.test.js.map +0 -1
  64. package/dist/__tests__/wiring-integrity.test.d.ts +0 -2
  65. package/dist/__tests__/wiring-integrity.test.d.ts.map +0 -1
  66. package/dist/__tests__/wiring-integrity.test.js +0 -160
  67. package/dist/__tests__/wiring-integrity.test.js.map +0 -1
  68. package/dist/cli/commands/info.test.d.ts +0 -2
  69. package/dist/cli/commands/info.test.d.ts.map +0 -1
  70. package/dist/cli/commands/info.test.js +0 -74
  71. package/dist/cli/commands/info.test.js.map +0 -1
  72. package/dist/cli/commands/upgrade.test.d.ts +0 -2
  73. package/dist/cli/commands/upgrade.test.d.ts.map +0 -1
  74. package/dist/cli/commands/upgrade.test.js +0 -99
  75. package/dist/cli/commands/upgrade.test.js.map +0 -1
  76. package/dist/cli/design/design-md-parser.test.d.ts +0 -2
  77. package/dist/cli/design/design-md-parser.test.d.ts.map +0 -1
  78. package/dist/cli/design/design-md-parser.test.js +0 -149
  79. package/dist/cli/design/design-md-parser.test.js.map +0 -1
  80. package/dist/cli/detect.characterization.test.d.ts +0 -7
  81. package/dist/cli/detect.characterization.test.d.ts.map +0 -1
  82. package/dist/cli/detect.characterization.test.js +0 -294
  83. package/dist/cli/detect.characterization.test.js.map +0 -1
  84. package/dist/cli/postinstall/fs-utils.test.d.ts +0 -2
  85. package/dist/cli/postinstall/fs-utils.test.d.ts.map +0 -1
  86. package/dist/cli/postinstall/fs-utils.test.js +0 -259
  87. package/dist/cli/postinstall/fs-utils.test.js.map +0 -1
  88. package/dist/cli/setup/CodexHooks.test.d.ts +0 -2
  89. package/dist/cli/setup/CodexHooks.test.d.ts.map +0 -1
  90. package/dist/cli/setup/CodexHooks.test.js +0 -52
  91. package/dist/cli/setup/CodexHooks.test.js.map +0 -1
  92. package/dist/cli/setup/GlobalInstaller.test.d.ts +0 -2
  93. package/dist/cli/setup/GlobalInstaller.test.d.ts.map +0 -1
  94. package/dist/cli/setup/GlobalInstaller.test.js +0 -16
  95. package/dist/cli/setup/GlobalInstaller.test.js.map +0 -1
  96. package/dist/cli/utils/cli-detector.test.d.ts +0 -2
  97. package/dist/cli/utils/cli-detector.test.d.ts.map +0 -1
  98. package/dist/cli/utils/cli-detector.test.js +0 -52
  99. package/dist/cli/utils/cli-detector.test.js.map +0 -1
  100. package/dist/infra/lib/SkillFrontmatter.test.d.ts +0 -2
  101. package/dist/infra/lib/SkillFrontmatter.test.d.ts.map +0 -1
  102. package/dist/infra/lib/SkillFrontmatter.test.js +0 -60
  103. package/dist/infra/lib/SkillFrontmatter.test.js.map +0 -1
  104. package/dist/infra/lib/__tests__/AutomationLevel.test.d.ts +0 -2
  105. package/dist/infra/lib/__tests__/AutomationLevel.test.d.ts.map +0 -1
  106. package/dist/infra/lib/__tests__/AutomationLevel.test.js +0 -297
  107. package/dist/infra/lib/__tests__/AutomationLevel.test.js.map +0 -1
  108. package/dist/infra/lib/__tests__/ComponentRegistry.test.d.ts +0 -2
  109. package/dist/infra/lib/__tests__/ComponentRegistry.test.d.ts.map +0 -1
  110. package/dist/infra/lib/__tests__/ComponentRegistry.test.js +0 -99
  111. package/dist/infra/lib/__tests__/ComponentRegistry.test.js.map +0 -1
  112. package/dist/infra/lib/__tests__/DecisionTracer.test.d.ts +0 -2
  113. package/dist/infra/lib/__tests__/DecisionTracer.test.d.ts.map +0 -1
  114. package/dist/infra/lib/__tests__/DecisionTracer.test.js +0 -274
  115. package/dist/infra/lib/__tests__/DecisionTracer.test.js.map +0 -1
  116. package/dist/infra/lib/__tests__/InteractiveCheckpoint.test.d.ts +0 -2
  117. package/dist/infra/lib/__tests__/InteractiveCheckpoint.test.d.ts.map +0 -1
  118. package/dist/infra/lib/__tests__/InteractiveCheckpoint.test.js +0 -350
  119. package/dist/infra/lib/__tests__/InteractiveCheckpoint.test.js.map +0 -1
  120. package/dist/infra/lib/__tests__/LoopBreaker.test.d.ts +0 -2
  121. package/dist/infra/lib/__tests__/LoopBreaker.test.d.ts.map +0 -1
  122. package/dist/infra/lib/__tests__/LoopBreaker.test.js +0 -340
  123. package/dist/infra/lib/__tests__/LoopBreaker.test.js.map +0 -1
  124. package/dist/infra/lib/__tests__/VerificationLoop.test.d.ts +0 -2
  125. package/dist/infra/lib/__tests__/VerificationLoop.test.d.ts.map +0 -1
  126. package/dist/infra/lib/__tests__/VerificationLoop.test.js +0 -486
  127. package/dist/infra/lib/__tests__/VerificationLoop.test.js.map +0 -1
  128. package/dist/infra/lib/config/GlobalConfigManager.test.d.ts +0 -2
  129. package/dist/infra/lib/config/GlobalConfigManager.test.d.ts.map +0 -1
  130. package/dist/infra/lib/config/GlobalConfigManager.test.js +0 -9
  131. package/dist/infra/lib/config/GlobalConfigManager.test.js.map +0 -1
  132. package/dist/infra/lib/embedding/__tests__/EmbeddingProvider.test.d.ts +0 -2
  133. package/dist/infra/lib/embedding/__tests__/EmbeddingProvider.test.d.ts.map +0 -1
  134. package/dist/infra/lib/embedding/__tests__/EmbeddingProvider.test.js +0 -43
  135. package/dist/infra/lib/embedding/__tests__/EmbeddingProvider.test.js.map +0 -1
  136. package/dist/infra/lib/embedding/__tests__/VectorStore.test.d.ts +0 -2
  137. package/dist/infra/lib/embedding/__tests__/VectorStore.test.d.ts.map +0 -1
  138. package/dist/infra/lib/embedding/__tests__/VectorStore.test.js +0 -94
  139. package/dist/infra/lib/embedding/__tests__/VectorStore.test.js.map +0 -1
  140. package/dist/infra/lib/embedding/__tests__/cosine.test.d.ts +0 -2
  141. package/dist/infra/lib/embedding/__tests__/cosine.test.d.ts.map +0 -1
  142. package/dist/infra/lib/embedding/__tests__/cosine.test.js +0 -73
  143. package/dist/infra/lib/embedding/__tests__/cosine.test.js.map +0 -1
  144. package/dist/infra/lib/evolution/__tests__/deprecation.test.d.ts +0 -2
  145. package/dist/infra/lib/evolution/__tests__/deprecation.test.d.ts.map +0 -1
  146. package/dist/infra/lib/evolution/__tests__/deprecation.test.js +0 -251
  147. package/dist/infra/lib/evolution/__tests__/deprecation.test.js.map +0 -1
  148. package/dist/infra/lib/evolution/__tests__/eval.test.d.ts +0 -2
  149. package/dist/infra/lib/evolution/__tests__/eval.test.d.ts.map +0 -1
  150. package/dist/infra/lib/evolution/__tests__/eval.test.js +0 -603
  151. package/dist/infra/lib/evolution/__tests__/eval.test.js.map +0 -1
  152. package/dist/infra/lib/evolution/__tests__/generation.test.d.ts +0 -2
  153. package/dist/infra/lib/evolution/__tests__/generation.test.d.ts.map +0 -1
  154. package/dist/infra/lib/evolution/__tests__/generation.test.js +0 -396
  155. package/dist/infra/lib/evolution/__tests__/generation.test.js.map +0 -1
  156. package/dist/infra/lib/evolution/__tests__/insight.test.d.ts +0 -2
  157. package/dist/infra/lib/evolution/__tests__/insight.test.d.ts.map +0 -1
  158. package/dist/infra/lib/evolution/__tests__/insight.test.js +0 -204
  159. package/dist/infra/lib/evolution/__tests__/insight.test.js.map +0 -1
  160. package/dist/infra/lib/evolution/__tests__/integration.test.d.ts +0 -2
  161. package/dist/infra/lib/evolution/__tests__/integration.test.d.ts.map +0 -1
  162. package/dist/infra/lib/evolution/__tests__/integration.test.js +0 -206
  163. package/dist/infra/lib/evolution/__tests__/integration.test.js.map +0 -1
  164. package/dist/infra/lib/evolution/__tests__/lifecycle.test.d.ts +0 -2
  165. package/dist/infra/lib/evolution/__tests__/lifecycle.test.d.ts.map +0 -1
  166. package/dist/infra/lib/evolution/__tests__/lifecycle.test.js +0 -350
  167. package/dist/infra/lib/evolution/__tests__/lifecycle.test.js.map +0 -1
  168. package/dist/infra/lib/evolution/__tests__/parity.test.d.ts +0 -2
  169. package/dist/infra/lib/evolution/__tests__/parity.test.d.ts.map +0 -1
  170. package/dist/infra/lib/evolution/__tests__/parity.test.js +0 -319
  171. package/dist/infra/lib/evolution/__tests__/parity.test.js.map +0 -1
  172. package/dist/infra/lib/llm/auth/AuthProfileManager.test.d.ts +0 -5
  173. package/dist/infra/lib/llm/auth/AuthProfileManager.test.d.ts.map +0 -1
  174. package/dist/infra/lib/llm/auth/AuthProfileManager.test.js +0 -168
  175. package/dist/infra/lib/llm/auth/AuthProfileManager.test.js.map +0 -1
  176. package/dist/infra/lib/llm/auth/TokenRefresher.test.d.ts +0 -2
  177. package/dist/infra/lib/llm/auth/TokenRefresher.test.d.ts.map +0 -1
  178. package/dist/infra/lib/llm/auth/TokenRefresher.test.js +0 -120
  179. package/dist/infra/lib/llm/auth/TokenRefresher.test.js.map +0 -1
  180. package/dist/infra/lib/llm/timeout.test.d.ts +0 -2
  181. package/dist/infra/lib/llm/timeout.test.d.ts.map +0 -1
  182. package/dist/infra/lib/llm/timeout.test.js +0 -50
  183. package/dist/infra/lib/llm/timeout.test.js.map +0 -1
  184. package/dist/infra/lib/llm-availability.test.d.ts +0 -2
  185. package/dist/infra/lib/llm-availability.test.d.ts.map +0 -1
  186. package/dist/infra/lib/llm-availability.test.js +0 -37
  187. package/dist/infra/lib/llm-availability.test.js.map +0 -1
  188. package/dist/infra/lib/memory/KnowledgeGraph.test.d.ts +0 -2
  189. package/dist/infra/lib/memory/KnowledgeGraph.test.d.ts.map +0 -1
  190. package/dist/infra/lib/memory/KnowledgeGraph.test.js +0 -189
  191. package/dist/infra/lib/memory/KnowledgeGraph.test.js.map +0 -1
  192. package/dist/infra/lib/memory/MemorySearch.test.d.ts +0 -2
  193. package/dist/infra/lib/memory/MemorySearch.test.d.ts.map +0 -1
  194. package/dist/infra/lib/memory/MemorySearch.test.js +0 -149
  195. package/dist/infra/lib/memory/MemorySearch.test.js.map +0 -1
  196. package/dist/infra/lib/memory/MemoryStorage.test.d.ts +0 -2
  197. package/dist/infra/lib/memory/MemoryStorage.test.d.ts.map +0 -1
  198. package/dist/infra/lib/memory/MemoryStorage.test.js +0 -198
  199. package/dist/infra/lib/memory/MemoryStorage.test.js.map +0 -1
  200. package/dist/infra/lib/memory/SessionRAGRetriever.test.d.ts +0 -2
  201. package/dist/infra/lib/memory/SessionRAGRetriever.test.d.ts.map +0 -1
  202. package/dist/infra/lib/memory/SessionRAGRetriever.test.js +0 -180
  203. package/dist/infra/lib/memory/SessionRAGRetriever.test.js.map +0 -1
  204. package/dist/infra/lib/memory/SessionRAGStore.test.d.ts +0 -2
  205. package/dist/infra/lib/memory/SessionRAGStore.test.d.ts.map +0 -1
  206. package/dist/infra/lib/memory/SessionRAGStore.test.js +0 -326
  207. package/dist/infra/lib/memory/SessionRAGStore.test.js.map +0 -1
  208. package/dist/infra/lib/memory/__tests__/InMemoryStorage.test.d.ts +0 -2
  209. package/dist/infra/lib/memory/__tests__/InMemoryStorage.test.d.ts.map +0 -1
  210. package/dist/infra/lib/memory/__tests__/InMemoryStorage.test.js +0 -114
  211. package/dist/infra/lib/memory/__tests__/InMemoryStorage.test.js.map +0 -1
  212. package/dist/infra/lib/memory/__tests__/ReflectionStore.test.d.ts +0 -2
  213. package/dist/infra/lib/memory/__tests__/ReflectionStore.test.d.ts.map +0 -1
  214. package/dist/infra/lib/memory/__tests__/ReflectionStore.test.js +0 -196
  215. package/dist/infra/lib/memory/__tests__/ReflectionStore.test.js.map +0 -1
  216. package/dist/infra/lib/memory/index.test.d.ts +0 -2
  217. package/dist/infra/lib/memory/index.test.d.ts.map +0 -1
  218. package/dist/infra/lib/memory/index.test.js +0 -23
  219. package/dist/infra/lib/memory/index.test.js.map +0 -1
  220. package/dist/infra/lib/telemetry/SkillTelemetry.test.d.ts +0 -2
  221. package/dist/infra/lib/telemetry/SkillTelemetry.test.d.ts.map +0 -1
  222. package/dist/infra/lib/telemetry/SkillTelemetry.test.js +0 -124
  223. package/dist/infra/lib/telemetry/SkillTelemetry.test.js.map +0 -1
  224. package/dist/infra/lib/ui-ux/Bm25Engine.test.d.ts +0 -2
  225. package/dist/infra/lib/ui-ux/Bm25Engine.test.d.ts.map +0 -1
  226. package/dist/infra/lib/ui-ux/Bm25Engine.test.js +0 -159
  227. package/dist/infra/lib/ui-ux/Bm25Engine.test.js.map +0 -1
  228. package/dist/infra/lib/ui-ux/CsvDataLoader.test.d.ts +0 -2
  229. package/dist/infra/lib/ui-ux/CsvDataLoader.test.d.ts.map +0 -1
  230. package/dist/infra/lib/ui-ux/CsvDataLoader.test.js +0 -141
  231. package/dist/infra/lib/ui-ux/CsvDataLoader.test.js.map +0 -1
  232. package/dist/infra/lib/ui-ux/DesignSystemGenerator.test.d.ts +0 -2
  233. package/dist/infra/lib/ui-ux/DesignSystemGenerator.test.d.ts.map +0 -1
  234. package/dist/infra/lib/ui-ux/DesignSystemGenerator.test.js +0 -370
  235. package/dist/infra/lib/ui-ux/DesignSystemGenerator.test.js.map +0 -1
  236. package/dist/infra/lib/ui-ux/SearchService.test.d.ts +0 -2
  237. package/dist/infra/lib/ui-ux/SearchService.test.d.ts.map +0 -1
  238. package/dist/infra/lib/ui-ux/SearchService.test.js +0 -215
  239. package/dist/infra/lib/ui-ux/SearchService.test.js.map +0 -1
  240. package/dist/test-helpers/index.d.ts +0 -36
  241. package/dist/test-helpers/index.d.ts.map +0 -1
  242. package/dist/test-helpers/index.js +0 -85
  243. package/dist/test-helpers/index.js.map +0 -1
  244. package/dist/test-helpers/index.test.d.ts +0 -2
  245. package/dist/test-helpers/index.test.d.ts.map +0 -1
  246. package/dist/test-helpers/index.test.js +0 -92
  247. package/dist/test-helpers/index.test.js.map +0 -1
  248. package/dist/tests/curated-release-notes.test.d.ts +0 -2
  249. package/dist/tests/curated-release-notes.test.d.ts.map +0 -1
  250. package/dist/tests/curated-release-notes.test.js +0 -160
  251. package/dist/tests/curated-release-notes.test.js.map +0 -1
  252. package/dist/tools/convention/analyzeComplexity.test.d.ts +0 -2
  253. package/dist/tools/convention/analyzeComplexity.test.d.ts.map +0 -1
  254. package/dist/tools/convention/analyzeComplexity.test.js +0 -207
  255. package/dist/tools/convention/analyzeComplexity.test.js.map +0 -1
  256. package/dist/tools/convention/validateCodeQuality.test.d.ts +0 -2
  257. package/dist/tools/convention/validateCodeQuality.test.d.ts.map +0 -1
  258. package/dist/tools/convention/validateCodeQuality.test.js +0 -230
  259. package/dist/tools/convention/validateCodeQuality.test.js.map +0 -1
  260. package/dist/tools/loop/validateLoopDefinition.test.d.ts +0 -14
  261. package/dist/tools/loop/validateLoopDefinition.test.d.ts.map +0 -1
  262. package/dist/tools/loop/validateLoopDefinition.test.js +0 -229
  263. package/dist/tools/loop/validateLoopDefinition.test.js.map +0 -1
  264. package/dist/tools/spec/executionPacket.test.d.ts +0 -2
  265. package/dist/tools/spec/executionPacket.test.d.ts.map +0 -1
  266. package/dist/tools/spec/executionPacket.test.js +0 -374
  267. package/dist/tools/spec/executionPacket.test.js.map +0 -1
  268. package/dist/tools/spec/prdParser.test.d.ts +0 -6
  269. package/dist/tools/spec/prdParser.test.d.ts.map +0 -1
  270. package/dist/tools/spec/prdParser.test.js +0 -332
  271. package/dist/tools/spec/prdParser.test.js.map +0 -1
  272. package/dist/tools/spec/specGenerator.test.d.ts +0 -2
  273. package/dist/tools/spec/specGenerator.test.d.ts.map +0 -1
  274. package/dist/tools/spec/specGenerator.test.js +0 -130
  275. package/dist/tools/spec/specGenerator.test.js.map +0 -1
  276. package/dist/tools/spec/traceabilityMatrix.path-resolution.test.d.ts +0 -10
  277. package/dist/tools/spec/traceabilityMatrix.path-resolution.test.d.ts.map +0 -1
  278. package/dist/tools/spec/traceabilityMatrix.path-resolution.test.js +0 -120
  279. package/dist/tools/spec/traceabilityMatrix.path-resolution.test.js.map +0 -1
  280. package/dist/tools/spec/traceabilityMatrix.test.d.ts +0 -6
  281. package/dist/tools/spec/traceabilityMatrix.test.d.ts.map +0 -1
  282. package/dist/tools/spec/traceabilityMatrix.test.js +0 -359
  283. package/dist/tools/spec/traceabilityMatrix.test.js.map +0 -1
  284. package/hooks/scripts/__tests__/.vibe/command-log.txt +0 -3
  285. package/hooks/scripts/__tests__/auto-test-debounce.test.js +0 -145
  286. package/hooks/scripts/__tests__/clone-behaviors.test.js +0 -152
  287. package/hooks/scripts/__tests__/clone-merge-responsive.test.js +0 -106
  288. package/hooks/scripts/__tests__/code-check-detectors.test.js +0 -213
  289. package/hooks/scripts/__tests__/code-check-memory-write.test.js +0 -81
  290. package/hooks/scripts/__tests__/codex-hook-adapter.test.js +0 -64
  291. package/hooks/scripts/__tests__/curation-index.test.js +0 -157
  292. package/hooks/scripts/__tests__/dispatcher-inprocess.test.js +0 -99
  293. package/hooks/scripts/__tests__/figma-extract.test.js +0 -338
  294. package/hooks/scripts/__tests__/hook-payload.test.js +0 -29
  295. package/hooks/scripts/__tests__/llm-orchestrate-antigravity.test.js +0 -66
  296. package/hooks/scripts/__tests__/loop-ledger.test.js +0 -321
  297. package/hooks/scripts/__tests__/post-edit-dispatcher.test.js +0 -139
  298. package/hooks/scripts/__tests__/pre-tool-guard.test.js +0 -523
  299. package/hooks/scripts/__tests__/recipe-extractor.test.js +0 -244
  300. package/hooks/scripts/__tests__/run-ledger-verify-required.test.js +0 -151
  301. package/hooks/scripts/__tests__/run-ledger.test.js +0 -481
  302. package/hooks/scripts/__tests__/scope-from-spec.test.js +0 -215
  303. package/hooks/scripts/__tests__/sentinel-guard.test.js +0 -263
  304. package/hooks/scripts/__tests__/step-counter.test.js +0 -459
  305. 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,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
- });