@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
@@ -10,11 +10,10 @@
10
10
  * (run-ledger verifyRequired)를 태우지 않는다 — 그 게이트는 결정론적
11
11
  * 검증 흐름(vibe.verify) 전용이다.
12
12
  */
13
- import { getToolsBaseUrl, PROJECT_DIR, readProjectConfig } from './utils.js';
13
+ import { getToolsBaseUrl, PROJECT_DIR } from './utils.js';
14
14
  import { readFileSync } from 'fs';
15
- import path from 'path';
16
15
  import { buildCliCtx, isDirectRun } from './lib/hook-context.js';
17
- import { globToRegExp } from './lib/glob.js';
16
+ import { CODE_EXT_RE, shouldCheckConsole } from './lib/console-allow.js';
18
17
 
19
18
  const BASE_URL = getToolsBaseUrl();
20
19
 
@@ -31,50 +30,6 @@ const P1_DETECTORS = [
31
30
  ];
32
31
 
33
32
  const TS_EXT_RE = /\.(ts|tsx)$/;
34
- const CODE_EXT_RE = /\.(ts|tsx|js|jsx|mjs|cjs)$/;
35
-
36
- // console.log 기본 허용 경로 (glob 패턴 → 정규식으로 변환)
37
- const DEFAULT_CONSOLE_ALLOW_GLOBS = [
38
- 'hooks/scripts/**',
39
- 'scripts/**',
40
- '**/cli/**',
41
- '**/*.test.*',
42
- '**/*.spec.*',
43
- '**/__tests__/**',
44
- ];
45
-
46
- /**
47
- * .vibe/config.json의 qualityCheck.consoleAllow 글로브 목록 로드.
48
- * 기본 글로브와 병합하여 반환.
49
- * @returns {RegExp[]}
50
- */
51
- function loadConsoleAllowPatterns() {
52
- try {
53
- const cfg = readProjectConfig();
54
- const extra = cfg?.qualityCheck?.consoleAllow;
55
- const globs = Array.isArray(extra)
56
- ? [...DEFAULT_CONSOLE_ALLOW_GLOBS, ...extra]
57
- : DEFAULT_CONSOLE_ALLOW_GLOBS;
58
- return globs.map(g => globToRegExp(g));
59
- } catch {
60
- return DEFAULT_CONSOLE_ALLOW_GLOBS.map(g => globToRegExp(g));
61
- }
62
- }
63
-
64
- /**
65
- * 파일 경로가 console.log 허용 경로인지 판단.
66
- * @param {string} filePath - 절대 또는 프로젝트 상대 경로
67
- * @returns {boolean}
68
- */
69
- function isConsoleAllowed(filePath) {
70
- try {
71
- const rel = path.relative(PROJECT_DIR, path.resolve(filePath)).replace(/\\/g, '/');
72
- const patterns = loadConsoleAllowPatterns();
73
- return patterns.some(re => re.test(rel));
74
- } catch {
75
- return false;
76
- }
77
- }
78
33
 
