@polymorphism-tech/morph-spec 8.33.0 → 8.35.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (253) hide show
  1. package/CLAUDE.md +22 -2
  2. package/README.md +2 -2
  3. package/bin/morph-spec.js +43 -5
  4. package/claude-plugin.json +1 -1
  5. package/docs/CHEATSHEET.md +5 -4
  6. package/docs/QUICKSTART.md +1 -1
  7. package/framework/CLAUDE.md +2 -2
  8. package/framework/MORPH.md +20 -22
  9. package/framework/agents.json +198 -44
  10. package/framework/ai-pin.json +22 -0
  11. package/framework/commands/morph-media.md +45 -0
  12. package/framework/docs/CLI.md +238 -10
  13. package/framework/evals/proof-rubrics/README.md +76 -0
  14. package/framework/evals/proof-rubrics/eval-de-modelo.md +64 -0
  15. package/framework/evals/proof-rubrics/fiacao.md +54 -0
  16. package/framework/evals/proof-rubrics/guard-cego.md +52 -0
  17. package/framework/evals/proof-rubrics/media-claims.md +109 -0
  18. package/framework/evals/proof-rubrics/mutation-proof.md +77 -0
  19. package/framework/evals/proof-rubrics/observabilidade-genai.md +119 -0
  20. package/framework/evals/proof-rubrics/verde-nao-e-coberto.md +48 -0
  21. package/framework/evals/rubrics/architecture.md +30 -0
  22. package/framework/evals/rubrics/code-quality.md +26 -0
  23. package/framework/evals/rubrics/test-coverage.md +20 -0
  24. package/framework/hooks/README.md +11 -1
  25. package/framework/hooks/claude-code/core/block-destructive-worktree-removal.js +5 -8
  26. package/framework/hooks/claude-code/core/block-raw-worktree-add.js +79 -0
  27. package/framework/hooks/claude-code/core/chain.js +27 -7
  28. package/framework/hooks/claude-code/core/protect-spec-files.js +41 -4
  29. package/framework/hooks/claude-code/core/worktree-context.js +83 -14
  30. package/framework/hooks/claude-code/core/worktree-create.js +206 -0
  31. package/framework/hooks/claude-code/core/worktree-env-inject.js +87 -0
  32. package/framework/hooks/claude-code/core/worktree-remove.js +81 -0
  33. package/framework/hooks/shared/command-tokens.js +23 -0
  34. package/framework/hooks/shared/e2e-check.js +0 -0
  35. package/framework/hooks/shared/git-paths.js +25 -0
  36. package/framework/hooks/shared/hook-response.js +17 -0
  37. package/framework/hooks/shared/morph-cli.js +134 -0
  38. package/framework/rules/ai-agents-standards.md +14 -2
  39. package/framework/rules/infrastructure-standards.md +3 -1
  40. package/framework/schemas/ai-pin.schema.json +97 -0
  41. package/framework/schemas/config.schema.json +107 -0
  42. package/framework/schemas/feature.schema.json +23 -4
  43. package/framework/skills/core/create-umbraco-lp/SKILL.md +1 -1
  44. package/framework/skills/core/migrate-lp-to-umbraco/SKILL.md +1 -1
  45. package/framework/skills/core/morph-apply/SKILL.md +28 -7
  46. package/framework/skills/core/morph-brainstorming/SKILL.md +1 -1
  47. package/framework/skills/core/morph-eval/SKILL.md +1 -1
  48. package/framework/skills/core/morph-hotfix/SKILL.md +2 -2
  49. package/framework/skills/core/morph-implement/SKILL.md +20 -2
  50. package/framework/skills/core/morph-media/SKILL.md +215 -0
  51. package/framework/skills/core/morph-plan/SKILL.md +38 -4
  52. package/framework/skills/core/morph-proposal/SKILL.md +1 -1
  53. package/framework/skills/core/morph-review/SKILL.md +166 -9
  54. package/framework/skills/core/morph-stack-scan/SKILL.md +1 -1
  55. package/framework/skills/core/morph-standards/SKILL.md +1 -1
  56. package/framework/skills/core/morph-uiux/SKILL.md +1 -1
  57. package/framework/standards/PROVENANCE.json +81 -3
  58. package/framework/standards/STANDARDS.json +1504 -830
  59. package/framework/standards/ai-agents/agent-archetypes.md +160 -0
  60. package/framework/standards/ai-agents/agent-session.md +256 -160
  61. package/framework/standards/ai-agents/agent-spec.md +196 -0
  62. package/framework/standards/ai-agents/batching.md +217 -193
  63. package/framework/standards/ai-agents/context-providers.md +261 -188
  64. package/framework/standards/ai-agents/conversational-agent-with-phases.md +239 -0
  65. package/framework/standards/ai-agents/cost-and-budget.md +256 -0
  66. package/framework/standards/ai-agents/durable-workflows-hitl.md +98 -5
  67. package/framework/standards/ai-agents/evals-with-cache.md +289 -0
  68. package/framework/standards/ai-agents/guardrails.md +222 -0
  69. package/framework/standards/ai-agents/llm-runtime-defaults.md +226 -0
  70. package/framework/standards/ai-agents/mcp-server.md +351 -250
  71. package/framework/standards/ai-agents/mcp-tools.md +250 -197
  72. package/framework/standards/ai-agents/media-pipeline.md +352 -0
  73. package/framework/standards/ai-agents/media-video.md +267 -0
  74. package/framework/standards/ai-agents/middleware-patterns.md +241 -207
  75. package/framework/standards/ai-agents/modalities-image-gen.md +422 -173
  76. package/framework/standards/ai-agents/modalities-speech-to-text.md +177 -171
  77. package/framework/standards/ai-agents/modalities-text-to-speech.md +189 -181
  78. package/framework/standards/ai-agents/modalities-vision.md +205 -201
  79. package/framework/standards/ai-agents/multi-agent-patterns.md +211 -155
  80. package/framework/standards/ai-agents/observability-patterns.md +227 -17
  81. package/framework/standards/ai-agents/production.md +260 -231
  82. package/framework/standards/ai-agents/prompt-sources.md +389 -0
  83. package/framework/standards/ai-agents/providers/model-registry.md +293 -158
  84. package/framework/standards/ai-agents/rag-custom-pgvector.md +69 -4
  85. package/framework/standards/ai-agents/service-tier-flex.md +143 -135
  86. package/framework/standards/ai-agents/setup.md +166 -50
  87. package/framework/standards/ai-agents/structured-output.md +280 -249
  88. package/framework/standards/ai-agents/sweet-spot.md +129 -109
  89. package/framework/standards/ai-agents/testing-ai.md +236 -0
  90. package/framework/standards/ai-agents/vector-data-extensions.md +50 -20
  91. package/framework/standards/ai-agents/workflows.md +27 -7
  92. package/framework/standards/frontend/design-system/ai-image-generation.md +12 -0
  93. package/framework/standards/frontend/design-system/ai-video-generation.md +13 -0
  94. package/framework/standards/infrastructure/docker/local-compose-isolation.md +90 -0
  95. package/framework/templates/REGISTRY.json +14 -1
  96. package/framework/templates/code/dotnet/ai-agents/PromptRepository.cs.template +95 -95
  97. package/framework/templates/code/sql/ai-agents/agent-prompts-migration.sql.template +58 -58
  98. package/framework/templates/dotnet/ai-kit/Directory.Build.props +25 -0
  99. package/framework/templates/dotnet/ai-kit/Directory.Packages.props +26 -0
  100. package/framework/templates/dotnet/ai-kit/Morph.AiKit.sln +56 -0
  101. package/framework/templates/dotnet/ai-kit/NuGet.config +12 -0
  102. package/framework/templates/dotnet/ai-kit/README.md +128 -0
  103. package/framework/templates/dotnet/ai-kit/global.json +9 -0
  104. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Agents/AgentApi.cs +65 -0
  105. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Agents/AgentFactory.cs +289 -0
  106. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Agents/AgentFactoryOptions.cs +91 -0
  107. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Agents/AgentSpec.cs +91 -0
  108. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Agents/AgentSpecOptions.cs +181 -0
  109. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/AiKitAssemblyMarker.cs +14 -0
  110. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Conversation/ConversationStore.cs +174 -0
  111. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Conversation/IConversationStore.cs +54 -0
  112. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Conversation/InMemoryConversationStore.cs +75 -0
  113. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/CountingChatClient.cs +71 -0
  114. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/EvalAssertion.cs +246 -0
  115. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/EvalCacheMissException.cs +57 -0
  116. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/EvalCacheOptions.cs +78 -0
  117. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/EvalCacheStamp.cs +225 -0
  118. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/EvalFixture.cs +463 -0
  119. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/EvalMode.cs +32 -0
  120. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/EvalQualityEvaluators.cs +49 -0
  121. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/EvalScenario.cs +214 -0
  122. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/EvalSummary.cs +168 -0
  123. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/OfflineChatClient.cs +142 -0
  124. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/ShortPathResponseCache.cs +349 -0
  125. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/IMediaGenerator.cs +42 -0
  126. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/IMediaProvider.cs +77 -0
  127. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaArtifact.cs +28 -0
  128. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaDisclosure.cs +107 -0
  129. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaGenerator.cs +253 -0
  130. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaIdempotencyKey.cs +103 -0
  131. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaJob.cs +259 -0
  132. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaJobId.cs +21 -0
  133. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaJobState.cs +47 -0
  134. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaPricing.cs +102 -0
  135. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaProvenance.cs +44 -0
  136. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaRequest.cs +192 -0
  137. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaResult.cs +94 -0
  138. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaReview.cs +99 -0
  139. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaSeams.cs +97 -0
  140. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaStore.cs +133 -0
  141. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MorphMedia.cs +93 -0
  142. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/Providers/GoogleImageProvider.cs +112 -0
  143. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/Providers/OpenAIImageProvider.cs +288 -0
  144. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/Providers/VeoVideoProvider.cs +158 -0
  145. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Middleware/IUsageCostSink.cs +29 -0
  146. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Middleware/UsageCostMiddleware.cs +208 -0
  147. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Middleware/UsageCostRecord.cs +95 -0
  148. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Morph.AiKit.csproj +38 -0
  149. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Observability/MorphOpenTelemetryExtensions.cs +119 -0
  150. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Observability/MorphTelemetryOptions.cs +82 -0
  151. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Providers/IProviderClientFactory.cs +33 -0
  152. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Providers/ModelAlias.cs +267 -0
  153. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Providers/ModelRegistry.cs +474 -0
  154. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Providers/model-registry.v2.json +87 -0
  155. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Rag/DeltaIngest.cs +169 -0
  156. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Rag/DeltaPlan.cs +49 -0
  157. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/AgentFactoryTests.cs +495 -0
  158. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/AgentSpecTests.cs +260 -0
  159. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/ConversationStoreTests.cs +405 -0
  160. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/DeltaIngestTests.cs +379 -0
  161. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Evals/EvalAssertionTests.cs +197 -0
  162. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Evals/EvalCacheMissTests.cs +139 -0
  163. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Evals/EvalCacheStampTests.cs +157 -0
  164. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Evals/EvalFailuresTests.cs +178 -0
  165. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Evals/EvalFixtureTests.cs +153 -0
  166. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Evals/EvalRefreshTests.cs +194 -0
  167. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Evals/EvalWorkspace.cs +86 -0
  168. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Evals/KitEvals.cs +213 -0
  169. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Evals/OfflineChatClientTests.cs +95 -0
  170. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Fakes/FakeChatClient.cs +225 -0
  171. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Fakes/FakeChatClientTests.cs +215 -0
  172. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Fakes/FakeClock.cs +33 -0
  173. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Fakes/FakeMediaProvider.cs +89 -0
  174. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Fakes/FakeProviderClientFactory.cs +55 -0
  175. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Fakes/FakeUsageCostSink.cs +60 -0
  176. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Fakes/InMemoryMediaStore.cs +131 -0
  177. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/MediaAccessibilityTests.cs +133 -0
  178. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/MediaAliasIsNotAnAgentTests.cs +127 -0
  179. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/MediaDisclosureTests.cs +216 -0
  180. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/MediaGeneratorTests.cs +264 -0
  181. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/MediaIdempotencyKeyTests.cs +148 -0
  182. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/MediaJobStateTests.cs +184 -0
  183. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/MediaReviewTests.cs +89 -0
  184. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/MediaStoreTests.cs +98 -0
  185. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/ModelRegistryTests.cs +451 -0
  186. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Morph.AiKit.Tests.csproj +76 -0
  187. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/SkeletonSmokeTests.cs +33 -0
  188. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/TelemetryTests.cs +261 -0
  189. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/UsageCostMiddlewareTests.cs +374 -0
  190. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/eval-cache/.morph-eval-cache.json +12 -0
  191. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/eval-cache/cache/orcamento.acme.a-resposta-estruturada/1/CF70A7CE27BBD66A/contents.data +13 -0
  192. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/eval-cache/cache/orcamento.acme.a-resposta-estruturada/1/CF70A7CE27BBD66A/entry.json +7 -0
  193. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/eval-cache/cache/triagem.acme.a-saudacao/1/19F631228C6E00BA/contents.data +13 -0
  194. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/eval-cache/cache/triagem.acme.a-saudacao/1/19F631228C6E00BA/entry.json +7 -0
  195. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/eval-cache/cache/triagem.acme.b-escalonamento/1/A24A7C9BDD8239D8/contents.data +13 -0
  196. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/eval-cache/cache/triagem.acme.b-escalonamento/1/A24A7C9BDD8239D8/entry.json +7 -0
  197. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/eval-cache/cache/triagem.globex.a-saudacao/1/E38CD37DF003EDB7/contents.data +13 -0
  198. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/eval-cache/cache/triagem.globex.a-saudacao/1/E38CD37DF003EDB7/entry.json +7 -0
  199. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/eval-datasets/orcamento/acme/a-resposta-estruturada.json +24 -0
  200. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/eval-datasets/triagem/acme/a-saudacao.json +23 -0
  201. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/eval-datasets/triagem/acme/b-escalonamento.json +24 -0
  202. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/eval-datasets/triagem/globex/a-saudacao.json +23 -0
  203. package/framework/templates/infrastructure/docker/docker-compose.template.yml +24 -14
  204. package/package.json +3 -3
  205. package/src/commands/e2e/e2e.js +91 -40
  206. package/src/commands/project/doctor.js +143 -6
  207. package/src/commands/project/env.js +95 -0
  208. package/src/commands/project/update.js +469 -153
  209. package/src/commands/project/worktree.js +274 -53
  210. package/src/commands/state/advance.js +24 -2
  211. package/src/commands/state/approve.js +33 -1
  212. package/src/commands/state/create.js +72 -11
  213. package/src/commands/state/delete.js +8 -0
  214. package/src/commands/state/finish.js +360 -53
  215. package/src/commands/state/gate-check.js +190 -6
  216. package/src/commands/state/score.js +36 -1
  217. package/src/commands/validation/verify.js +134 -25
  218. package/src/core/state/state-manager.js +9 -1
  219. package/src/dashboard/app/pages/HookInspector.jsx +430 -418
  220. package/src/dashboard/scanner/hooks-scanner.js +5 -1
  221. package/src/lib/ai/ai-checks.js +182 -0
  222. package/src/lib/ai/ai-project-scan.js +347 -0
  223. package/src/lib/ai/code-audit.js +181 -0
  224. package/src/lib/ai/comment-stripper.js +184 -0
  225. package/src/lib/ai/pin-drift.js +138 -0
  226. package/src/lib/ai/registry-audit.js +285 -0
  227. package/src/lib/e2e/compose-env.js +55 -14
  228. package/src/lib/e2e/stack-teardown.js +115 -0
  229. package/src/lib/evaluator-report.js +269 -15
  230. package/src/lib/generators/architecture-scanner.js +2 -1
  231. package/src/lib/git/default-branch.js +92 -4
  232. package/src/lib/graph/graphifyignore.js +2 -1
  233. package/src/lib/installers/effects.js +193 -0
  234. package/src/lib/installers/templates-reconcile.js +3 -3
  235. package/src/lib/risk-detector.js +284 -0
  236. package/src/lib/standards/verified-against.js +276 -0
  237. package/src/lib/tasks/eval-node.js +485 -0
  238. package/src/lib/tasks/linux-runner.js +297 -0
  239. package/src/lib/tasks/test-nodes.js +486 -0
  240. package/src/lib/tasks/test-runner.js +431 -37
  241. package/src/lib/validators/verify-runner.js +590 -28
  242. package/src/lib/worktree/dirs.js +32 -0
  243. package/src/lib/worktree/docker-audit.js +287 -0
  244. package/src/lib/worktree/env-allocator.js +69 -11
  245. package/src/lib/worktree/worktree-audit.js +70 -3
  246. package/src/scripts/setup-infra.js +24 -11
  247. package/src/utils/agents-installer.js +18 -8
  248. package/src/utils/claude-md-injector.js +10 -5
  249. package/src/utils/file-copier.js +28 -16
  250. package/src/utils/hooks-installer.js +75 -29
  251. package/src/utils/skills-installer.js +194 -187
  252. package/src/utils/version-checker.js +68 -3
  253. package/framework/standards/ai-agents/prompts-in-db.md +0 -214