79
34
  /**
80
35
  * hook ctx에서 수정된 파일 경로 추출.
@@ -99,16 +54,72 @@ function classifyObservation(files) {
99
54
  return { type: 'feature', title: 'Code modified' };
100
55
  }
101
56
 
57
+ /**
58
+ * 백슬래시로 이스케이프되지 않은 첫 token 위치. 없으면 -1.
59
+ * 템플릿 리터럴 안의 \` 를 종료 백틱으로 오인하지 않기 위해 필요하다.
60
+ * @param {string} str
61
+ * @param {string} token
62
+ * @returns {number}
63
+ */
64
+ function findUnescaped(str, token) {
65
+ for (let i = 0; i < str.length; i++) {
66
+ if (str[i] === '\\') { i++; continue; }
67
+ if (str.startsWith(token, i)) return i;
68
+ }
69
+ return -1;
70
+ }
71
+
72
+ /**
73
+ * 한 줄에서 코드가 아닌 구간(주석·문자열·템플릿 리터럴)을 지운다.
74
+ * 여러 줄에 걸친 블록 주석·템플릿 리터럴을 잇기 위해 state를 받고 갱신해 돌려준다.
75
+ * @param {string} line
76
+ * @param {{ inBlock: boolean, inTemplate: boolean }} state - 제자리 갱신
77
+ * @returns {string} 코드 구간만 남은 문자열
78
+ */
79
+ function stripNonCodeLine(line, state) {
80
+ let rest = line;
81
+ let code = '';
82
+ while (rest.length > 0) {
83
+ if (state.inBlock || state.inTemplate) {
84
+ const closer = state.inBlock ? '*/' : '`';
85
+ const end = findUnescaped(rest, closer);
86
+ if (end === -1) break;
87
+ if (state.inBlock) state.inBlock = false;
88
+ else state.inTemplate = false;
89
+ rest = rest.slice(end + closer.length);
90
+ continue;
91
+ }
92
+ const opener = rest.match(/\/\*|\/\/|`|'|"/);
93
+ if (!opener) return code + rest;
94
+ code += rest.slice(0, opener.index);
95
+ const token = opener[0];
96
+ rest = rest.slice(opener.index + token.length);
97
+ if (token === '//') break;
98
+ if (token === '/*') { state.inBlock = true; continue; }
99
+ if (token === '`') { state.inTemplate = true; continue; }
100
+ const close = findUnescaped(rest, token); // 홑/겹따옴표는 줄을 넘지 않는다
101
+ rest = close === -1 ? '' : rest.slice(close + 1);
102
+ }
103
+ return code;
104
+ }
105
+
102
106
  /**
103
107
  * P1: any 타입 탐지 — .ts/.tsx 전용, 단어 경계 기반.
108
+ *
109
+ * 주석·문자열·템플릿 리터럴은 제외한다: `any` 를 **금지하는** 문서 문장이
110
+ * 그 자체로 P1 이 되면, 고칠 수도 없는 경고가 그 파일을 편집할 때마다
111
+ * 주입돼 게이트 신뢰도가 떨어진다 (detectConsoleLogs의 확장자 게이트와 같은 이유).
112
+ *
104
113
  * @param {string[]} lines
105
114
  * @returns {Array<{ line: number, match: string, severity: 'P1' }>}
106
115
  */
107
116
  function detectAnyType(lines) {
108
117
  const findings = [];
118
+ const state = { inBlock: false, inTemplate: false };
109
119
  lines.forEach((line, i) => {
120
+ const code = stripNonCodeLine(line, state);
110
121
  for (const re of P1_DETECTORS) {
111
- if (re.test(line)) {
122
+ if (re.test(code)) {
112
123
  findings.push({
113
124
  line: i + 1,
114
125
  match: line.trim(),
@@ -125,21 +136,27 @@ function detectAnyType(lines) {
125
136
  /**
126
137
  * P1: console.log 탐지 — 허용 경로가 아닌 곳의 src/ 코드.
127
138
  *
128
- * 코드 확장자에만 적용한다: 마크다운·JSON·텍스트에 인용된 `console.log(` 는
129
- * 커밋되면 되는 디버그 코드가 아니라 문서상의 예시다. 확장자 게이트가 없으면
130
- * SKILL.md 안의 bash/JS 스니펫이 편집마다 P1 으로 주입돼, 고칠 수도 없는
131
- * 경고가 반복되고 게이트 신뢰도가 떨어진다.
139
+ * 겹의 게이트를 쓴다:
140
+ * 1. 확장자 마크다운·JSON·텍스트에 인용된 `console.log(` 커밋되면 되는
141
+ * 디버그 코드가 아니라 문서상의 예시다. 이게 없으면 SKILL.md 안의 스니펫이
142
+ * 편집마다 P1 으로 주입된다.
143
+ * 2. 코드 구간 — 코드 파일 **안에서도** JSDoc 예시와 사용자에게 출력할 마크다운
144
+ * 템플릿 리터럴에 `console.log(` 가 등장한다. 원본 라인을 그대로 검사하면
145
+ * 그것들이 전부 P1 이 되어, 고칠 수 없는 경고가 반복되고 게이트 신뢰도가
146
+ * 떨어진다 — detectAnyType 이 stripNonCodeLine 을 쓰는 것과 같은 이유다.
147
+ * (2026-07-28 감사: 실측 9건 중 7건이 이 오탐이었다)
132
148
  *
133
149
  * @param {string[]} lines
134
150
  * @param {string} filePath
135
151
  * @returns {Array<{ line: number, match: string, severity: 'P1' }>}
136
152
  */
137
153
  function detectConsoleLogs(lines, filePath) {
138
- if (!CODE_EXT_RE.test(filePath)) return [];
139
- if (isConsoleAllowed(filePath)) return [];
154
+ if (!shouldCheckConsole(filePath)) return [];
140
155
  const findings = [];
156
+ const state = { inBlock: false, inTemplate: false };
141
157
  lines.forEach((line, i) => {
142
- if (/console\.log\(/.test(line)) {
158
+ const code = stripNonCodeLine(line, state);
159
+ if (/console\.log\(/.test(code)) {
143
160
  findings.push({
144
161
  line: i + 1,
145
162
  match: line.trim(),
@@ -0,0 +1,74 @@
1
+ /**
2
+ * ANCHOR — 루프 회전 시작 시 디스크에서 상태를 재고정한다.
3
+ *
4
+ * loop-contract 는 ANCHOR 를 컨텍스트 오염 방어의 근거로 규정하는데, JUDGE·RECORD·stuck 과
5
+ * 달리 실행 수단이 없어 모델 재량으로 남아 있었다 (감사 2026-07-28 L3). 재고정 대상이
6
+ * 무엇인지 결정론적으로 답하는 것이 이 모듈의 역할이다 — 파일 내용을 해석하지는 않는다.
7
+ */
8
+ import fs from 'fs';
9
+ import path from 'path';
10
+ import { readLedger } from './run-ledger.js';
11
+
12
+ /** 우선순위대로 첫 번째로 존재하는 경로를 고른다 */
13
+ function firstExisting(projectDir, candidates) {
14
+ for (const rel of candidates) {
15
+ if (fs.existsSync(path.join(projectDir, rel))) return rel;
16
+ }
17
+ return null;
18
+ }
19
+
20
+ /** `.vibe/.last-feature` 에 기록된 직전 feature 이름 */
21
+ function readLastFeature(projectDir) {
22
+ try {
23
+ const raw = fs.readFileSync(path.join(projectDir, '.vibe', '.last-feature'), 'utf-8').trim();
24
+ return raw.length > 0 ? raw : null;
25
+ } catch {
26
+ return null;
27
+ }
28
+ }
29
+
30
+ /** SPEC 경로 — 신규 레이아웃 우선, 레거시 폴백 */
31
+ function findSpec(projectDir, feature) {
32
+ if (!feature) return null;
33
+ return firstExisting(projectDir, [
34
+ path.join('.vibe', 'specs', `${feature}.md`),
35
+ path.join('.vibe', 'specs', feature, '_index.md'),
36
+ path.join('.claude', 'vibe', 'specs', `${feature}.md`),
37
+ path.join('.claude', 'specs', `${feature}.md`),
38
+ ]);
39
+ }
40
+
41
+ /** 인박스에서 가장 최근 블록(다음 `## ` 직전까지) */
42
+ function readLatestInboxBlock(projectDir) {
43
+ try {
44
+ const raw = fs.readFileSync(path.join(projectDir, '.vibe', 'loops', 'inbox.md'), 'utf-8');
45
+ const start = raw.indexOf('## ');
46
+ if (start === -1) return null;
47
+ const next = raw.indexOf('\n## ', start + 3);
48
+ return (next === -1 ? raw.slice(start) : raw.slice(start, next)).trim() || null;
49
+ } catch {
50
+ return null;
51
+ }
52
+ }
53
+
54
+ /**
55
+ * 재고정 번들 — loop-contract ANCHOR 절이 지정한 SPEC + run-ledger + scope.json + 직전 인박스.
56
+ *
57
+ * @param {string} projectDir
58
+ * @param {string} [feature] - 생략 시 `.vibe/.last-feature`
59
+ * @returns {{ feature: string|null, spec: string|null, scope: string|null,
60
+ * ledger: object|null, latestInbox: string|null, missing: string[] }}
61
+ */
62
+ export function buildAnchor(projectDir, feature) {
63
+ const resolved = feature || readLastFeature(projectDir);
64
+ const spec = findSpec(projectDir, resolved);
65
+ const scope = firstExisting(projectDir, [path.join('.vibe', 'scope.json')]);
66
+ const ledger = readLedger(projectDir);
67
+
68
+ const missing = [];
69
+ if (!resolved) missing.push('feature');
70
+ if (!spec) missing.push('spec');
71
+ if (!ledger) missing.push('run-ledger');
72
+
73
+ return { feature: resolved, spec, scope, ledger, latestInbox: readLatestInboxBlock(projectDir), missing };
74
+ }
@@ -0,0 +1,66 @@
1
+ /**
2
+ * console.log 탐지의 적용 범위 판단 — code-check.js·post-edit.js 공용 (중복 제거).
3
+ *
4
+ * 두 훅이 같은 검사를 각자 들고 있으면 한쪽의 허용 경로 설계가 다른 쪽 경고에
5
+ * 무력화된다. 범위 규칙은 여기 하나만 둔다.
6
+ */
7
+ import path from 'path';
8
+ import { PROJECT_DIR, readProjectConfig } from '../utils.js';
9
+ import { globToRegExp } from './glob.js';
10
+
11
+ /**
12
+ * 코드 확장자 — 마크다운·JSON·텍스트에 인용된 `console.log(` 는 커밋되면 안 되는
13
+ * 디버그 코드가 아니라 문서상의 예시다.
14
+ */
15
+ export const CODE_EXT_RE = /\.(ts|tsx|js|jsx|mjs|cjs)$/;
16
+
17
+ // console.log 기본 허용 경로 (glob 패턴 → 정규식으로 변환)
18
+ const DEFAULT_CONSOLE_ALLOW_GLOBS = [
19
+ 'hooks/scripts/**',
20
+ 'scripts/**',
21
+ '**/cli/**',
22
+ '**/*.test.*',
23
+ '**/*.spec.*',
24
+ '**/__tests__/**',
25
+ ];
26
+
27
+ /**
28
+ * .vibe/config.json의 qualityCheck.consoleAllow 글로브 목록 로드.
29
+ * 기본 글로브와 병합하여 반환.
30
+ * @returns {RegExp[]}
31
+ */
32
+ function loadConsoleAllowPatterns() {
33
+ try {
34
+ const cfg = readProjectConfig();
35
+ const extra = cfg?.qualityCheck?.consoleAllow;
36
+ const globs = Array.isArray(extra)
37
+ ? [...DEFAULT_CONSOLE_ALLOW_GLOBS, ...extra]
38
+ : DEFAULT_CONSOLE_ALLOW_GLOBS;
39
+ return globs.map(g => globToRegExp(g));
40
+ } catch {
41
+ return DEFAULT_CONSOLE_ALLOW_GLOBS.map(g => globToRegExp(g));
42
+ }
43
+ }
44
+
45
+ /**
46
+ * 파일 경로가 console.log 허용 경로인지 판단.
47
+ * @param {string} filePath - 절대 또는 프로젝트 상대 경로
48
+ * @returns {boolean}
49
+ */
50
+ export function isConsoleAllowed(filePath) {
51
+ try {
52
+ const rel = path.relative(PROJECT_DIR, path.resolve(filePath)).replace(/\\/g, '/');
53
+ return loadConsoleAllowPatterns().some(re => re.test(rel));
54
+ } catch {
55
+ return false;
56
+ }
57
+ }
58
+
59
+ /**
60
+ * console.log 검사 대상 파일인지 — 코드 확장자이면서 허용 경로가 아닌 경우.
61
+ * @param {string} filePath
62
+ * @returns {boolean}
63
+ */
64
+ export function shouldCheckConsole(filePath) {
65
+ return CODE_EXT_RE.test(filePath) && !isConsoleAllowed(filePath);
66
+ }
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Hook dispatcher library — 여러 hook script를 단일 이벤트에서 병렬 실행.
2
+ * Hook dispatcher library — 여러 hook script를 단일 이벤트에서 실행.
3
3
  *
4
4
  * 목적:
5
5
  * - stdin을 한 번만 읽어 각 자식에 동일 버퍼로 pipe (중복 파싱/읽기 방지)
@@ -7,14 +7,23 @@
7
7
  * - 한 스크립트 실패가 다른 스크립트를 막지 않도록 cascade 격리
8
8
  * - PreToolUse 계열: 자식 중 하나라도 exit 2(deny)면 상위에 전파
9
9
  *
10
- * 직렬 병렬 전환 (2026-04):
11
- * 기존 직렬 실행은 tool당 150~300ms 누적 오버헤드를 유발.
12
- * PreToolUse 가드는 모두 독립적 검증자이므로 병렬화해도 의미상 문제 없음.
13
- * 트레이드오프:
14
- * - early-deny 낭비: sentinel-guard가 block이어도 pre-tool/scope-guard가
15
- * 이미 spawn됨. 실측 μs 수준이라 무시.
16
- * - stderr 인터리빙: 가드 2개가 동시 block 시 경고 메시지가 섞일 수 있음.
17
- * 메시지는 자체적으로 완결된 라인이라 가독성 문제 없음.
10
+ * 실행 모델이 둘로 갈린다 — 스텝이 서로 독립인지가 기준:
11
+ *
12
+ * dispatch() = 순차 (spawn). 유일한 사용처인 Stop 스텝끼리
13
+ * 부작용을 공유한다(auto-commit 의 git 상태를
14
+ * devlog-gen 읽는다). 병렬화하면 auto-commit
15
+ * git cascade 겹쳐 프로세스가 폭주하고,
16
+ * devlog 커밋 이전 상태를 관측한다.
17
+ * 회귀 방지: __tests__/stop-dispatcher-sequential.test.js
18
+ *
19
+ * dispatchInProcess() = 병렬 (import). PreToolUse 가드는 모두 독립적
20
+ * 검증자라 순서가 의미 없고, 직렬 실행은 tool당
21
+ * 150~300ms 누적 오버헤드를 유발한다.
22
+ * 트레이드오프:
23
+ * - early-deny 낭비: sentinel-guard가 block이어도
24
+ * pre-tool/scope-guard가 이미 실행됨. 실측 μs 수준.
25
+ * - stderr 인터리빙: 가드 2개가 동시 block 시 경고가
26
+ * 섞일 수 있음. 각 메시지가 완결된 라인이라 무해.
18
27
  */
19
28
  import { spawn } from 'child_process';
20
29
  import path from 'path';
@@ -47,6 +56,44 @@ function isEnabled(hookConfig, name) {
47
56
  return true;
48
57
  }
49
58
 
59
+ /**
60
+ * guard 크래시를 deny 로 승격할지 — `VIBE_HOOK_FAILCLOSED=0` 이면 끈다.
61
+ *
62
+ * fail-closed 는 "검증하지 못했으면 통과시키지 않는다" 는 안전한 기본값이지만,
63
+ * guard 에 버그가 생기면 Edit·Bash 가 전면 차단되어 guard 소스를 고칠 수단까지
64
+ * 막힌다. 그래서 탈출구는 선택이 아니라 **복구 유일 수단**이고, 차단 메시지가
65
+ * 스스로 그 방법을 알려줘야 한다 (교착에 빠진 사용자는 문서를 찾아볼 수 없다).
66
+ */
67
+ function isFailClosed() {
68
+ return process.env.VIBE_HOOK_FAILCLOSED !== '0';
69
+ }
70
+
71
+ /**
72
+ * step 크래시를 stderr 에 드러낸다. 침묵하면 게이트가 죽은 줄도 모른다.
73
+ * @param {string} name
74
+ * @param {unknown} err
75
+ */
76
+ function reportCrash(name, err) {
77
+ const message = (err && err.message) || String(err);
78
+ process.stderr.write(`[vibe] hook step "${name}" crashed: ${message}\n`);
79
+ }
80
+
81
+ /**
82
+ * 크래시한 step 의 종료 코드를 정한다.
83
+ * deny 권한(denyOnExit2)이 있는 guard 만 2(deny)로 승격한다 — 로깅용 step 하나가
84
+ * 죽었다고 도구 호출을 막을 이유는 없다.
85
+ * @param {{ name: string, denyOnExit2?: boolean }} step
86
+ * @returns {number}
87
+ */
88
+ function crashExitCode(step) {
89
+ if (!step.denyOnExit2 || !isFailClosed()) return 1;
90
+ process.stderr.write(
91
+ `[vibe] blocked: guard "${step.name}" could not complete, so the operation was not verified.\n` +
92
+ ` To bypass temporarily, re-run with VIBE_HOOK_FAILCLOSED=0\n`
93
+ );
94
+ return 2;
95
+ }
96
+
50
97
  async function readStdin() {
51
98
  if (process.stdin.isTTY) return '';
52
99
  let data = '';
@@ -93,7 +140,11 @@ function buildChildEnv(stdinData) {
93
140
  }
94
141
 
95
142
  /**
96
- * 디스패처 실행 — 활성화된 스텝을 병렬로 spawn.
143
+ * 디스패처 실행 — 활성화된 스텝을 선언 순서대로 **순차** spawn.
144
+ *
145
+ * 순차인 이유는 파일 상단 주석 참고 — 스텝이 git 상태 같은 부작용을 공유한다.
146
+ * 앞 스텝이 실패해도 다음 스텝은 계속 실행한다(cascade 격리 유지).
147
+ *
97
148
  * @param {Array<{name: string, script: string, args?: string[], denyOnExit2?: boolean, timeoutMs?: number}>} steps
98
149
  */
99
150
  export async function dispatch(steps) {
@@ -101,12 +152,11 @@ export async function dispatch(steps) {
101
152
  const hookConfig = loadHookConfig();
102
153
 
103
154
  const enabledSteps = steps.filter(s => isEnabled(hookConfig, s.name));
104
- const results = await Promise.all(
105
- enabledSteps.map(step =>
106
- runScript(step.script, step.args || [], stdinData, step.timeoutMs || 30000)
107
- .then(code => ({ step, code }))
108
- )
109
- );
155
+ const results = [];
156
+ for (const step of enabledSteps) {
157
+ const code = await runScript(step.script, step.args || [], stdinData, step.timeoutMs || 30000);
158
+ results.push({ step, code });
159
+ }
110
160
 
111
161
  // 하나라도 deny(exit 2) 반환 → 상위에 전파
112
162
  if (results.some(({ step, code }) => step.denyOnExit2 && code === 2)) {
@@ -138,9 +188,11 @@ export async function dispatchInProcess(steps, { argvToolName = '' } = {}) {
138
188
  enabledSteps.map(async (step) => {
139
189
  try {
140
190
  return { step, code: await step.run(ctx) };
141
- } catch {
142
- // 크래시 격리 실패 step은 exit 1 취급, 나머지 step과 디스패처는 계속
143
- return { step, code: 1 };
191
+ } catch (err) {
192
+ // 크래시 격리는 유지하되 침묵하지 않는다. deny 권한이 있는 guard
193
+ // 검증에 실패한 것이므로 통과시키지 않는다 (fail-closed).
194
+ reportCrash(step.name, err);
195
+ return { step, code: crashExitCode(step) };
144
196
  }
145
197
  })
146
198
  );
@@ -0,0 +1,60 @@
1
+ /**
2
+ * 루프 인박스 — 사람 리뷰 큐(`.vibe/loops/inbox.md`) 기록.
3
+ *
4
+ * loop-history.jsonl 은 결정론적으로 기록되는데 인박스만 모델이 마크다운을 직접
5
+ * prepend 하고 있었다 (감사 2026-07-28 L5). 블록 형식과 최신순 정렬을 코드가 보장한다.
6
+ *
7
+ * fail-open — 기록 실패가 루프를 멈추지 않는다.
8
+ */
9
+ import fs from 'fs';
10
+ import path from 'path';
11
+
12
+ const HEADER = '# Loop Inbox\n\n> 루프가 남긴 사람 리뷰 큐. 최신 항목이 위에 온다.\n';
13
+
14
+ function inboxPath(projectDir) {
15
+ return path.join(projectDir, '.vibe', 'loops', 'inbox.md');
16
+ }
17
+
18
+ /**
19
+ * 인박스 블록을 최상단에 prepend 한다.
20
+ *
21
+ * @param {string} projectDir
22
+ * @param {{ loop: string, result: 'ok'|'fail'|'stuck', at: string, lines?: string[] }} entry
23
+ * at 은 호출자가 넘긴다 — 이 모듈은 시각을 직접 읽지 않는다 (테스트 결정성)
24
+ * @returns {boolean} 성공 여부
25
+ */
26
+ export function prependInboxBlock(projectDir, entry) {
27
+ try {
28
+ if (!entry?.loop || !entry?.result || !entry?.at) return false;
29
+
30
+ const body = (entry.lines ?? []).map(l => `- ${l}`).join('\n');
31
+ const block = `## ${entry.loop} — ${entry.at} — ${entry.result}\n${body}\n`;
32
+
33
+ const target = inboxPath(projectDir);
34
+ fs.mkdirSync(path.dirname(target), { recursive: true });
35
+
36
+ const existing = fs.existsSync(target) ? fs.readFileSync(target, 'utf-8') : '';
37
+ const blocksStart = existing.indexOf('## ');
38
+ const head = blocksStart === -1 ? HEADER : existing.slice(0, blocksStart);
39
+ const rest = blocksStart === -1 ? '' : existing.slice(blocksStart);
40
+
41
+ fs.writeFileSync(target, `${head}\n${block}\n${rest}`.replace(/\n{3,}/g, '\n\n'), 'utf-8');
42
+ return true;
43
+ } catch {
44
+ return false;
45
+ }
46
+ }
47
+
48
+ /**
49
+ * 아직 처리되지 않은 블록 수 — `## ` 로 시작하는 줄의 개수.
50
+ * @param {string} projectDir
51
+ * @returns {number}
52
+ */
53
+ export function countInboxBlocks(projectDir) {
54
+ try {
55
+ const raw = fs.readFileSync(inboxPath(projectDir), 'utf-8');
56
+ return raw.split('\n').filter(l => l.startsWith('## ')).length;
57
+ } catch {
58
+ return 0;
59
+ }
60
+ }
@@ -6,12 +6,17 @@
6
6
  * node hooks/scripts/loop-ledger.js start <name>
7
7
  * node hooks/scripts/loop-ledger.js end <name> <ok|fail|stuck> [summary]
8
8
  * node hooks/scripts/loop-ledger.js check-stuck <name> <discoverHash>
9
+ * node hooks/scripts/loop-ledger.js anchor [feature]
10
+ * node hooks/scripts/loop-ledger.js inbox <name> <ok|fail|stuck> [line...]
9
11
  *
10
12
  * check-stuck: 'stuck' 또는 'ok'를 stdout에 출력하고 항상 exit 0.
13
+ * anchor: 재고정 번들 JSON을 stdout에 출력한다 (loop-contract ANCHOR 절).
11
14
  * 항상 exit 0 (fail-open).
12
15
  */
13
16
 
14
17
  import { appendLoopEvent, isStuck } from './lib/loop-ledger.js';
18
+ import { buildAnchor } from './lib/anchor.js';
19
+ import { prependInboxBlock } from './lib/inbox.js';
15
20
 
16
21
  const [, , subcommand, ...args] = process.argv;
17
22
  const projectDir = process.env.CLAUDE_PROJECT_DIR || process.cwd();
@@ -47,9 +52,27 @@ if (subcommand === 'start') {
47
52
  appendLoopEvent(projectDir, { loop, event: 'discover', discoverHash });
48
53
  process.stdout.write(stuck ? 'stuck\n' : 'ok\n');
49
54
 
55
+ } else if (subcommand === 'anchor') {
56
+ // 회전 시작 시 디스크 재고정 — 모델이 무엇을 다시 읽어야 하는지 결정론적으로 답한다
57
+ process.stdout.write(JSON.stringify(buildAnchor(projectDir, args[0]), null, 2) + '\n');
58
+
59
+ } else if (subcommand === 'inbox') {
60
+ const [loop, result, ...lines] = args;
61
+ if (!loop || !result) {
62
+ process.stdout.write('[loop-ledger] error: inbox 에 루프 이름과 결과(ok|fail|stuck)가 필요합니다\n');
63
+ process.exit(0);
64
+ }
65
+ const at = new Date().toISOString();
66
+ const ok = prependInboxBlock(projectDir, { loop, result, at, lines });
67
+ process.stdout.write(
68
+ ok ? `[loop-ledger] inbox recorded: loop=${loop} result=${result}\n`
69
+ : '[loop-ledger] WARNING: inbox write failed\n'
70
+ );
71
+
50
72
  } else {
51
73
  process.stdout.write(
52
- '[loop-ledger] 사용법: start <name> | end <name> <ok|fail|stuck> [summary] | check-stuck <name> <hash>\n'
74
+ '[loop-ledger] 사용법: start <name> | end <name> <ok|fail|stuck> [summary] | '
75
+ + 'check-stuck <name> <hash> | anchor [feature] | inbox <name> <ok|fail|stuck> [line...]\n'
53
76
  );
54
77
  }
55
78
 
@@ -10,7 +10,10 @@
10
10
  * auto-format — 코드 스타일 정규화 (변경 시 finding 반환)
11
11
  * code-check — 하드룰(any/console.log) 탐지 (additionalContext 주입만, 커밋 게이트 미연동)
12
12
  * auto-test — 관련 테스트 실행 (debounce 지원)
13
- * post-edit — console.log 감지
13
+ *
14
+ * post-edit.js 는 여기서 돌리지 않는다 — console.log 감지는 code-check 가 같은
15
+ * 허용 경로 규칙(lib/console-allow.js)으로 이미 수행한다. 둘 다 돌리면 허용
16
+ * 경로에서도 경고가 남는다. post-edit.js 는 antigravity-hooks.json 단독 등록용.
14
17
  *
15
18
  * 출력 계약 (Claude Code PostToolUse):
16
19
  * findings 있음 → stdout에 JSON hookSpecificOutput 1개 출력, exit 0
@@ -33,7 +36,6 @@ import path from 'path';
33
36
  import { run as autoFormat } from './auto-format.js';
34
37
  import { run as codeCheck } from './code-check.js';
35
38
  import { run as autoTest } from './auto-test.js';
36
- import { run as postEdit } from './post-edit.js';
37
39
 
38
40
  // ─── 설정 로딩 ────────────────────────────────────────────────────────
39
41
  function loadHookConfig() {
@@ -59,7 +61,6 @@ const steps = [
59
61
  { name: 'auto-format', run: autoFormat },
60
62
  { name: 'code-check', run: codeCheck },
61
63
  { name: 'auto-test', run: autoTest },
62
- { name: 'post-edit', run: postEdit },
63
64
  ];
64
65
 
65
66
  const enabledSteps = steps.filter(s => isEnabled(hookConfig, s.name));
@@ -10,9 +10,9 @@
10
10
  import { existsSync, readFileSync } from 'fs';
11
11
  import path from 'path';
12
12
  import { buildCliCtx, isDirectRun } from './lib/hook-context.js';
13
+ import { shouldCheckConsole } from './lib/console-allow.js';
13
14
 
14
15
  const CONSOLE_LOG_RE = /console\.log\(/;
15
- const CODE_EXT_RE = /\.(ts|tsx|js|jsx|mjs|cjs)$/;
16
16
 
17
17
  /**
18
18
  * in-process 진입점 — console.log 감지만 수행.
@@ -25,7 +25,7 @@ export async function run(ctx) {
25
25
  try {
26
26
  const filePath = ctx.filePath;
27
27
 
28
- if (filePath && CODE_EXT_RE.test(filePath)) {
28
+ if (filePath && shouldCheckConsole(filePath)) {
29
29
  const resolved = path.resolve(filePath);
30
30
  if (existsSync(resolved)) {
31
31
  const lines = readFileSync(resolved, 'utf-8').split('\n');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@su-record/vibe",
3
- "version": "3.2.13",
3
+ "version": "3.2.15",
4
4
  "description": "AI Coding Framework for Claude Code — 7+ agents, 52 skills, multi-LLM orchestration",
5
5
  "type": "module",
6
6
  "main": "dist/cli/index.js",
@@ -21,7 +21,7 @@
21
21
  "vibe": "dist/cli/index.js"
22
22
  },
23
23
  "scripts": {
24
- "build": "tsc && chmod +x dist/cli/index.js",
24
+ "build": "tsc -p tsconfig.build.json && chmod +x dist/cli/index.js",
25
25
  "dev": "tsc --watch",
26
26
  "gen:skill-docs": "npx tsx scripts/gen-skill-docs.ts",
27
27
  "gen:skill-docs:check": "npx tsx scripts/gen-skill-docs.ts --check",
@@ -31,8 +31,9 @@
31
31
  "validate:counts": "npx tsx scripts/validate-counts.ts",
32
32
  "test": "vitest run",
33
33
  "test:watch": "vitest",
34
+ "test:coverage": "vitest run --coverage",
34
35
  "prepublishOnly": "pnpm build",
35
- "postinstall": "node -e \"import('./dist/cli/postinstall/main.js').then(m=>m.main()).catch(()=>{})\"",
36
+ "postinstall": "node -e \"import('./dist/cli/postinstall/main.js').then(m=>m.main()).catch(e=>process.stderr.write('\\u26a0\\ufe0f vibe postinstall skipped: '+((e&&e.message)||e)+'\\n Assets and hooks were not installed. Run: npx vibe upgrade\\n'))\"",
36
37
  "release": "pnpm version patch && git push origin main --follow-tags"
37
38
  },
38
39
  "keywords": [
@@ -66,16 +67,17 @@
66
67
  "access": "public"
67
68
  },
68
69
  "engines": {
69
- "node": ">=18.0.0"
70
+ "node": ">=20.12.0"
70
71
  },
72
+ "packageManager": "pnpm@9.15.9",
71
73
  "optionalDependencies": {
72
- "@anthropic-ai/claude-agent-sdk": "^0.2.6",
74
+ "@anthropic-ai/claude-agent-sdk": "^0.2.38",
73
75
  "@ast-grep/napi": "^0.40.5"
74
76
  },
75
77
  "dependencies": {
76
78
  "@clack/prompts": "^1.0.0",
77
79
  "better-sqlite3": "^12.9.0",
78
- "chalk": "^5.3.0",
80
+ "chalk": "^5.6.2",
79
81
  "glob": "^13.0.1",
80
82
  "papaparse": "^5.5.3",
81
83
  "ts-morph": "^26.0.0",
@@ -84,11 +86,12 @@
84
86
  },
85
87
  "devDependencies": {
86
88
  "@types/better-sqlite3": "^7.6.13",
87
- "@types/node": "^22.0.0",
89
+ "@types/node": "^22.19.10",
88
90
  "@types/papaparse": "^5.5.2",
91
+ "@vitest/coverage-v8": "^4.1.10",
89
92
  "ajv": "^8.17.1",
90
- "typescript": "^5.5.4",
91
- "vitest": "^4.0.9"
93
+ "typescript": "^5.9.3",
94
+ "vitest": "^4.1.10"
92
95
  },
93
96
  "files": [
94
97
  "dist/",
@@ -97,6 +100,7 @@
97
100
  "agents/",
98
101
  "skills/",
99
102
  "hooks/",
103
+ "!hooks/scripts/__tests__",
100
104
  ".env.example",
101
105
  "CLAUDE.md",
102
106
  "README.md",