@@ -1,418 +1,430 @@
1
- import { useState, useMemo, useCallback } from 'react';
2
- import { Handle, Position, MarkerType } from '@xyflow/react';
3
- import morphData from 'virtual:morph-data';
4
- import FlowGraph from '../components/FlowGraph.jsx';
5
- import DetailPanel from '../components/DetailPanel.jsx';
6
- import { useDetailPanel } from '../hooks/useDetailPanel.js';
7
-
8
- // Event color palette
9
- const EVENT_COLORS = {
10
- 'session-start': { color: '#34d399', bg: '#064e3b', label: 'Session Start' },
11
- 'user-prompt': { color: '#fbbf24', bg: '#78350f', label: 'User Prompt' },
12
- 'pre-tool-use': { color: '#60a5fa', bg: '#1e3a5f', label: 'Pre-Tool-Use' },
13
- 'post-tool-use': { color: '#f472b6', bg: '#500724', label: 'Post-Tool-Use' },
14
- 'pre-compact': { color: '#a78bfa', bg: '#2e1065', label: 'Pre-Compact' },
15
- 'stop': { color: '#ef4444', bg: '#450a0a', label: 'Stop' },
16
- 'teammate-idle': { color: '#94a3b8', bg: '#1e293b', label: 'Teammate Idle' },
17
- };
18
-
19
- // Node dimensions
20
- const NODE_W = 200;
21
- const NODE_H = 120; // base; expands with hooks
22
-
23
- // ─── Custom: EventGroupNode ───────────────────────────────────────────────────
24
- function EventGroupNode({ data }) {
25
- const { eventKey, hooks, canBlock } = data;
26
- const palette = EVENT_COLORS[eventKey] || { color: '#94a3b8', bg: '#1e293b', label: eventKey };
27
-
28
- return (
29
- <div
30
- className="rounded-xl px-4 py-3 min-w-[190px] cursor-pointer select-none"
31
- style={{
32
- background: palette.bg,
33
- border: `2px solid ${palette.color}`,
34
- boxShadow: `0 0 12px ${palette.color}33`,
35
- }}
36
- >
37
- <Handle type="target" position={Position.Left}
38
- style={{ background: palette.color, width: 8, height: 8, border: 'none' }} />
39
-
40
- {/* Header */}
41
- <div className="flex items-center justify-between mb-2">
42
- <span className="text-white text-xs font-bold tracking-wide uppercase">{palette.label}</span>
43
- {canBlock && (
44
- <span className="text-[9px] font-bold px-1.5 py-0.5 rounded"
45
- style={{ background: '#1e40af', color: '#93c5fd', border: '1px solid #3b82f6' }}>
46
- can BLOCK
47
- </span>
48
- )}
49
- </div>
50
-
51
- {/* Hook list */}
52
- <ul className="space-y-0.5">
53
- {hooks.map((h) => (
54
- <li key={h.name}
55
- className="text-[10px] font-mono truncate"
56
- style={{ color: palette.color }}
57
- title={h.name}>
58
- · {h.name}
59
- </li>
60
- ))}
61
- </ul>
62
-
63
- <Handle type="source" position={Position.Right}
64
- style={{ background: palette.color, width: 8, height: 8, border: 'none' }} />
65
- </div>
66
- );
67
- }
68
-
69
- // ─── Custom: ToolNode ─────────────────────────────────────────────────────────
70
- function ToolNode({ data }) {
71
- return (
72
- <div
73
- className="rounded-xl px-5 py-3 select-none text-center min-w-[140px]"
74
- style={{
75
- background: '#374151',
76
- border: '2px solid #6b7280',
77
- boxShadow: '0 0 10px #6b728033',
78
- }}
79
- >
80
- <Handle type="target" position={Position.Left}
81
- style={{ background: '#9ca3af', width: 8, height: 8, border: 'none' }} />
82
- <div className="text-gray-100 text-sm font-bold">TOOL</div>
83
- <div className="text-gray-400 text-[10px] mt-0.5">executes</div>
84
- <Handle type="source" position={Position.Right}
85
- style={{ background: '#9ca3af', width: 8, height: 8, border: 'none' }} />
86
- </div>
87
- );
88
- }
89
-
90
- const nodeTypes = {
91
- eventGroupNode: EventGroupNode,
92
- toolNode: ToolNode,
93
- };
94
-
95
- // ─── Layout: manual horizontal positions ─────────────────────────────────────
96
- // Flow: session-start → user-prompt → pre-tool-use → TOOL → post-tool-use → stop
97
- // → pre-compact
98
- // teammate-idle is disconnected at bottom
99
- // Loop-back: post-tool-use → user-prompt (dashed)
100
-
101
- const COL_GAP = 260;
102
- const ROW_Y = 100;
103
- const BRANCH_Y_STOP = 80;
104
- const BRANCH_Y_COMPACT = 240;
105
-
106
- function buildNodesAndEdges(hooksByEvent) {
107
- // Main flow columns (x positions)
108
- const cols = {
109
- 'session-start': 0,
110
- 'user-prompt': COL_GAP,
111
- 'pre-tool-use': COL_GAP * 2,
112
- '__tool__': COL_GAP * 3,
113
- 'post-tool-use': COL_GAP * 4,
114
- 'stop': COL_GAP * 5,
115
- 'pre-compact': COL_GAP * 5,
116
- 'teammate-idle': COL_GAP * 2,
117
- };
118
-
119
- const nodes = [];
120
- const edges = [];
121
-
122
- // Helper to build an EventGroupNode
123
- function addEventNode(eventKey, x, y, extra = {}) {
124
- const hooks = hooksByEvent[eventKey] || [];
125
- nodes.push({
126
- id: eventKey,
127
- type: 'eventGroupNode',
128
- position: { x, y },
129
- data: {
130
- eventKey,
131
- hooks,
132
- canBlock: eventKey === 'pre-tool-use',
133
- color: (EVENT_COLORS[eventKey] || {}).color || '#94a3b8',
134
- ...extra,
135
- },
136
- });
137
- }
138
-
139
- // Main flow nodes
140
- addEventNode('session-start', cols['session-start'], ROW_Y);
141
- addEventNode('user-prompt', cols['user-prompt'], ROW_Y);
142
- addEventNode('pre-tool-use', cols['pre-tool-use'], ROW_Y);
143
-
144
- // TOOL node
145
- nodes.push({
146
- id: '__tool__',
147
- type: 'toolNode',
148
- position: { x: cols['__tool__'], y: ROW_Y + 30 },
149
- data: { color: '#6b7280' },
150
- });
151
-
152
- addEventNode('post-tool-use', cols['post-tool-use'], ROW_Y);
153
-
154
- // Branches: stop (top branch) and pre-compact (bottom branch)
155
- addEventNode('stop', cols['stop'], BRANCH_Y_STOP);
156
- addEventNode('pre-compact', cols['pre-compact'], BRANCH_Y_COMPACT);
157
-
158
- // teammate-idle disconnected below main flow
159
- addEventNode('teammate-idle', cols['teammate-idle'] - COL_GAP * 0.5, ROW_Y + 280);
160
-
161
- // ─── Edges ────────────────────────────────────────────────────────────
162
- const solid = (id, source, target, color = '#475569') => ({
163
- id, source, target,
164
- type: 'smoothstep',
165
- style: { stroke: color, strokeWidth: 2 },
166
- markerEnd: { type: MarkerType.ArrowClosed, color },
167
- });
168
-
169
- edges.push(solid('e-ss-up', 'session-start', 'user-prompt', EVENT_COLORS['user-prompt'].color));
170
- edges.push(solid('e-up-ptu', 'user-prompt', 'pre-tool-use', EVENT_COLORS['pre-tool-use'].color));
171
- edges.push(solid('e-ptu-tool', 'pre-tool-use', '__tool__', '#6b7280'));
172
- edges.push(solid('e-tool-pou', '__tool__', 'post-tool-use', EVENT_COLORS['post-tool-use'].color));
173
-
174
- // Post-tool-use branches to stop and pre-compact
175
- edges.push(solid('e-pou-stop', 'post-tool-use', 'stop', EVENT_COLORS['stop'].color));
176
- edges.push(solid('e-pou-compact', 'post-tool-use', 'pre-compact', EVENT_COLORS['pre-compact'].color));
177
-
178
- // Loop-back dashed: post-tool-use → user-prompt
179
- edges.push({
180
- id: 'e-loop',
181
- source: 'post-tool-use',
182
- target: 'user-prompt',
183
- type: 'smoothstep',
184
- style: { stroke: '#fbbf24', strokeWidth: 1.5, strokeDasharray: '6 4' },
185
- label: 'conversation loop',
186
- labelStyle: { fill: '#fbbf24', fontSize: 10, fontFamily: 'monospace' },
187
- labelBgStyle: { fill: '#1c1917', fillOpacity: 0.85 },
188
- markerEnd: { type: MarkerType.ArrowClosed, color: '#fbbf24' },
189
- });
190
-
191
- return { nodes, edges };
192
- }
193
-
194
- // ─── HookDetail: detail panel content ────────────────────────────────────────
195
- function HookDetail({ hook }) {
196
- const [showSource, setShowSource] = useState(false);
197
- const palette = EVENT_COLORS[hook.event] || { color: '#94a3b8' };
198
-
199
- return (
200
- <div className="text-sm text-gray-200 space-y-3">
201
- {/* Meta */}
202
- <div>
203
- <p className="text-gray-400 text-xs">
204
- Event: <span className="font-mono" style={{ color: palette.color }}>{hook.event}</span>
205
- </p>
206
- <p className="text-gray-400 text-xs mt-0.5">
207
- File: <span className="text-gray-300 font-mono">{hook.filename}</span>
208
- </p>
209
- </div>
210
-
211
- {/* Description */}
212
- {hook.description && (
213
- <div>
214
- <p className="text-gray-500 text-[10px] uppercase tracking-wide mb-1">Description</p>
215
- <p className="text-gray-300 text-xs leading-relaxed">{hook.description}</p>
216
- </div>
217
- )}
218
-
219
- {/* Imports */}
220
- {hook.imports?.length > 0 && (
221
- <div>
222
- <p className="text-gray-500 text-[10px] uppercase tracking-wide mb-1.5">Imports</p>
223
- <div className="flex flex-wrap gap-1">
224
- {hook.imports.map((imp) => (
225
- <span key={imp}
226
- className="px-2 py-0.5 rounded text-[10px] font-mono bg-gray-800 border border-gray-700 text-gray-400"
227
- title={imp}>
228
- {imp.length > 28 ? '…' + imp.slice(-22) : imp}
229
- </span>
230
- ))}
231
- </div>
232
- </div>
233
- )}
234
-
235
- {/* Source toggle */}
236
- <div>
237
- <button
238
- onClick={() => setShowSource((v) => !v)}
239
- className="w-full text-left px-3 py-1.5 bg-gray-800 hover:bg-gray-700 border border-gray-700 rounded text-xs text-gray-300 transition-colors"
240
- >
241
- {showSource ? 'Hide Source' : 'Show Source'}
242
- </button>
243
- {showSource && (
244
- <pre className="mt-2 p-3 bg-gray-950 border border-gray-800 rounded text-[9px] text-gray-400 overflow-x-auto whitespace-pre font-mono leading-relaxed max-h-80 overflow-y-auto">
245
- {hook.source}
246
- </pre>
247
- )}
248
- </div>
249
- </div>
250
- );
251
- }
252
-
253
- // ─── EventGroupDetail: panel for clicking a group node ───────────────────────
254
- function EventGroupDetail({ eventKey, hooks, openHook }) {
255
- const palette = EVENT_COLORS[eventKey] || { color: '#94a3b8', label: eventKey };
256
- const isBlockable = eventKey === 'pre-tool-use';
257
-
258
- return (
259
- <div className="space-y-3">
260
- {/* Event header */}
261
- <div className="flex items-center gap-2">
262
- <span
263
- className="inline-block w-2.5 h-2.5 rounded-full flex-shrink-0"
264
- style={{ background: palette.color }}
265
- />
266
- <span className="text-gray-300 text-sm font-semibold">{palette.label}</span>
267
- {isBlockable && (
268
- <span className="text-[9px] font-bold px-1.5 py-0.5 rounded"
269
- style={{ background: '#1e40af', color: '#93c5fd', border: '1px solid #3b82f6' }}>
270
- can BLOCK
271
- </span>
272
- )}
273
- </div>
274
- <p className="text-gray-500 text-xs">{hooks.length} hook{hooks.length !== 1 ? 's' : ''} registered</p>
275
-
276
- {/* Hook list */}
277
- <ul className="space-y-2">
278
- {hooks.map((hook) => (
279
- <li key={hook.name}>
280
- <button
281
- onClick={() => openHook(hook)}
282
- className="w-full text-left px-3 py-2 rounded-lg border transition-colors hover:opacity-90"
283
- style={{
284
- background: '#111827',
285
- borderColor: palette.color + '55',
286
- }}
287
- >
288
- <p className="font-mono text-xs" style={{ color: palette.color }}>{hook.name}</p>
289
- {hook.description && (
290
- <p className="text-gray-400 text-[10px] mt-0.5 line-clamp-2 leading-snug">
291
- {hook.description}
292
- </p>
293
- )}
294
- </button>
295
- </li>
296
- ))}
297
- </ul>
298
- </div>
299
- );
300
- }
301
-
302
- // ─── Main Page ────────────────────────────────────────────────────────────────
303
- export default function HookInspector() {
304
- const { hooks } = morphData;
305
- const { panel, openPanel, closePanel } = useDetailPanel();
306
-
307
- // Group hooks by event
308
- const hooksByEvent = useMemo(() => {
309
- const map = {};
310
- for (const hook of hooks) {
311
- if (!map[hook.event]) map[hook.event] = [];
312
- map[hook.event].push(hook);
313
- }
314
- return map;
315
- }, [hooks]);
316
-
317
- // Build graph
318
- const { nodes, edges } = useMemo(() => buildNodesAndEdges(hooksByEvent), [hooksByEvent]);
319
-
320
- // Open panel: click on an event group → show its hooks list
321
- // From that list, user can click a hook → show hook detail
322
- const openHookDetail = useCallback((hook) => {
323
- const palette = EVENT_COLORS[hook.event] || { label: hook.event };
324
- openPanel(`${hook.name}`, <HookDetail hook={hook} />);
325
- }, [openPanel]);
326
-
327
- const handleNodeClick = useCallback((_event, node) => {
328
- if (node.type === 'toolNode') {
329
- openPanel('TOOL executes', (
330
- <div className="text-gray-300 text-sm space-y-2">
331
- <p>This represents the actual tool call Claude Code performs (e.g., <span className="font-mono text-blue-300">Bash</span>, <span className="font-mono text-blue-300">Read</span>, <span className="font-mono text-blue-300">Edit</span>).</p>
332
- <p className="text-gray-400 text-xs">Pre-Tool-Use hooks run before this and can block execution by returning a non-empty decision. Post-Tool-Use hooks run after and receive the tool result.</p>
333
- </div>
334
- ));
335
- return;
336
- }
337
- if (node.type === 'eventGroupNode') {
338
- const { eventKey, hooks: hooksInGroup } = node.data;
339
- const palette = EVENT_COLORS[eventKey] || { label: eventKey };
340
- openPanel(
341
- palette.label,
342
- <EventGroupDetail eventKey={eventKey} hooks={hooksInGroup} openHook={openHookDetail} />
343
- );
344
- }
345
- }, [openPanel, openHookDetail]);
346
-
347
- const totalHooks = hooks.length;
348
- const eventCount = Object.keys(hooksByEvent).length;
349
-
350
- return (
351
- <div className="flex flex-col h-full">
352
- {/* Header bar */}
353
- <div className="flex items-center justify-between px-5 py-3 bg-gray-900 border-b border-gray-800 flex-shrink-0">
354
- <div>
355
- <h1 className="text-white font-bold text-base leading-tight">Hook Inspector</h1>
356
- <p className="text-gray-400 text-xs mt-0.5">
357
- Lifecycle timeline — {totalHooks} hooks across {eventCount} event types. Click a node to inspect.
358
- </p>
359
- </div>
360
- <div className="flex items-center gap-3">
361
- {/* Legend */}
362
- <div className="flex flex-wrap gap-2">
363
- {Object.entries(EVENT_COLORS).map(([key, { color, label }]) => (
364
- <div key={key} className="flex items-center gap-1.5">
365
- <span className="inline-block w-2.5 h-2.5 rounded-full" style={{ background: color }} />
366
- <span className="text-gray-400 text-[10px] hidden lg:inline">{label}</span>
367
- </div>
368
- ))}
369
- </div>
370
- </div>
371
- </div>
372
-
373
- {/* Flow graph */}
374
- <div className="flex-1 relative">
375
- <FlowGraph
376
- nodes={nodes}
377
- edges={edges}
378
- nodeTypes={nodeTypes}
379
- onNodeClick={handleNodeClick}
380
- fitView
381
- />
382
-
383
- {/* Floating legend / instructions */}
384
- <div className="absolute top-3 left-3 bg-gray-900/90 border border-gray-700 rounded-lg p-3 text-xs space-y-2 z-10 max-w-[200px]">
385
- <p className="text-gray-400 font-semibold uppercase tracking-wide text-[10px] mb-1">Flow Key</p>
386
- <div className="flex items-center gap-2">
387
- <span className="inline-block w-6 border-t-2 border-yellow-400 border-dashed" />
388
- <span className="text-gray-400 text-[10px]">conversation loop</span>
389
- </div>
390
- <div className="flex items-center gap-2">
391
- <span className="inline-block w-2 h-2 rounded" style={{ background: '#374151', border: '1.5px solid #6b7280' }} />
392
- <span className="text-gray-400 text-[10px]">tool execution</span>
393
- </div>
394
- <div className="flex items-center gap-2">
395
- <span className="px-1.5 py-0.5 rounded text-[8px] font-bold"
396
- style={{ background: '#1e40af', color: '#93c5fd', border: '1px solid #3b82f6' }}>
397
- can BLOCK
398
- </span>
399
- <span className="text-gray-400 text-[10px]">pre-tool only</span>
400
- </div>
401
- <div className="flex items-center gap-2">
402
- <span className="text-gray-500 text-[10px] italic">teammate-idle</span>
403
- <span className="text-gray-500 text-[10px]">= disconnected</span>
404
- </div>
405
- </div>
406
-
407
- {/* Hook count badge */}
408
- <div className="absolute top-3 right-3 bg-gray-900/90 border border-gray-700 rounded-lg px-3 py-1.5 text-xs text-gray-400 z-10">
409
- {totalHooks} hooks · {eventCount} events
410
- </div>
411
- </div>
412
-
413
- <DetailPanel open={panel.open} title={panel.title} onClose={closePanel}>
414
- {panel.content}
415
- </DetailPanel>
416
- </div>
417
- );
418
- }
1
+ import { useState, useMemo, useCallback } from 'react';
2
+ import { Handle, Position, MarkerType } from '@xyflow/react';
3
+ import morphData from 'virtual:morph-data';
4
+ import FlowGraph from '../components/FlowGraph.jsx';
5
+ import DetailPanel from '../components/DetailPanel.jsx';
6
+ import { useDetailPanel } from '../hooks/useDetailPanel.js';
7
+
8
+ // Event color palette
9
+ const EVENT_COLORS = {
10
+ 'session-start': { color: '#34d399', bg: '#064e3b', label: 'Session Start' },
11
+ 'user-prompt': { color: '#fbbf24', bg: '#78350f', label: 'User Prompt' },
12
+ 'pre-tool-use': { color: '#60a5fa', bg: '#1e3a5f', label: 'Pre-Tool-Use' },
13
+ 'post-tool-use': { color: '#f472b6', bg: '#500724', label: 'Post-Tool-Use' },
14
+ 'pre-compact': { color: '#a78bfa', bg: '#2e1065', label: 'Pre-Compact' },
15
+ 'stop': { color: '#ef4444', bg: '#450a0a', label: 'Stop' },
16
+ 'teammate-idle': { color: '#94a3b8', bg: '#1e293b', label: 'Teammate Idle' },
17
+ 'worktree-create': { color: '#2dd4bf', bg: '#134e4a', label: 'Worktree Create' },
18
+ 'worktree-remove': { color: '#fb923c', bg: '#431407', label: 'Worktree Remove' },
19
+ };
20
+
21
+ // Node dimensions
22
+ const NODE_W = 200;
23
+ const NODE_H = 120; // base; expands with hooks
24
+
25
+ // ─── Custom: EventGroupNode ───────────────────────────────────────────────────
26
+ function EventGroupNode({ data }) {
27
+ const { eventKey, hooks, canBlock } = data;
28
+ const palette = EVENT_COLORS[eventKey] || { color: '#94a3b8', bg: '#1e293b', label: eventKey };
29
+
30
+ return (
31
+ <div
32
+ className="rounded-xl px-4 py-3 min-w-[190px] cursor-pointer select-none"
33
+ style={{
34
+ background: palette.bg,
35
+ border: `2px solid ${palette.color}`,
36
+ boxShadow: `0 0 12px ${palette.color}33`,
37
+ }}
38
+ >
39
+ <Handle type="target" position={Position.Left}
40
+ style={{ background: palette.color, width: 8, height: 8, border: 'none' }} />
41
+
42
+ {/* Header */}
43
+ <div className="flex items-center justify-between mb-2">
44
+ <span className="text-white text-xs font-bold tracking-wide uppercase">{palette.label}</span>
45
+ {canBlock && (
46
+ <span className="text-[9px] font-bold px-1.5 py-0.5 rounded"
47
+ style={{ background: '#1e40af', color: '#93c5fd', border: '1px solid #3b82f6' }}>
48
+ can BLOCK
49
+ </span>
50
+ )}
51
+ </div>
52
+
53
+ {/* Hook list */}
54
+ <ul className="space-y-0.5">
55
+ {hooks.map((h) => (
56
+ <li key={h.name}
57
+ className="text-[10px] font-mono truncate"
58
+ style={{ color: palette.color }}
59
+ title={h.name}>
60
+ · {h.name}
61
+ </li>
62
+ ))}
63
+ </ul>
64
+
65
+ <Handle type="source" position={Position.Right}
66
+ style={{ background: palette.color, width: 8, height: 8, border: 'none' }} />
67
+ </div>
68
+ );
69
+ }
70
+
71
+ // ─── Custom: ToolNode ─────────────────────────────────────────────────────────
72
+ function ToolNode({ data }) {
73
+ return (
74
+ <div
75
+ className="rounded-xl px-5 py-3 select-none text-center min-w-[140px]"
76
+ style={{
77
+ background: '#374151',
78
+ border: '2px solid #6b7280',
79
+ boxShadow: '0 0 10px #6b728033',
80
+ }}
81
+ >
82
+ <Handle type="target" position={Position.Left}
83
+ style={{ background: '#9ca3af', width: 8, height: 8, border: 'none' }} />
84
+ <div className="text-gray-100 text-sm font-bold">TOOL</div>
85
+ <div className="text-gray-400 text-[10px] mt-0.5">executes</div>
86
+ <Handle type="source" position={Position.Right}
87
+ style={{ background: '#9ca3af', width: 8, height: 8, border: 'none' }} />
88
+ </div>
89
+ );
90
+ }
91
+
92
+ const nodeTypes = {
93
+ eventGroupNode: EventGroupNode,
94
+ toolNode: ToolNode,
95
+ };
96
+
97
+ // ─── Layout: manual horizontal positions ─────────────────────────────────────
98
+ // Flow: session-start → user-prompt → pre-tool-use → TOOL → post-tool-use → stop
99
+ // → pre-compact
100
+ // teammate-idle is disconnected at bottom
101
+ // Loop-back: post-tool-use → user-prompt (dashed)
102
+
103
+ const COL_GAP = 260;
104
+ const ROW_Y = 100;
105
+ const BRANCH_Y_STOP = 80;
106
+ const BRANCH_Y_COMPACT = 240;
107
+
108
+ function buildNodesAndEdges(hooksByEvent) {
109
+ // Main flow columns (x positions)
110
+ const cols = {
111
+ 'session-start': 0,
112
+ 'user-prompt': COL_GAP,
113
+ 'pre-tool-use': COL_GAP * 2,
114
+ '__tool__': COL_GAP * 3,
115
+ 'post-tool-use': COL_GAP * 4,
116
+ 'stop': COL_GAP * 5,
117
+ 'pre-compact': COL_GAP * 5,
118
+ 'teammate-idle': COL_GAP * 2,
119
+ // Worktree lifecycle — outside the tool-call flow, drawn as a pair below it.
120
+ 'worktree-create': COL_GAP * 0,
121
+ 'worktree-remove': COL_GAP * 1,
122
+ };
123
+
124
+ const nodes = [];
125
+ const edges = [];
126
+
127
+ // Helper to build an EventGroupNode
128
+ function addEventNode(eventKey, x, y, extra = {}) {
129
+ const hooks = hooksByEvent[eventKey] || [];
130
+ nodes.push({
131
+ id: eventKey,
132
+ type: 'eventGroupNode',
133
+ position: { x, y },
134
+ data: {
135
+ eventKey,
136
+ hooks,
137
+ canBlock: eventKey === 'pre-tool-use',
138
+ color: (EVENT_COLORS[eventKey] || {}).color || '#94a3b8',
139
+ ...extra,
140
+ },
141
+ });
142
+ }
143
+
144
+ // Main flow nodes
145
+ addEventNode('session-start', cols['session-start'], ROW_Y);
146
+ addEventNode('user-prompt', cols['user-prompt'], ROW_Y);
147
+ addEventNode('pre-tool-use', cols['pre-tool-use'], ROW_Y);
148
+
149
+ // TOOL node
150
+ nodes.push({
151
+ id: '__tool__',
152
+ type: 'toolNode',
153
+ position: { x: cols['__tool__'], y: ROW_Y + 30 },
154
+ data: { color: '#6b7280' },
155
+ });
156
+
157
+ addEventNode('post-tool-use', cols['post-tool-use'], ROW_Y);
158
+
159
+ // Branches: stop (top branch) and pre-compact (bottom branch)
160
+ addEventNode('stop', cols['stop'], BRANCH_Y_STOP);
161
+ addEventNode('pre-compact', cols['pre-compact'], BRANCH_Y_COMPACT);
162
+
163
+ // teammate-idle disconnected below main flow
164
+ addEventNode('teammate-idle', cols['teammate-idle'] - COL_GAP * 0.5, ROW_Y + 280);
165
+
166
+ // Worktree lifecycle (WorktreeCreate → WorktreeRemove) — its own row below
167
+ // the main flow: these fire on `claude -w` / EnterWorktree / Agent isolation,
168
+ // not on a tool call.
169
+ addEventNode('worktree-create', cols['worktree-create'], ROW_Y + 280);
170
+ addEventNode('worktree-remove', cols['worktree-remove'], ROW_Y + 280);
171
+
172
+ // ─── Edges ────────────────────────────────────────────────────────────
173
+ const solid = (id, source, target, color = '#475569') => ({
174
+ id, source, target,
175
+ type: 'smoothstep',
176
+ style: { stroke: color, strokeWidth: 2 },
177
+ markerEnd: { type: MarkerType.ArrowClosed, color },
178
+ });
179
+
180
+ edges.push(solid('e-ss-up', 'session-start', 'user-prompt', EVENT_COLORS['user-prompt'].color));
181
+ edges.push(solid('e-up-ptu', 'user-prompt', 'pre-tool-use', EVENT_COLORS['pre-tool-use'].color));
182
+ edges.push(solid('e-ptu-tool', 'pre-tool-use', '__tool__', '#6b7280'));
183
+ edges.push(solid('e-tool-pou', '__tool__', 'post-tool-use', EVENT_COLORS['post-tool-use'].color));
184
+
185
+ // Post-tool-use branches to stop and pre-compact
186
+ edges.push(solid('e-pou-stop', 'post-tool-use', 'stop', EVENT_COLORS['stop'].color));
187
+ edges.push(solid('e-pou-compact', 'post-tool-use', 'pre-compact', EVENT_COLORS['pre-compact'].color));
188
+ edges.push(solid('e-wt-create-remove', 'worktree-create', 'worktree-remove', EVENT_COLORS['worktree-remove'].color));
189
+
190
+ // Loop-back dashed: post-tool-use → user-prompt
191
+ edges.push({
192
+ id: 'e-loop',
193
+ source: 'post-tool-use',
194
+ target: 'user-prompt',
195
+ type: 'smoothstep',
196
+ style: { stroke: '#fbbf24', strokeWidth: 1.5, strokeDasharray: '6 4' },
197
+ label: 'conversation loop',
198
+ labelStyle: { fill: '#fbbf24', fontSize: 10, fontFamily: 'monospace' },
199
+ labelBgStyle: { fill: '#1c1917', fillOpacity: 0.85 },
200
+ markerEnd: { type: MarkerType.ArrowClosed, color: '#fbbf24' },
201
+ });
202
+
203
+ return { nodes, edges };
204
+ }
205
+
206
+ // ─── HookDetail: detail panel content ────────────────────────────────────────
207
+ function HookDetail({ hook }) {
208
+ const [showSource, setShowSource] = useState(false);
209
+ const palette = EVENT_COLORS[hook.event] || { color: '#94a3b8' };
210
+
211
+ return (
212
+ <div className="text-sm text-gray-200 space-y-3">
213
+ {/* Meta */}
214
+ <div>
215
+ <p className="text-gray-400 text-xs">
216
+ Event: <span className="font-mono" style={{ color: palette.color }}>{hook.event}</span>
217
+ </p>
218
+ <p className="text-gray-400 text-xs mt-0.5">
219
+ File: <span className="text-gray-300 font-mono">{hook.filename}</span>
220
+ </p>
221
+ </div>
222
+
223
+ {/* Description */}
224
+ {hook.description && (
225
+ <div>
226
+ <p className="text-gray-500 text-[10px] uppercase tracking-wide mb-1">Description</p>
227
+ <p className="text-gray-300 text-xs leading-relaxed">{hook.description}</p>
228
+ </div>
229
+ )}
230
+
231
+ {/* Imports */}
232
+ {hook.imports?.length > 0 && (
233
+ <div>
234
+ <p className="text-gray-500 text-[10px] uppercase tracking-wide mb-1.5">Imports</p>
235
+ <div className="flex flex-wrap gap-1">
236
+ {hook.imports.map((imp) => (
237
+ <span key={imp}
238
+ className="px-2 py-0.5 rounded text-[10px] font-mono bg-gray-800 border border-gray-700 text-gray-400"
239
+ title={imp}>
240
+ {imp.length > 28 ? '…' + imp.slice(-22) : imp}
241
+ </span>
242
+ ))}
243
+ </div>
244
+ </div>
245
+ )}
246
+
247
+ {/* Source toggle */}
248
+ <div>
249
+ <button
250
+ onClick={() => setShowSource((v) => !v)}
251
+ className="w-full text-left px-3 py-1.5 bg-gray-800 hover:bg-gray-700 border border-gray-700 rounded text-xs text-gray-300 transition-colors"
252
+ >
253
+ {showSource ? 'Hide Source' : 'Show Source'}
254
+ </button>
255
+ {showSource && (
256
+ <pre className="mt-2 p-3 bg-gray-950 border border-gray-800 rounded text-[9px] text-gray-400 overflow-x-auto whitespace-pre font-mono leading-relaxed max-h-80 overflow-y-auto">
257
+ {hook.source}
258
+ </pre>
259
+ )}
260
+ </div>
261
+ </div>
262
+ );
263
+ }
264
+
265
+ // ─── EventGroupDetail: panel for clicking a group node ───────────────────────
266
+ function EventGroupDetail({ eventKey, hooks, openHook }) {
267
+ const palette = EVENT_COLORS[eventKey] || { color: '#94a3b8', label: eventKey };
268
+ const isBlockable = eventKey === 'pre-tool-use';
269
+
270
+ return (
271
+ <div className="space-y-3">
272
+ {/* Event header */}
273
+ <div className="flex items-center gap-2">
274
+ <span
275
+ className="inline-block w-2.5 h-2.5 rounded-full flex-shrink-0"
276
+ style={{ background: palette.color }}
277
+ />
278
+ <span className="text-gray-300 text-sm font-semibold">{palette.label}</span>
279
+ {isBlockable && (
280
+ <span className="text-[9px] font-bold px-1.5 py-0.5 rounded"
281
+ style={{ background: '#1e40af', color: '#93c5fd', border: '1px solid #3b82f6' }}>
282
+ can BLOCK
283
+ </span>
284
+ )}
285
+ </div>
286
+ <p className="text-gray-500 text-xs">{hooks.length} hook{hooks.length !== 1 ? 's' : ''} registered</p>
287
+
288
+ {/* Hook list */}
289
+ <ul className="space-y-2">
290
+ {hooks.map((hook) => (
291
+ <li key={hook.name}>
292
+ <button
293
+ onClick={() => openHook(hook)}
294
+ className="w-full text-left px-3 py-2 rounded-lg border transition-colors hover:opacity-90"
295
+ style={{
296
+ background: '#111827',
297
+ borderColor: palette.color + '55',
298
+ }}
299
+ >
300
+ <p className="font-mono text-xs" style={{ color: palette.color }}>{hook.name}</p>
301
+ {hook.description && (
302
+ <p className="text-gray-400 text-[10px] mt-0.5 line-clamp-2 leading-snug">
303
+ {hook.description}
304
+ </p>
305
+ )}
306
+ </button>
307
+ </li>
308
+ ))}
309
+ </ul>
310
+ </div>
311
+ );
312
+ }
313
+
314
+ // ─── Main Page ────────────────────────────────────────────────────────────────
315
+ export default function HookInspector() {
316
+ const { hooks } = morphData;
317
+ const { panel, openPanel, closePanel } = useDetailPanel();
318
+
319
+ // Group hooks by event
320
+ const hooksByEvent = useMemo(() => {
321
+ const map = {};
322
+ for (const hook of hooks) {
323
+ if (!map[hook.event]) map[hook.event] = [];
324
+ map[hook.event].push(hook);
325
+ }
326
+ return map;
327
+ }, [hooks]);
328
+
329
+ // Build graph
330
+ const { nodes, edges } = useMemo(() => buildNodesAndEdges(hooksByEvent), [hooksByEvent]);
331
+
332
+ // Open panel: click on an event group → show its hooks list
333
+ // From that list, user can click a hook → show hook detail
334
+ const openHookDetail = useCallback((hook) => {
335
+ const palette = EVENT_COLORS[hook.event] || { label: hook.event };
336
+ openPanel(`${hook.name}`, <HookDetail hook={hook} />);
337
+ }, [openPanel]);
338
+
339
+ const handleNodeClick = useCallback((_event, node) => {
340
+ if (node.type === 'toolNode') {
341
+ openPanel('TOOL executes', (
342
+ <div className="text-gray-300 text-sm space-y-2">
343
+ <p>This represents the actual tool call Claude Code performs (e.g., <span className="font-mono text-blue-300">Bash</span>, <span className="font-mono text-blue-300">Read</span>, <span className="font-mono text-blue-300">Edit</span>).</p>
344
+ <p className="text-gray-400 text-xs">Pre-Tool-Use hooks run before this and can block execution by returning a non-empty decision. Post-Tool-Use hooks run after and receive the tool result.</p>
345
+ </div>
346
+ ));
347
+ return;
348
+ }
349
+ if (node.type === 'eventGroupNode') {
350
+ const { eventKey, hooks: hooksInGroup } = node.data;
351
+ const palette = EVENT_COLORS[eventKey] || { label: eventKey };
352
+ openPanel(
353
+ palette.label,
354
+ <EventGroupDetail eventKey={eventKey} hooks={hooksInGroup} openHook={openHookDetail} />
355
+ );
356
+ }
357
+ }, [openPanel, openHookDetail]);
358
+
359
+ const totalHooks = hooks.length;
360
+ const eventCount = Object.keys(hooksByEvent).length;
361
+
362
+ return (
363
+ <div className="flex flex-col h-full">
364
+ {/* Header bar */}
365
+ <div className="flex items-center justify-between px-5 py-3 bg-gray-900 border-b border-gray-800 flex-shrink-0">
366
+ <div>
367
+ <h1 className="text-white font-bold text-base leading-tight">Hook Inspector</h1>
368
+ <p className="text-gray-400 text-xs mt-0.5">
369
+ Lifecycle timeline — {totalHooks} hooks across {eventCount} event types. Click a node to inspect.
370
+ </p>
371
+ </div>
372
+ <div className="flex items-center gap-3">
373
+ {/* Legend */}
374
+ <div className="flex flex-wrap gap-2">
375
+ {Object.entries(EVENT_COLORS).map(([key, { color, label }]) => (
376
+ <div key={key} className="flex items-center gap-1.5">
377
+ <span className="inline-block w-2.5 h-2.5 rounded-full" style={{ background: color }} />
378
+ <span className="text-gray-400 text-[10px] hidden lg:inline">{label}</span>
379
+ </div>
380
+ ))}
381
+ </div>
382
+ </div>
383
+ </div>
384
+
385
+ {/* Flow graph */}
386
+ <div className="flex-1 relative">
387
+ <FlowGraph
388
+ nodes={nodes}
389
+ edges={edges}
390
+ nodeTypes={nodeTypes}
391
+ onNodeClick={handleNodeClick}
392
+ fitView
393
+ />
394
+
395
+ {/* Floating legend / instructions */}
396
+ <div className="absolute top-3 left-3 bg-gray-900/90 border border-gray-700 rounded-lg p-3 text-xs space-y-2 z-10 max-w-[200px]">
397
+ <p className="text-gray-400 font-semibold uppercase tracking-wide text-[10px] mb-1">Flow Key</p>
398
+ <div className="flex items-center gap-2">
399
+ <span className="inline-block w-6 border-t-2 border-yellow-400 border-dashed" />
400
+ <span className="text-gray-400 text-[10px]">conversation loop</span>
401
+ </div>
402
+ <div className="flex items-center gap-2">
403
+ <span className="inline-block w-2 h-2 rounded" style={{ background: '#374151', border: '1.5px solid #6b7280' }} />
404
+ <span className="text-gray-400 text-[10px]">tool execution</span>
405
+ </div>
406
+ <div className="flex items-center gap-2">
407
+ <span className="px-1.5 py-0.5 rounded text-[8px] font-bold"
408
+ style={{ background: '#1e40af', color: '#93c5fd', border: '1px solid #3b82f6' }}>
409
+ can BLOCK
410
+ </span>
411
+ <span className="text-gray-400 text-[10px]">pre-tool only</span>
412
+ </div>
413
+ <div className="flex items-center gap-2">
414
+ <span className="text-gray-500 text-[10px] italic">teammate-idle</span>
415
+ <span className="text-gray-500 text-[10px]">= disconnected</span>
416
+ </div>
417
+ </div>
418
+
419
+ {/* Hook count badge */}
420
+ <div className="absolute top-3 right-3 bg-gray-900/90 border border-gray-700 rounded-lg px-3 py-1.5 text-xs text-gray-400 z-10">
421
+ {totalHooks} hooks · {eventCount} events
422
+ </div>
423
+ </div>
424
+
425
+ <DetailPanel open={panel.open} title={panel.title} onClose={closePanel}>
426
+ {panel.content}
427
+ </DetailPanel>
428
+ </div>
429
+ );
430
+ }