@sureshdsk/devflow-mcp 3.0.0 → 3.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (504) hide show
  1. package/.next/standalone/.next/BUILD_ID +1 -1
  2. package/.next/standalone/.next/build-manifest.json +4 -4
  3. package/.next/standalone/.next/prerender-manifest.json +3 -3
  4. package/.next/standalone/.next/required-server-files.json +1 -1
  5. package/.next/standalone/.next/server/app/_global-error/page/build-manifest.json +2 -2
  6. package/.next/standalone/.next/server/app/_global-error/page.js +1 -1
  7. package/.next/standalone/.next/server/app/_global-error/page.js.nft.json +1 -1
  8. package/.next/standalone/.next/server/app/_global-error.html +2 -2
  9. package/.next/standalone/.next/server/app/_global-error.rsc +1 -1
  10. package/.next/standalone/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
  11. package/.next/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  12. package/.next/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  13. package/.next/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  14. package/.next/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  15. package/.next/standalone/.next/server/app/_not-found/page/build-manifest.json +2 -2
  16. package/.next/standalone/.next/server/app/_not-found/page.js +2 -2
  17. package/.next/standalone/.next/server/app/_not-found/page.js.nft.json +1 -1
  18. package/.next/standalone/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  19. package/.next/standalone/.next/server/app/_not-found.html +1 -1
  20. package/.next/standalone/.next/server/app/_not-found.rsc +14 -13
  21. package/.next/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +14 -13
  22. package/.next/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  23. package/.next/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +5 -4
  24. package/.next/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  25. package/.next/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  26. package/.next/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -2
  27. package/.next/standalone/.next/server/app/api/projects/[id]/route.js +2 -2
  28. package/.next/standalone/.next/server/app/api/projects/[id]/route.js.nft.json +1 -1
  29. package/.next/standalone/.next/server/app/api/projects/route.js +2 -2
  30. package/.next/standalone/.next/server/app/api/projects/route.js.nft.json +1 -1
  31. package/.next/standalone/.next/server/app/api/specs/[name]/artifacts/[artifactType]/approve/route.js +2 -2
  32. package/.next/standalone/.next/server/app/api/specs/[name]/artifacts/[artifactType]/approve/route.js.nft.json +1 -1
  33. package/.next/standalone/.next/server/app/api/specs/[name]/artifacts/[artifactType]/route.js +2 -2
  34. package/.next/standalone/.next/server/app/api/specs/[name]/artifacts/[artifactType]/route.js.nft.json +1 -1
  35. package/.next/standalone/.next/server/app/api/specs/[name]/promote/route.js +2 -2
  36. package/.next/standalone/.next/server/app/api/specs/[name]/promote/route.js.nft.json +1 -1
  37. package/.next/standalone/.next/server/app/api/specs/[name]/route.js +2 -2
  38. package/.next/standalone/.next/server/app/api/specs/[name]/route.js.nft.json +1 -1
  39. package/.next/standalone/.next/server/app/api/specs/route.js +2 -2
  40. package/.next/standalone/.next/server/app/api/specs/route.js.nft.json +1 -1
  41. package/.next/standalone/.next/server/app/api/tasks/[id]/route.js +2 -2
  42. package/.next/standalone/.next/server/app/api/tasks/[id]/route.js.nft.json +1 -1
  43. package/.next/standalone/.next/server/app/api/tasks/route.js +2 -2
  44. package/.next/standalone/.next/server/app/api/tasks/route.js.nft.json +1 -1
  45. package/.next/standalone/.next/server/app/index.html +1 -1
  46. package/.next/standalone/.next/server/app/index.rsc +15 -14
  47. package/.next/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +2 -2
  48. package/.next/standalone/.next/server/app/index.segments/_full.segment.rsc +15 -14
  49. package/.next/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
  50. package/.next/standalone/.next/server/app/index.segments/_index.segment.rsc +5 -4
  51. package/.next/standalone/.next/server/app/index.segments/_tree.segment.rsc +2 -2
  52. package/.next/standalone/.next/server/app/page/build-manifest.json +2 -2
  53. package/.next/standalone/.next/server/app/page.js +2 -2
  54. package/.next/standalone/.next/server/app/page.js.nft.json +1 -1
  55. package/.next/standalone/.next/server/app/page_client-reference-manifest.js +1 -1
  56. package/.next/standalone/.next/server/app/specs/[name]/page/build-manifest.json +2 -2
  57. package/.next/standalone/.next/server/app/specs/[name]/page.js +2 -2
  58. package/.next/standalone/.next/server/app/specs/[name]/page.js.nft.json +1 -1
  59. package/.next/standalone/.next/server/app/specs/[name]/page_client-reference-manifest.js +1 -1
  60. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0be0eb8b._.js +3 -0
  61. package/.next/standalone/.next/server/chunks/{[root-of-the-server]__a9d478ee._.js → [root-of-the-server]__160c54ca._.js} +2 -2
  62. package/.next/standalone/.next/server/chunks/[root-of-the-server]__17ac51e3._.js +3 -0
  63. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1bb083e9._.js +3 -0
  64. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1dc1e944._.js +3 -0
  65. package/.next/standalone/.next/server/chunks/[root-of-the-server]__22ecfa2c._.js +3 -0
  66. package/.next/standalone/.next/server/chunks/{[root-of-the-server]__13a93eb6._.js → [root-of-the-server]__64710c0d._.js} +2 -2
  67. package/.next/standalone/.next/server/chunks/[root-of-the-server]__a2b7e5f2._.js +3 -0
  68. package/.next/standalone/.next/server/chunks/[root-of-the-server]__cf302bda._.js +3 -0
  69. package/.next/standalone/.next/server/chunks/[root-of-the-server]__d85482cd._.js +3 -0
  70. package/.next/standalone/.next/server/chunks/[root-of-the-server]__dfa21757._.js +3 -0
  71. package/.next/standalone/.next/server/chunks/_3f452afe._.js +2 -2
  72. package/.next/standalone/.next/server/chunks/src_db_index_ts_a71f50f0._.js +3 -0
  73. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__023e2c69._.js +3 -0
  74. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0a1a1435._.js +3 -0
  75. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0c49a387._.js +3 -0
  76. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1be1fc5f._.js +3 -0
  77. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__3a2f2efb._.js +3 -0
  78. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__4693399c._.js +3 -0
  79. package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__937c9212._.js → [root-of-the-server]__72a14301._.js} +2 -2
  80. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__8064ca06._.js +3 -0
  81. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__8b786d48._.js +3 -0
  82. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__8c052461._.js +3 -0
  83. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__8d306066._.js +3 -0
  84. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__9532fd59._.js +3 -0
  85. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__a119dd75._.js +3 -0
  86. package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__7fb6ef14._.js → [root-of-the-server]__a57b312b._.js} +2 -2
  87. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__a80bd0ea._.js +3 -0
  88. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__abe095b3._.js +3 -0
  89. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__ad3bd783._.js +3 -0
  90. package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__1e05fe0e._.js → [root-of-the-server]__ae9f05d6._.js} +2 -2
  91. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__c8781469._.js +3 -0
  92. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__c8a25070._.js +3 -0
  93. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__e8c1e595._.js +3 -0
  94. package/.next/standalone/.next/server/chunks/ssr/_0ff3de19._.js +1 -1
  95. package/.next/standalone/.next/server/chunks/ssr/node_modules_01a5cd4b._.js +3 -0
  96. package/.next/standalone/.next/server/chunks/ssr/node_modules_05c6819f._.js +31 -0
  97. package/.next/standalone/.next/server/chunks/ssr/node_modules_06c2c30a._.js +3 -0
  98. package/.next/standalone/.next/server/chunks/ssr/node_modules_09efe605._.js +3 -0
  99. package/.next/standalone/.next/server/chunks/ssr/{node_modules_mermaid_dist_chunks_mermaid_core_ef601841._.js → node_modules_0a439e3b._.js} +2 -2
  100. package/.next/standalone/.next/server/chunks/ssr/{node_modules_3507b41a._.js → node_modules_0e73ad6c._.js} +3 -3
  101. package/.next/standalone/.next/server/chunks/ssr/node_modules_0e7ff06d._.js +3 -0
  102. package/.next/standalone/.next/server/chunks/ssr/node_modules_0f73e25c._.js +3 -0
  103. package/.next/standalone/.next/server/chunks/ssr/node_modules_14cf9cea._.js +3 -0
  104. package/.next/standalone/.next/server/chunks/ssr/node_modules_19801f01._.js +3 -0
  105. package/.next/standalone/.next/server/chunks/ssr/node_modules_2235b8cf._.js +17 -0
  106. package/.next/standalone/.next/server/chunks/ssr/node_modules_23096c6d._.js +3 -0
  107. package/.next/standalone/.next/server/chunks/ssr/node_modules_23db4cf7._.js +17 -0
  108. package/.next/standalone/.next/server/chunks/ssr/node_modules_24381e95._.js +3 -0
  109. package/.next/standalone/.next/server/chunks/ssr/node_modules_24ca0eb6._.js +17 -0
  110. package/.next/standalone/.next/server/chunks/ssr/node_modules_27525793._.js +17 -0
  111. package/.next/standalone/.next/server/chunks/ssr/{node_modules_mermaid_dist_chunks_mermaid_core_4cfa6360._.js → node_modules_27c20382._.js} +2 -2
  112. package/.next/standalone/.next/server/chunks/ssr/node_modules_2c901b92._.js +3 -0
  113. package/.next/standalone/.next/server/chunks/ssr/node_modules_33b818b7._.js +45 -0
  114. package/.next/standalone/.next/server/chunks/ssr/node_modules_345d7791._.js +3 -0
  115. package/.next/standalone/.next/server/chunks/ssr/node_modules_36185c24._.js +45 -0
  116. package/.next/standalone/.next/server/chunks/ssr/node_modules_3ac7ce09._.js +3 -0
  117. package/.next/standalone/.next/server/chunks/ssr/node_modules_41b77cc7._.js +3 -0
  118. package/.next/standalone/.next/server/chunks/ssr/{node_modules_ed317ff6._.js → node_modules_42acd8db._.js} +2 -2
  119. package/.next/standalone/.next/server/chunks/ssr/node_modules_434aa5ec._.js +3 -0
  120. package/.next/standalone/.next/server/chunks/ssr/node_modules_54a9e72d._.js +17 -0
  121. package/.next/standalone/.next/server/chunks/ssr/node_modules_54f1273a._.js +26 -0
  122. package/.next/standalone/.next/server/chunks/ssr/node_modules_58d64491._.js +3 -0
  123. package/.next/standalone/.next/server/chunks/ssr/node_modules_5e012e3d._.js +3 -0
  124. package/.next/standalone/.next/server/chunks/ssr/node_modules_5ea9e4cf._.js +3 -0
  125. package/.next/standalone/.next/server/chunks/ssr/node_modules_5ecf8270._.js +26 -0
  126. package/.next/standalone/.next/server/chunks/ssr/node_modules_611c99b5._.js +1 -1
  127. package/.next/standalone/.next/server/chunks/ssr/{node_modules_d3-scale_src_e02ef77f._.js → node_modules_6629853d._.js} +2 -2
  128. package/.next/standalone/.next/server/chunks/ssr/node_modules_6fe2661c._.js +3 -0
  129. package/.next/standalone/.next/server/chunks/ssr/node_modules_7fe65a84._.js +3 -0
  130. package/.next/standalone/.next/server/chunks/ssr/node_modules_860f971a._.js +3 -0
  131. package/.next/standalone/.next/server/chunks/ssr/node_modules_882f3743._.js +3 -0
  132. package/.next/standalone/.next/server/chunks/ssr/node_modules_8e047938._.js +3 -0
  133. package/.next/standalone/.next/server/chunks/ssr/node_modules_9268cdc4._.js +3 -0
  134. package/.next/standalone/.next/server/chunks/ssr/node_modules_942541c9._.js +3 -0
  135. package/.next/standalone/.next/server/chunks/ssr/node_modules_9d52c0d6._.js +3 -0
  136. package/.next/standalone/.next/server/chunks/ssr/node_modules_@tanstack_query-core_build_modern_21ece5ea._.js +3 -0
  137. package/.next/standalone/.next/server/chunks/ssr/node_modules_ae6841d2._.js +17 -0
  138. package/.next/standalone/.next/server/chunks/ssr/node_modules_ba280bb0._.js +31 -0
  139. package/.next/standalone/.next/server/chunks/ssr/node_modules_bffdb8ec._.js +3 -0
  140. package/.next/standalone/.next/server/chunks/ssr/node_modules_c00f1821._.js +17 -0
  141. package/.next/standalone/.next/server/chunks/ssr/node_modules_c142ba86._.js +3 -0
  142. package/.next/standalone/.next/server/chunks/ssr/node_modules_d1a47cde._.js +17 -0
  143. package/.next/standalone/.next/server/chunks/ssr/node_modules_d9b454fe._.js +3 -0
  144. package/.next/standalone/.next/server/chunks/ssr/node_modules_dbe36b1d._.js +3 -0
  145. package/.next/standalone/.next/server/chunks/ssr/node_modules_ddbe26b7._.js +3 -0
  146. package/.next/standalone/.next/server/chunks/ssr/node_modules_e76b8bf2._.js +3 -0
  147. package/.next/standalone/.next/server/chunks/ssr/node_modules_e945b74d._.js +3 -0
  148. package/.next/standalone/.next/server/chunks/ssr/node_modules_fbd8a2ee._.js +17 -0
  149. package/.next/standalone/.next/server/chunks/ssr/node_modules_katex_dist_katex_mjs_31d456ba._.js +3 -0
  150. package/.next/standalone/.next/server/chunks/ssr/node_modules_katex_dist_katex_mjs_e1edc676._.js +3 -0
  151. package/.next/standalone/.next/server/chunks/ssr/node_modules_lodash-es_33e24dce._.js +3 -0
  152. package/.next/standalone/.next/server/chunks/ssr/node_modules_mermaid_dist_chunks_mermaid_core_chunk-QXUST7PY_mjs_159fba97._.js +1 -1
  153. package/.next/standalone/.next/server/chunks/ssr/node_modules_mermaid_dist_chunks_mermaid_core_chunk-S3R3BYOJ_mjs_dbeeb277._.js +1 -1
  154. package/.next/standalone/.next/server/chunks/ssr/node_modules_mermaid_dist_mermaid_core_mjs_a11916de._.js +9 -0
  155. package/.next/standalone/.next/server/chunks/ssr/src_66a70595._.js +3 -0
  156. package/.next/standalone/.next/server/chunks/ssr/src_components_kanban-board_tsx_7b875e8e._.js +1 -1
  157. package/.next/standalone/.next/server/middleware-build-manifest.js +2 -2
  158. package/.next/standalone/.next/server/pages/404.html +1 -1
  159. package/.next/standalone/.next/server/pages/500.html +2 -2
  160. package/.next/standalone/.next/server/server-reference-manifest.js +1 -1
  161. package/.next/standalone/.next/server/server-reference-manifest.json +1 -1
  162. package/.next/standalone/AGENTS.md +13 -7
  163. package/.next/standalone/CONTRIBUTING.md +42 -26
  164. package/.next/standalone/README.md +166 -25
  165. package/.next/standalone/bin/devflow.js +12 -5
  166. package/.next/standalone/bun.lock +78 -24
  167. package/.next/standalone/devflow/project-config.json +4 -0
  168. package/.next/standalone/devflow/schemas/api-first/schema.yaml +39 -0
  169. package/.next/standalone/devflow/schemas/api-first/templates/api-design.md +41 -0
  170. package/.next/standalone/devflow/schemas/api-first/templates/db-design.md +28 -0
  171. package/.next/standalone/devflow/schemas/api-first/templates/final-architecture.md +31 -0
  172. package/.next/standalone/devflow/schemas/api-first/templates/proposal.md +22 -0
  173. package/.next/standalone/devflow/schemas/api-first/templates/tasks.md +16 -0
  174. package/.next/standalone/devflow/specs/autodetect-skill-and-slash-command-installation/.approvals.json +1 -1
  175. package/.next/standalone/devflow/specs/autodetect-skill-and-slash-command-installation/.meta.json +1 -1
  176. package/.next/standalone/devflow/specs/autodetect-skill-and-slash-command-installation/design.md +11 -0
  177. package/.next/standalone/devflow/specs/autodetect-skill-and-slash-command-installation/proposal.md +3 -0
  178. package/.next/standalone/devflow/specs/autodetect-skill-and-slash-command-installation/tasks.md +34 -2
  179. package/.next/standalone/devflow/specs/precommit/.approvals.json +26 -0
  180. package/.next/standalone/devflow/specs/precommit/.meta.json +7 -0
  181. package/.next/standalone/devflow/specs/precommit/design.md +105 -0
  182. package/.next/standalone/devflow/specs/precommit/proposal.md +43 -0
  183. package/.next/standalone/devflow/specs/precommit/specs.md +89 -0
  184. package/.next/standalone/devflow/specs/precommit/tasks.md +283 -0
  185. package/.next/standalone/docs/custom-schemas.md +117 -0
  186. package/.next/standalone/docs/local-dev-guide.md +73 -0
  187. package/.next/standalone/drizzle.config.ts +7 -7
  188. package/.next/standalone/eslint.config.mjs +2 -2
  189. package/.next/standalone/instrumentation.ts +3 -5
  190. package/.next/standalone/next.config.ts +11 -2
  191. package/.next/standalone/node_modules/{sharp → libsql}/node_modules/detect-libc/package.json +4 -8
  192. package/.next/standalone/package-lock.json +5257 -2085
  193. package/.next/standalone/package.json +16 -7
  194. package/.next/standalone/postcss.config.mjs +1 -1
  195. package/.next/standalone/scripts/init-db.js +3 -5
  196. package/.next/standalone/scripts/init-db.test.js +6 -6
  197. package/.next/standalone/scripts/init-schema.js +22 -22
  198. package/.next/standalone/scripts/init-schema.test.js +45 -45
  199. package/.next/standalone/scripts/install-environments.js +27 -8
  200. package/.next/standalone/scripts/install-environments.test.js +19 -10
  201. package/.next/standalone/scripts/install-skills.js +104 -31
  202. package/.next/standalone/scripts/install-skills.test.js +6 -6
  203. package/.next/standalone/server.js +1 -1
  204. package/.next/standalone/src/app/api/agents/route.ts +2 -2
  205. package/.next/standalone/src/app/api/projects/[id]/route.ts +10 -19
  206. package/.next/standalone/src/app/api/projects/route.ts +4 -10
  207. package/.next/standalone/src/app/api/specs/[name]/artifacts/[artifactType]/approve/route.ts +8 -8
  208. package/.next/standalone/src/app/api/specs/[name]/artifacts/[artifactType]/route.ts +14 -14
  209. package/.next/standalone/src/app/api/specs/[name]/promote/route.ts +17 -23
  210. package/.next/standalone/src/app/api/specs/[name]/route.ts +34 -36
  211. package/.next/standalone/src/app/api/specs/route.ts +10 -10
  212. package/.next/standalone/src/app/api/tasks/[id]/route.ts +11 -33
  213. package/.next/standalone/src/app/api/tasks/route.ts +3 -6
  214. package/.next/standalone/src/app/globals.css +3 -3
  215. package/.next/standalone/src/app/layout.tsx +9 -5
  216. package/.next/standalone/src/app/page.tsx +1 -1
  217. package/.next/standalone/src/app/specs/[name]/page.tsx +26 -68
  218. package/.next/standalone/src/components/kanban-board.tsx +78 -157
  219. package/.next/standalone/src/components/kanban-column.tsx +10 -12
  220. package/.next/standalone/src/components/markdown-preview.tsx +36 -19
  221. package/.next/standalone/src/components/providers.tsx +20 -0
  222. package/.next/standalone/src/components/specs/artifact-dag-status.tsx +12 -14
  223. package/.next/standalone/src/components/specs/artifact-editor.tsx +55 -26
  224. package/.next/standalone/src/components/specs/spec-detail.tsx +48 -40
  225. package/.next/standalone/src/components/specs/spec-kanban-column.tsx +49 -68
  226. package/.next/standalone/src/components/specs/spec-modal.tsx +119 -83
  227. package/.next/standalone/src/components/task-card.tsx +34 -38
  228. package/.next/standalone/src/components/task-dialog.tsx +16 -31
  229. package/.next/standalone/src/components/ui/badge.tsx +12 -18
  230. package/.next/standalone/src/components/ui/button.tsx +23 -28
  231. package/.next/standalone/src/components/ui/card.tsx +42 -62
  232. package/.next/standalone/src/components/ui/dialog.tsx +17 -35
  233. package/.next/standalone/src/db/index.ts +10 -10
  234. package/.next/standalone/src/db/schema.ts +37 -37
  235. package/.next/standalone/src/hooks/use-queries.ts +81 -0
  236. package/.next/standalone/src/hooks/use-websocket.ts +74 -0
  237. package/.next/standalone/src/lib/schema.test.ts +37 -37
  238. package/.next/standalone/src/lib/schema.ts +77 -31
  239. package/.next/standalone/src/lib/specs-dir.ts +8 -8
  240. package/.next/standalone/src/lib/specs.ts +98 -92
  241. package/.next/standalone/src/lib/utils.ts +2 -2
  242. package/.next/standalone/src/mcp/server.ts +556 -349
  243. package/.next/standalone/src/mcp/websocket.ts +26 -23
  244. package/.next/standalone/src/schemas/backend-api/templates/proposal.md +6 -4
  245. package/.next/standalone/src/schemas/backend-api/templates/tasks.md +6 -0
  246. package/.next/standalone/src/schemas/data-engineering/templates/proposal.md +6 -4
  247. package/.next/standalone/src/schemas/data-engineering/templates/tasks.md +6 -0
  248. package/.next/standalone/src/schemas/devops-platform/templates/proposal.md +6 -4
  249. package/.next/standalone/src/schemas/devops-platform/templates/tasks.md +6 -0
  250. package/.next/standalone/src/schemas/frontend-product/templates/proposal.md +6 -4
  251. package/.next/standalone/src/schemas/frontend-product/templates/tasks.md +6 -0
  252. package/.next/standalone/src/schemas/spec-driven/templates/proposal.md +6 -4
  253. package/.next/standalone/src/schemas/spec-driven/templates/tasks.md +26 -0
  254. package/.next/standalone/src/websocket/server.ts +20 -18
  255. package/.next/standalone/tsconfig.json +3 -12
  256. package/.next/standalone/tsconfig.tsbuildinfo +1 -1
  257. package/.next/static/chunks/4f23f64d46848f57.js +1 -0
  258. package/.next/static/chunks/53081dcdcad4f31e.js +1 -0
  259. package/.next/static/chunks/{6a3d582315f1c214.js → 701cfcbb41e0ab47.js} +1 -1
  260. package/.next/static/chunks/ac4b6b4d5b7e486e.css +1 -0
  261. package/.next/static/chunks/d0309ea872db5d9f.js +1 -0
  262. package/.next/static/chunks/d4cf8893a018e965.js +1 -0
  263. package/.next/static/chunks/e6c0233269a4bd69.js +13 -0
  264. package/.next/static/chunks/{7e65cfa9841f66a5.js → e7d83ce855afe416.js} +2 -2
  265. package/.next/static/chunks/ee7035560fc9e5e9.js +1 -0
  266. package/.next/static/chunks/{turbopack-f4d3806ab575051d.js → turbopack-8b3b66a22a5e0fb4.js} +2 -2
  267. package/README.md +166 -25
  268. package/bin/devflow.js +12 -5
  269. package/drizzle.config.ts +7 -7
  270. package/next.config.ts +11 -2
  271. package/package.json +16 -7
  272. package/scripts/init-db.js +3 -5
  273. package/scripts/init-db.test.js +6 -6
  274. package/scripts/init-schema.js +22 -22
  275. package/scripts/init-schema.test.js +45 -45
  276. package/scripts/install-environments.js +27 -8
  277. package/scripts/install-environments.test.js +19 -10
  278. package/scripts/install-skills.js +104 -31
  279. package/scripts/install-skills.test.js +6 -6
  280. package/src/app/api/agents/route.ts +2 -2
  281. package/src/app/api/projects/[id]/route.ts +10 -19
  282. package/src/app/api/projects/route.ts +4 -10
  283. package/src/app/api/specs/[name]/artifacts/[artifactType]/approve/route.ts +8 -8
  284. package/src/app/api/specs/[name]/artifacts/[artifactType]/route.ts +14 -14
  285. package/src/app/api/specs/[name]/promote/route.ts +17 -23
  286. package/src/app/api/specs/[name]/route.ts +34 -36
  287. package/src/app/api/specs/route.ts +10 -10
  288. package/src/app/api/tasks/[id]/route.ts +11 -33
  289. package/src/app/api/tasks/route.ts +3 -6
  290. package/src/app/globals.css +3 -3
  291. package/src/app/layout.tsx +9 -5
  292. package/src/app/page.tsx +1 -1
  293. package/src/app/specs/[name]/page.tsx +26 -68
  294. package/src/components/kanban-board.tsx +78 -157
  295. package/src/components/kanban-column.tsx +10 -12
  296. package/src/components/markdown-preview.tsx +36 -19
  297. package/src/components/providers.tsx +20 -0
  298. package/src/components/specs/artifact-dag-status.tsx +12 -14
  299. package/src/components/specs/artifact-editor.tsx +55 -26
  300. package/src/components/specs/spec-detail.tsx +48 -40
  301. package/src/components/specs/spec-kanban-column.tsx +49 -68
  302. package/src/components/specs/spec-modal.tsx +119 -83
  303. package/src/components/task-card.tsx +34 -38
  304. package/src/components/task-dialog.tsx +16 -31
  305. package/src/components/ui/badge.tsx +12 -18
  306. package/src/components/ui/button.tsx +23 -28
  307. package/src/components/ui/card.tsx +42 -62
  308. package/src/components/ui/dialog.tsx +17 -35
  309. package/src/db/index.ts +10 -10
  310. package/src/db/schema.ts +37 -37
  311. package/src/hooks/use-queries.ts +81 -0
  312. package/src/hooks/use-websocket.ts +74 -0
  313. package/src/lib/schema.test.ts +37 -37
  314. package/src/lib/schema.ts +77 -31
  315. package/src/lib/specs-dir.ts +8 -8
  316. package/src/lib/specs.ts +98 -92
  317. package/src/lib/utils.ts +2 -2
  318. package/src/mcp/server.ts +556 -349
  319. package/src/mcp/websocket.ts +26 -23
  320. package/src/schemas/backend-api/templates/proposal.md +6 -4
  321. package/src/schemas/backend-api/templates/tasks.md +6 -0
  322. package/src/schemas/data-engineering/templates/proposal.md +6 -4
  323. package/src/schemas/data-engineering/templates/tasks.md +6 -0
  324. package/src/schemas/devops-platform/templates/proposal.md +6 -4
  325. package/src/schemas/devops-platform/templates/tasks.md +6 -0
  326. package/src/schemas/frontend-product/templates/proposal.md +6 -4
  327. package/src/schemas/frontend-product/templates/tasks.md +6 -0
  328. package/src/schemas/spec-driven/templates/proposal.md +6 -4
  329. package/src/schemas/spec-driven/templates/tasks.md +26 -0
  330. package/src/types/bun-sqlite.d.ts +1 -1
  331. package/src/websocket/server.ts +20 -18
  332. package/tsconfig.json +3 -12
  333. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1be4e4b6._.js +0 -3
  334. package/.next/standalone/.next/server/chunks/[root-of-the-server]__492a4a24._.js +0 -3
  335. package/.next/standalone/.next/server/chunks/[root-of-the-server]__67c59ae1._.js +0 -3
  336. package/.next/standalone/.next/server/chunks/[root-of-the-server]__6cc7aecd._.js +0 -3
  337. package/.next/standalone/.next/server/chunks/[root-of-the-server]__83a77b07._.js +0 -3
  338. package/.next/standalone/.next/server/chunks/[root-of-the-server]__88d91cb5._.js +0 -3
  339. package/.next/standalone/.next/server/chunks/[root-of-the-server]__a9e095e8._.js +0 -3
  340. package/.next/standalone/.next/server/chunks/[root-of-the-server]__b367d327._.js +0 -3
  341. package/.next/standalone/.next/server/chunks/[root-of-the-server]__eb413bbc._.js +0 -3
  342. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0b8c072a._.js +0 -3
  343. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__12b2191f._.js +0 -3
  344. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__15316462._.js +0 -3
  345. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__19c9c409._.js +0 -3
  346. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__19e5e22f._.js +0 -3
  347. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__25b5ccb3._.js +0 -3
  348. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__2f78c3da._.js +0 -3
  349. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__3160b3f4._.js +0 -9
  350. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__319559be._.js +0 -3
  351. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__34f7cbcb._.js +0 -31
  352. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__3abab94b._.js +0 -3
  353. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__3b3d8930._.js +0 -3
  354. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__4250799b._.js +0 -3
  355. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__4c29cbc3._.js +0 -26
  356. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__4e0bb6eb._.js +0 -9
  357. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__5678d2e3._.js +0 -3
  358. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__673445e0._.js +0 -31
  359. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__934de06b._.js +0 -3
  360. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__9b6c6f09._.js +0 -3
  361. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__a1b83a98._.js +0 -3
  362. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__b00a15c5._.js +0 -3
  363. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__b8567be3._.js +0 -26
  364. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__ce64536e._.js +0 -45
  365. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__e96d33d2._.js +0 -45
  366. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__fd7ec054._.js +0 -3
  367. package/.next/standalone/.next/server/chunks/ssr/node_modules_16a3f9e7._.js +0 -17
  368. package/.next/standalone/.next/server/chunks/ssr/node_modules_45bce0e1._.js +0 -3
  369. package/.next/standalone/.next/server/chunks/ssr/node_modules_5e5a372d._.js +0 -17
  370. package/.next/standalone/.next/server/chunks/ssr/node_modules_6ece6f1e._.js +0 -3
  371. package/.next/standalone/.next/server/chunks/ssr/node_modules_d173e749._.js +0 -3
  372. package/.next/standalone/.next/server/chunks/ssr/node_modules_d3-shape_src_arc_fb1ac087.js +0 -3
  373. package/.next/standalone/.next/server/chunks/ssr/node_modules_mermaid_dist_chunks_mermaid_core_1f73a830._.js +0 -3
  374. package/.next/standalone/.next/server/chunks/ssr/node_modules_mermaid_dist_chunks_mermaid_core_663ac803._.js +0 -3
  375. package/.next/standalone/.next/server/chunks/ssr/node_modules_mermaid_dist_chunks_mermaid_core_chunk-FMBD7UC4_mjs_4f485529._.js +0 -17
  376. package/.next/standalone/.next/server/chunks/ssr/node_modules_mermaid_dist_chunks_mermaid_core_chunk-TZMSLE5B_mjs_8436a62a._.js +0 -3
  377. package/.next/standalone/.next/server/chunks/ssr/node_modules_mermaid_dist_chunks_mermaid_core_f78d2dc4._.js +0 -3
  378. package/.next/standalone/.next/server/chunks/ssr/src_app_layout_tsx_cc8184fa._.js +0 -3
  379. package/.next/standalone/devflow/specs/add-default-schema-template-selection/.approvals.json +0 -26
  380. package/.next/standalone/devflow/specs/add-default-schema-template-selection/.meta.json +0 -8
  381. package/.next/standalone/devflow/specs/add-default-schema-template-selection/design.md +0 -126
  382. package/.next/standalone/devflow/specs/add-default-schema-template-selection/proposal.md +0 -65
  383. package/.next/standalone/devflow/specs/add-default-schema-template-selection/specs.md +0 -107
  384. package/.next/standalone/devflow/specs/add-default-schema-template-selection/tasks.md +0 -235
  385. package/.next/standalone/devflow/specs/test-no-project/.approvals.json +0 -17
  386. package/.next/standalone/devflow/specs/test-no-project/.meta.json +0 -6
  387. package/.next/standalone/devflow/specs/ui-cleanup/.approvals.json +0 -29
  388. package/.next/standalone/devflow/specs/ui-cleanup/.meta.json +0 -6
  389. package/.next/standalone/devflow/specs/ui-cleanup/design.md +0 -49
  390. package/.next/standalone/devflow/specs/ui-cleanup/proposal.md +0 -34
  391. package/.next/standalone/devflow/specs/ui-cleanup/specs.md +0 -55
  392. package/.next/standalone/devflow/specs/ui-cleanup/tasks.md +0 -28
  393. package/.next/standalone/node_modules/@img/colour/color.cjs +0 -1594
  394. package/.next/standalone/node_modules/@img/colour/index.cjs +0 -1
  395. package/.next/standalone/node_modules/@img/colour/package.json +0 -45
  396. package/.next/standalone/node_modules/@img/sharp-darwin-arm64/lib/sharp-darwin-arm64.node +0 -0
  397. package/.next/standalone/node_modules/@img/sharp-darwin-arm64/package.json +0 -40
  398. package/.next/standalone/node_modules/@img/sharp-libvips-darwin-arm64/README.md +0 -46
  399. package/.next/standalone/node_modules/@img/sharp-libvips-darwin-arm64/lib/glib-2.0/include/glibconfig.h +0 -220
  400. package/.next/standalone/node_modules/@img/sharp-libvips-darwin-arm64/lib/index.js +0 -1
  401. package/.next/standalone/node_modules/@img/sharp-libvips-darwin-arm64/lib/libvips-cpp.8.17.3.dylib +0 -0
  402. package/.next/standalone/node_modules/@img/sharp-libvips-darwin-arm64/package.json +0 -36
  403. package/.next/standalone/node_modules/@img/sharp-libvips-darwin-arm64/versions.json +0 -30
  404. package/.next/standalone/node_modules/buffer-from/index.js +0 -72
  405. package/.next/standalone/node_modules/buffer-from/package.json +0 -19
  406. package/.next/standalone/node_modules/sharp/lib/channel.js +0 -177
  407. package/.next/standalone/node_modules/sharp/lib/colour.js +0 -195
  408. package/.next/standalone/node_modules/sharp/lib/composite.js +0 -212
  409. package/.next/standalone/node_modules/sharp/lib/constructor.js +0 -499
  410. package/.next/standalone/node_modules/sharp/lib/index.js +0 -16
  411. package/.next/standalone/node_modules/sharp/lib/input.js +0 -809
  412. package/.next/standalone/node_modules/sharp/lib/is.js +0 -143
  413. package/.next/standalone/node_modules/sharp/lib/libvips.js +0 -207
  414. package/.next/standalone/node_modules/sharp/lib/operation.js +0 -1016
  415. package/.next/standalone/node_modules/sharp/lib/output.js +0 -1666
  416. package/.next/standalone/node_modules/sharp/lib/resize.js +0 -595
  417. package/.next/standalone/node_modules/sharp/lib/sharp.js +0 -121
  418. package/.next/standalone/node_modules/sharp/lib/utility.js +0 -291
  419. package/.next/standalone/node_modules/sharp/node_modules/detect-libc/lib/detect-libc.js +0 -313
  420. package/.next/standalone/node_modules/sharp/node_modules/detect-libc/lib/elf.js +0 -39
  421. package/.next/standalone/node_modules/sharp/node_modules/detect-libc/lib/filesystem.js +0 -51
  422. package/.next/standalone/node_modules/sharp/node_modules/detect-libc/lib/process.js +0 -24
  423. package/.next/standalone/node_modules/sharp/node_modules/semver/classes/comparator.js +0 -143
  424. package/.next/standalone/node_modules/sharp/node_modules/semver/classes/range.js +0 -557
  425. package/.next/standalone/node_modules/sharp/node_modules/semver/classes/semver.js +0 -333
  426. package/.next/standalone/node_modules/sharp/node_modules/semver/functions/cmp.js +0 -54
  427. package/.next/standalone/node_modules/sharp/node_modules/semver/functions/coerce.js +0 -62
  428. package/.next/standalone/node_modules/sharp/node_modules/semver/functions/compare.js +0 -7
  429. package/.next/standalone/node_modules/sharp/node_modules/semver/functions/eq.js +0 -5
  430. package/.next/standalone/node_modules/sharp/node_modules/semver/functions/gt.js +0 -5
  431. package/.next/standalone/node_modules/sharp/node_modules/semver/functions/gte.js +0 -5
  432. package/.next/standalone/node_modules/sharp/node_modules/semver/functions/lt.js +0 -5
  433. package/.next/standalone/node_modules/sharp/node_modules/semver/functions/lte.js +0 -5
  434. package/.next/standalone/node_modules/sharp/node_modules/semver/functions/neq.js +0 -5
  435. package/.next/standalone/node_modules/sharp/node_modules/semver/functions/parse.js +0 -18
  436. package/.next/standalone/node_modules/sharp/node_modules/semver/functions/satisfies.js +0 -12
  437. package/.next/standalone/node_modules/sharp/node_modules/semver/internal/constants.js +0 -37
  438. package/.next/standalone/node_modules/sharp/node_modules/semver/internal/debug.js +0 -11
  439. package/.next/standalone/node_modules/sharp/node_modules/semver/internal/identifiers.js +0 -29
  440. package/.next/standalone/node_modules/sharp/node_modules/semver/internal/lrucache.js +0 -42
  441. package/.next/standalone/node_modules/sharp/node_modules/semver/internal/parse-options.js +0 -17
  442. package/.next/standalone/node_modules/sharp/node_modules/semver/internal/re.js +0 -223
  443. package/.next/standalone/node_modules/sharp/node_modules/semver/package.json +0 -78
  444. package/.next/standalone/node_modules/sharp/package.json +0 -202
  445. package/.next/standalone/node_modules/source-map/lib/array-set.js +0 -121
  446. package/.next/standalone/node_modules/source-map/lib/base64-vlq.js +0 -140
  447. package/.next/standalone/node_modules/source-map/lib/base64.js +0 -67
  448. package/.next/standalone/node_modules/source-map/lib/binary-search.js +0 -111
  449. package/.next/standalone/node_modules/source-map/lib/mapping-list.js +0 -79
  450. package/.next/standalone/node_modules/source-map/lib/quick-sort.js +0 -114
  451. package/.next/standalone/node_modules/source-map/lib/source-map-consumer.js +0 -1145
  452. package/.next/standalone/node_modules/source-map/lib/source-map-generator.js +0 -425
  453. package/.next/standalone/node_modules/source-map/lib/source-node.js +0 -413
  454. package/.next/standalone/node_modules/source-map/lib/util.js +0 -488
  455. package/.next/standalone/node_modules/source-map/package.json +0 -73
  456. package/.next/standalone/node_modules/source-map/source-map.js +0 -8
  457. package/.next/standalone/node_modules/source-map-support/LICENSE.md +0 -21
  458. package/.next/standalone/node_modules/source-map-support/README.md +0 -284
  459. package/.next/standalone/node_modules/source-map-support/browser-source-map-support.js +0 -114
  460. package/.next/standalone/node_modules/source-map-support/package.json +0 -31
  461. package/.next/standalone/node_modules/source-map-support/register-hook-require.js +0 -1
  462. package/.next/standalone/node_modules/source-map-support/register.js +0 -1
  463. package/.next/standalone/node_modules/source-map-support/source-map-support.js +0 -625
  464. package/.next/standalone/node_modules/typescript/lib/_tsc.js +0 -133818
  465. package/.next/standalone/node_modules/typescript/lib/_tsserver.js +0 -659
  466. package/.next/standalone/node_modules/typescript/lib/_typingsInstaller.js +0 -222
  467. package/.next/standalone/node_modules/typescript/lib/cs/diagnosticMessages.generated.json +0 -2122
  468. package/.next/standalone/node_modules/typescript/lib/de/diagnosticMessages.generated.json +0 -2122
  469. package/.next/standalone/node_modules/typescript/lib/es/diagnosticMessages.generated.json +0 -2122
  470. package/.next/standalone/node_modules/typescript/lib/fr/diagnosticMessages.generated.json +0 -2122
  471. package/.next/standalone/node_modules/typescript/lib/it/diagnosticMessages.generated.json +0 -2122
  472. package/.next/standalone/node_modules/typescript/lib/ja/diagnosticMessages.generated.json +0 -2122
  473. package/.next/standalone/node_modules/typescript/lib/ko/diagnosticMessages.generated.json +0 -2122
  474. package/.next/standalone/node_modules/typescript/lib/pl/diagnosticMessages.generated.json +0 -2122
  475. package/.next/standalone/node_modules/typescript/lib/pt-br/diagnosticMessages.generated.json +0 -2122
  476. package/.next/standalone/node_modules/typescript/lib/ru/diagnosticMessages.generated.json +0 -2122
  477. package/.next/standalone/node_modules/typescript/lib/tr/diagnosticMessages.generated.json +0 -2122
  478. package/.next/standalone/node_modules/typescript/lib/tsc.js +0 -8
  479. package/.next/standalone/node_modules/typescript/lib/tsserver.js +0 -8
  480. package/.next/standalone/node_modules/typescript/lib/tsserverlibrary.js +0 -21
  481. package/.next/standalone/node_modules/typescript/lib/typesMap.json +0 -497
  482. package/.next/standalone/node_modules/typescript/lib/typescript.js +0 -200276
  483. package/.next/standalone/node_modules/typescript/lib/typingsInstaller.js +0 -8
  484. package/.next/standalone/node_modules/typescript/lib/watchGuard.js +0 -53
  485. package/.next/standalone/node_modules/typescript/lib/zh-cn/diagnosticMessages.generated.json +0 -2122
  486. package/.next/standalone/node_modules/typescript/lib/zh-tw/diagnosticMessages.generated.json +0 -2122
  487. package/.next/standalone/node_modules/typescript/package.json +0 -120
  488. package/.next/standalone/src/components/ui/collapsible.tsx +0 -12
  489. package/.next/standalone/src/components/ui/sheet.tsx +0 -130
  490. package/.next/standalone/src/components/ui/tabs.tsx +0 -58
  491. package/.next/static/chunks/0177331b8adf7346.js +0 -7
  492. package/.next/static/chunks/06af74e2f3e207c5.js +0 -7
  493. package/.next/static/chunks/1748d5cd2443723b.css +0 -1
  494. package/.next/static/chunks/7ae917cb100be2b9.js +0 -7
  495. package/.next/static/chunks/7c4b901b394c3a8c.js +0 -7
  496. package/src/components/ui/collapsible.tsx +0 -12
  497. package/src/components/ui/sheet.tsx +0 -130
  498. package/src/components/ui/tabs.tsx +0 -58
  499. /package/.next/standalone/node_modules/{detect-libc → libsql/node_modules/detect-libc}/lib/detect-libc.js +0 -0
  500. /package/.next/standalone/node_modules/{detect-libc → libsql/node_modules/detect-libc}/lib/filesystem.js +0 -0
  501. /package/.next/standalone/node_modules/{detect-libc → libsql/node_modules/detect-libc}/lib/process.js +0 -0
  502. /package/.next/static/{5YFrdIQd1UU2IxLxekrU9 → Ql6qo0GYLL23WRu6ec4N5}/_buildManifest.js +0 -0
  503. /package/.next/static/{5YFrdIQd1UU2IxLxekrU9 → Ql6qo0GYLL23WRu6ec4N5}/_clientMiddlewareManifest.json +0 -0
  504. /package/.next/static/{5YFrdIQd1UU2IxLxekrU9 → Ql6qo0GYLL23WRu6ec4N5}/_ssgManifest.js +0 -0
@@ -1,12 +1,13 @@
1
- "use client";
1
+ 'use client';
2
2
 
3
- import { useState } from "react";
4
- import { Button } from "@/components/ui/button";
5
- import { MarkdownPreview } from "@/components/markdown-preview";
3
+ import { useState, useEffect } from 'react';
4
+ import { Button } from '@/components/ui/button';
5
+ import { MarkdownPreview } from '@/components/markdown-preview';
6
+ import { useArtifact, useInvalidate } from '@/hooks/use-queries';
6
7
 
7
8
  interface ArtifactStatus {
8
9
  id: string;
9
- state: "blocked" | "ready" | "in_review" | "in_progress" | "done";
10
+ state: 'blocked' | 'ready' | 'in_review' | 'in_progress' | 'done';
10
11
  approved: boolean;
11
12
  approvedAt?: string;
12
13
  approvedBy?: string;
@@ -19,13 +20,13 @@ interface ArtifactEditorProps {
19
20
  content: string | null;
20
21
  status: ArtifactStatus;
21
22
  onSave: () => void;
22
- defaultMode?: "edit" | "preview";
23
+ defaultMode?: 'edit' | 'preview';
23
24
  }
24
25
 
25
26
  function relativeTime(iso: string): string {
26
27
  const diff = Date.now() - new Date(iso).getTime();
27
28
  const mins = Math.floor(diff / 60000);
28
- if (mins < 1) return "just now";
29
+ if (mins < 1) return 'just now';
29
30
  if (mins < 60) return `${mins}m ago`;
30
31
  const hours = Math.floor(mins / 60);
31
32
  if (hours < 24) return `${hours}h ago`;
@@ -42,21 +43,40 @@ export function ArtifactEditor({
42
43
  content,
43
44
  status,
44
45
  onSave,
45
- defaultMode = "edit",
46
+ defaultMode = 'edit',
46
47
  }: ArtifactEditorProps) {
47
- const [editContent, setEditContent] = useState(content || "");
48
+ const { data: artifactData, isLoading } = useArtifact(specName, artifactType);
49
+ const { invalidateArtifact, invalidateSpec, invalidateSpecs } = useInvalidate();
50
+ const [editContent, setEditContent] = useState(content || '');
48
51
  const [isSaving, setIsSaving] = useState(false);
49
52
  const [isApproving, setIsApproving] = useState(false);
50
- const [preview, setPreview] = useState(defaultMode === "preview");
53
+ const [preview, setPreview] = useState(defaultMode === 'preview');
54
+ const [hasLocalEdits, setHasLocalEdits] = useState(false);
55
+
56
+ // Sync from query data when it changes, but only if user hasn't edited locally
57
+ useEffect(() => {
58
+ if (artifactData?.content != null && !hasLocalEdits) {
59
+ setEditContent(artifactData.content);
60
+ }
61
+ }, [artifactData, hasLocalEdits]);
62
+
63
+ function handleContentChange(value: string) {
64
+ setEditContent(value);
65
+ setHasLocalEdits(true);
66
+ }
51
67
 
52
68
  async function handleSave() {
53
69
  setIsSaving(true);
54
70
  try {
55
71
  await fetch(`/api/specs/${specName}/artifacts/${artifactType}`, {
56
- method: "PUT",
57
- headers: { "Content-Type": "application/json" },
72
+ method: 'PUT',
73
+ headers: { 'Content-Type': 'application/json' },
58
74
  body: JSON.stringify({ content: editContent }),
59
75
  });
76
+ setHasLocalEdits(false);
77
+ invalidateArtifact(specName, artifactType);
78
+ invalidateSpec(specName);
79
+ invalidateSpecs();
60
80
  onSave();
61
81
  } finally {
62
82
  setIsSaving(false);
@@ -67,25 +87,34 @@ export function ArtifactEditor({
67
87
  setIsApproving(true);
68
88
  try {
69
89
  await fetch(`/api/specs/${specName}/artifacts/${artifactType}/approve`, {
70
- method: "POST",
71
- headers: { "Content-Type": "application/json" },
72
- body: JSON.stringify({ approvedBy: "human" }),
90
+ method: 'POST',
91
+ headers: { 'Content-Type': 'application/json' },
92
+ body: JSON.stringify({ approvedBy: 'human' }),
73
93
  });
94
+ invalidateArtifact(specName, artifactType);
95
+ invalidateSpec(specName);
96
+ invalidateSpecs();
74
97
  onSave();
75
98
  } finally {
76
99
  setIsApproving(false);
77
100
  }
78
101
  }
79
102
 
80
- if (status.state === "blocked") {
103
+ if (isLoading) {
104
+ return (
105
+ <div className="p-6 bg-gray-50 border-4 border-gray-200 text-center">
106
+ <p className="font-bold text-gray-500 uppercase">Loading...</p>
107
+ </div>
108
+ );
109
+ }
110
+
111
+ if (status.state === 'blocked') {
81
112
  return (
82
113
  <div className="p-6 bg-gray-100 border-4 border-gray-300 text-center">
83
114
  <p className="font-bold text-gray-600 uppercase">
84
115
  Blocked — approve required predecessors first
85
116
  </p>
86
- <p className="text-sm text-gray-500 mt-1">
87
- Required: {status.requires?.join(", ")}
88
- </p>
117
+ <p className="text-sm text-gray-500 mt-1">Required: {status.requires?.join(', ')}</p>
89
118
  </div>
90
119
  );
91
120
  }
@@ -98,7 +127,7 @@ export function ArtifactEditor({
98
127
  <button
99
128
  onClick={() => setPreview(false)}
100
129
  className={`px-3 py-1.5 text-sm font-bold uppercase tracking-wide transition-colors ${
101
- !preview ? "bg-black text-white" : "bg-white text-black hover:bg-gray-100"
130
+ !preview ? 'bg-black text-white' : 'bg-white text-black hover:bg-gray-100'
102
131
  }`}
103
132
  >
104
133
  Edit
@@ -106,7 +135,7 @@ export function ArtifactEditor({
106
135
  <button
107
136
  onClick={() => setPreview(true)}
108
137
  className={`px-3 py-1.5 text-sm font-bold uppercase tracking-wide border-l-4 border-black transition-colors ${
109
- preview ? "bg-black text-white" : "bg-white text-black hover:bg-gray-100"
138
+ preview ? 'bg-black text-white' : 'bg-white text-black hover:bg-gray-100'
110
139
  }`}
111
140
  >
112
141
  Preview
@@ -114,22 +143,22 @@ export function ArtifactEditor({
114
143
  </div>
115
144
 
116
145
  <div className="flex items-center gap-2">
117
- {status.state === "done" && status.approvedAt && (
146
+ {status.state === 'done' && status.approvedAt && (
118
147
  <span className="text-xs font-mono text-green-700 bg-green-100 border-2 border-green-600 px-2 py-1">
119
148
  ✓ approved {relativeTime(status.approvedAt)}
120
149
  </span>
121
150
  )}
122
151
  <Button onClick={handleSave} disabled={isSaving} size="sm">
123
- {isSaving ? "Saving..." : "Save"}
152
+ {isSaving ? 'Saving...' : 'Save'}
124
153
  </Button>
125
- {status.state === "in_review" && (
154
+ {status.state === 'in_review' && (
126
155
  <Button
127
156
  onClick={handleApprove}
128
157
  disabled={isApproving}
129
158
  size="sm"
130
159
  className="bg-green-600 hover:bg-green-700 text-white border-black"
131
160
  >
132
- {isApproving ? "Approving..." : "Approve"}
161
+ {isApproving ? 'Approving...' : 'Approve'}
133
162
  </Button>
134
163
  )}
135
164
  </div>
@@ -143,7 +172,7 @@ export function ArtifactEditor({
143
172
  <textarea
144
173
  className="flex-1 p-4 bg-white border-4 border-black font-mono text-sm resize-none focus:outline-none focus:ring-2 focus:ring-black"
145
174
  value={editContent}
146
- onChange={(e) => setEditContent(e.target.value)}
175
+ onChange={(e) => handleContentChange(e.target.value)}
147
176
  placeholder={`Write your ${artifactType} here...`}
148
177
  />
149
178
  )}
@@ -1,13 +1,13 @@
1
- "use client";
1
+ 'use client';
2
2
 
3
- import { useState } from "react";
4
- import { ArtifactDagStatus } from "./artifact-dag-status";
5
- import { ArtifactEditor } from "./artifact-editor";
6
- import { Button } from "@/components/ui/button";
3
+ import { useState } from 'react';
4
+ import { ArtifactDagStatus } from './artifact-dag-status';
5
+ import { ArtifactEditor } from './artifact-editor';
6
+ import { Button } from '@/components/ui/button';
7
7
 
8
8
  interface ArtifactStatus {
9
9
  id: string;
10
- state: "blocked" | "ready" | "in_review" | "in_progress" | "done";
10
+ state: 'blocked' | 'ready' | 'in_review' | 'in_progress' | 'done';
11
11
  description: string;
12
12
  requires: string[];
13
13
  fileExists: boolean;
@@ -34,18 +34,18 @@ interface SpecDetailProps {
34
34
  }
35
35
 
36
36
  const TASK_STATUS_COLORS: Record<string, string> = {
37
- backlog: "bg-gray-100 border-gray-400 text-gray-600",
38
- todo: "bg-blue-100 border-blue-400 text-blue-700",
39
- in_progress: "bg-orange-100 border-orange-400 text-orange-700",
40
- interrupted: "bg-red-100 border-red-400 text-red-700",
41
- done: "bg-green-100 border-green-500 text-green-700",
37
+ backlog: 'bg-gray-100 border-gray-400 text-gray-600',
38
+ todo: 'bg-blue-100 border-blue-400 text-blue-700',
39
+ in_progress: 'bg-orange-100 border-orange-400 text-orange-700',
40
+ interrupted: 'bg-red-100 border-red-400 text-red-700',
41
+ done: 'bg-green-100 border-green-500 text-green-700',
42
42
  };
43
43
 
44
44
  const PRIORITY_COLORS: Record<string, string> = {
45
- urgent: "text-red-600",
46
- high: "text-orange-500",
47
- medium: "text-gray-500",
48
- low: "text-gray-400",
45
+ urgent: 'text-red-600',
46
+ high: 'text-orange-500',
47
+ medium: 'text-gray-500',
48
+ low: 'text-gray-400',
49
49
  };
50
50
 
51
51
  export function SpecDetail({
@@ -56,18 +56,18 @@ export function SpecDetail({
56
56
  tasks,
57
57
  onRefresh,
58
58
  }: SpecDetailProps) {
59
- const [activeTab, setActiveTab] = useState(statuses[0]?.id || "proposal");
59
+ const [activeTab, setActiveTab] = useState(statuses[0]?.id || 'proposal');
60
60
  const [isPromoting, setIsPromoting] = useState(false);
61
61
 
62
- const artifactStatuses = statuses.filter((s) => s.id !== "development");
63
- const allArtifactsApproved = artifactStatuses.every((s) => s.state === "done");
64
- const developmentStatus = statuses.find((s) => s.id === "development");
62
+ const artifactStatuses = statuses.filter((s) => s.id !== 'development');
63
+ const allArtifactsApproved = artifactStatuses.every((s) => s.state === 'done');
64
+ const developmentStatus = statuses.find((s) => s.id === 'development');
65
65
 
66
66
  async function handlePromote() {
67
67
  setIsPromoting(true);
68
68
  try {
69
69
  const response = await fetch(`/api/specs/${specName}/promote`, {
70
- method: "POST",
70
+ method: 'POST',
71
71
  });
72
72
  const data = await response.json();
73
73
  if (response.ok) {
@@ -82,11 +82,11 @@ export function SpecDetail({
82
82
  }
83
83
 
84
84
  const activeStatus = statuses.find((s) => s.id === activeTab);
85
- const isDevelopmentTab = activeTab === "development";
85
+ const isDevelopmentTab = activeTab === 'development';
86
86
 
87
87
  const total = tasks.length;
88
- const done = tasks.filter((t) => t.status === "done").length;
89
- const inProgress = tasks.filter((t) => t.status === "in_progress").length;
88
+ const done = tasks.filter((t) => t.status === 'done').length;
89
+ const inProgress = tasks.filter((t) => t.status === 'in_progress').length;
90
90
 
91
91
  return (
92
92
  <div className="flex flex-col gap-6">
@@ -106,15 +106,17 @@ export function SpecDetail({
106
106
  disabled={isPromoting}
107
107
  className="bg-green-600 hover:bg-green-700 text-white"
108
108
  >
109
- {isPromoting ? "Promoting..." : "Promote to Tasks"}
109
+ {isPromoting ? 'Promoting...' : 'Promote to Tasks'}
110
110
  </Button>
111
111
  </div>
112
112
  </div>
113
113
  )}
114
114
 
115
- {developmentStatus?.state === "done" && (
115
+ {developmentStatus?.state === 'done' && (
116
116
  <div className="p-4 bg-green-100 border-4 border-green-600">
117
- <p className="font-black text-green-800 uppercase">✓ Spec complete — all {total} tasks done</p>
117
+ <p className="font-black text-green-800 uppercase">
118
+ ✓ Spec complete — all {total} tasks done
119
+ </p>
118
120
  </div>
119
121
  )}
120
122
 
@@ -125,7 +127,7 @@ export function SpecDetail({
125
127
  key={s.id}
126
128
  onClick={() => setActiveTab(s.id)}
127
129
  className={`px-4 py-2 font-bold uppercase text-sm border-r-4 border-black last:border-r-0 ${
128
- activeTab === s.id ? "bg-black text-white" : "bg-white hover:bg-gray-100"
130
+ activeTab === s.id ? 'bg-black text-white' : 'bg-white hover:bg-gray-100'
129
131
  }`}
130
132
  >
131
133
  {s.id}
@@ -171,7 +173,7 @@ function DevelopmentPanel({
171
173
  done: number;
172
174
  inProgress: number;
173
175
  }) {
174
- if (status?.state === "blocked") {
176
+ if (status?.state === 'blocked') {
175
177
  return (
176
178
  <div className="p-6 bg-gray-100 border-4 border-gray-300 text-center">
177
179
  <p className="font-bold text-gray-600 uppercase">
@@ -207,33 +209,39 @@ function DevelopmentPanel({
207
209
  </div>
208
210
  <span className="font-mono text-sm font-bold whitespace-nowrap">
209
211
  {done}/{total} done
210
- {inProgress > 0 && <span className="text-orange-600 ml-2">· {inProgress} in progress</span>}
212
+ {inProgress > 0 && (
213
+ <span className="text-orange-600 ml-2">· {inProgress} in progress</span>
214
+ )}
211
215
  </span>
212
216
  </div>
213
217
 
214
218
  {/* Task list */}
215
219
  <div className="flex flex-col gap-2">
216
220
  {tasks.map((task) => (
217
- <div
218
- key={task.id}
219
- className="flex items-center gap-3 p-3 border-4 border-black bg-white"
220
- >
221
- <span className={`text-lg ${task.status === "done" ? "opacity-100" : "opacity-30"}`}>
222
- {task.status === "done" ? "✓" : task.status === "in_progress" ? "⟳" : "○"}
221
+ <div key={task.id} className="flex items-center gap-3 p-3 border-4 border-black bg-white">
222
+ <span className={`text-lg ${task.status === 'done' ? 'opacity-100' : 'opacity-30'}`}>
223
+ {task.status === 'done' ? '✓' : task.status === 'in_progress' ? '⟳' : '○'}
223
224
  </span>
224
- <span className={`flex-1 font-bold text-sm ${task.status === "done" ? "line-through text-gray-400" : ""}`}>
225
+ <span
226
+ className={`flex-1 font-bold text-sm ${task.status === 'done' ? 'line-through text-gray-400' : ''}`}
227
+ >
225
228
  {task.title}
226
229
  </span>
227
- <span className={`text-xs font-mono uppercase font-bold ${PRIORITY_COLORS[task.priority] || ""}`}>
230
+ <span
231
+ className={`text-xs font-mono uppercase font-bold ${PRIORITY_COLORS[task.priority] || ''}`}
232
+ >
228
233
  {task.priority}
229
234
  </span>
230
235
  <span
231
- className={`px-2 py-0.5 border-2 text-xs font-bold uppercase ${TASK_STATUS_COLORS[task.status] || ""}`}
236
+ className={`px-2 py-0.5 border-2 text-xs font-bold uppercase ${TASK_STATUS_COLORS[task.status] || ''}`}
232
237
  >
233
- {task.status.replace("_", " ")}
238
+ {task.status.replace('_', ' ')}
234
239
  </span>
235
240
  {task.assignedAgent && (
236
- <span className="text-xs font-mono text-gray-500 truncate max-w-[120px]" title={task.assignedAgent}>
241
+ <span
242
+ className="text-xs font-mono text-gray-500 truncate max-w-[120px]"
243
+ title={task.assignedAgent}
244
+ >
237
245
  @{task.assignedAgent}
238
246
  </span>
239
247
  )}
@@ -1,11 +1,11 @@
1
- "use client";
1
+ 'use client';
2
2
 
3
- import { useEffect, useState } from "react";
4
- import { CheckCircle2, Circle, Clock, Lock } from "lucide-react";
3
+ import { CheckCircle2, Circle, Clock, Lock } from 'lucide-react';
4
+ import { useSpecs } from '@/hooks/use-queries';
5
5
 
6
6
  interface ArtifactStatus {
7
7
  id: string;
8
- state: "blocked" | "ready" | "in_review" | "in_progress" | "done";
8
+ state: 'blocked' | 'ready' | 'in_review' | 'in_progress' | 'done';
9
9
  }
10
10
 
11
11
  interface SpecMeta {
@@ -21,36 +21,38 @@ interface SpecKanbanColumnProps {
21
21
  }
22
22
 
23
23
  const DOT_COLORS: Record<string, string> = {
24
- blocked: "bg-gray-300",
25
- ready: "bg-blue-400",
26
- in_review: "bg-yellow-400",
27
- in_progress: "bg-orange-400",
28
- done: "bg-green-500",
24
+ blocked: 'bg-gray-300',
25
+ ready: 'bg-blue-400',
26
+ in_review: 'bg-yellow-400',
27
+ in_progress: 'bg-orange-400',
28
+ done: 'bg-green-500',
29
29
  };
30
30
 
31
- function overallState(statuses: ArtifactStatus[]): "done" | "in_progress" | "in_review" | "ready" | "blocked" {
32
- const all = statuses.filter((s) => s.id !== "development");
33
- if (all.length === 0) return "ready";
34
- if (all.every((s) => s.state === "done")) return "done";
35
- if (all.some((s) => s.state === "in_review" || s.state === "in_progress")) return "in_review";
36
- if (all.some((s) => s.state === "ready")) return "ready";
37
- return "blocked";
31
+ function overallState(
32
+ statuses: ArtifactStatus[],
33
+ ): 'done' | 'in_progress' | 'in_review' | 'ready' | 'blocked' {
34
+ const all = statuses.filter((s) => s.id !== 'development');
35
+ if (all.length === 0) return 'ready';
36
+ if (all.every((s) => s.state === 'done')) return 'done';
37
+ if (all.some((s) => s.state === 'in_review' || s.state === 'in_progress')) return 'in_review';
38
+ if (all.some((s) => s.state === 'ready')) return 'ready';
39
+ return 'blocked';
38
40
  }
39
41
 
40
42
  const OVERALL_ICON: Record<string, React.ReactNode> = {
41
- done: <CheckCircle2 className="h-3 w-3 text-green-600" />,
42
- in_review: <Clock className="h-3 w-3 text-yellow-500" />,
43
- in_progress:<Clock className="h-3 w-3 text-orange-500" />,
44
- ready: <Circle className="h-3 w-3 text-blue-500" />,
45
- blocked: <Lock className="h-3 w-3 text-gray-400" />,
43
+ done: <CheckCircle2 className="h-3 w-3 text-green-600" />,
44
+ in_review: <Clock className="h-3 w-3 text-yellow-500" />,
45
+ in_progress: <Clock className="h-3 w-3 text-orange-500" />,
46
+ ready: <Circle className="h-3 w-3 text-blue-500" />,
47
+ blocked: <Lock className="h-3 w-3 text-gray-400" />,
46
48
  };
47
49
 
48
50
  const OVERALL_LABEL: Record<string, string> = {
49
- done: "done",
50
- in_review: "in review",
51
- in_progress: "in progress",
52
- ready: "ready",
53
- blocked: "blocked",
51
+ done: 'done',
52
+ in_review: 'in review',
53
+ in_progress: 'in progress',
54
+ ready: 'ready',
55
+ blocked: 'blocked',
54
56
  };
55
57
 
56
58
  function SpecKanbanCard({
@@ -62,26 +64,30 @@ function SpecKanbanCard({
62
64
  selected: boolean;
63
65
  onClick: () => void;
64
66
  }) {
65
- const state = spec.statuses ? overallState(spec.statuses) : "ready";
66
- const artifactStatuses = spec.statuses?.filter((s) => s.id !== "development") ?? [];
67
+ const state = spec.statuses ? overallState(spec.statuses) : 'ready';
68
+ const artifactStatuses = spec.statuses?.filter((s) => s.id !== 'development') ?? [];
67
69
 
68
70
  return (
69
71
  <button
70
72
  onClick={onClick}
71
73
  className={`w-full text-left p-3 border-4 transition-all ${
72
74
  selected
73
- ? "border-black bg-black text-white shadow-none translate-x-[2px] translate-y-[2px]"
74
- : "border-black bg-white shadow-[4px_4px_0px_0px_rgba(0,0,0,1)] hover:translate-x-[2px] hover:translate-y-[2px] hover:shadow-none"
75
+ ? 'border-black bg-black text-white shadow-none translate-x-[2px] translate-y-[2px]'
76
+ : 'border-black bg-white shadow-[4px_4px_0px_0px_rgba(0,0,0,1)] hover:translate-x-[2px] hover:translate-y-[2px] hover:shadow-none'
75
77
  }`}
76
78
  >
77
79
  <div className="flex items-start justify-between gap-1 mb-2">
78
- <span className={`font-black text-xs uppercase leading-tight line-clamp-2 flex-1 ${selected ? "text-white" : ""}`}>
80
+ <span
81
+ className={`font-black text-xs uppercase leading-tight line-clamp-2 flex-1 ${selected ? 'text-white' : ''}`}
82
+ >
79
83
  {spec.title || spec.name}
80
84
  </span>
81
85
  <span className="flex-shrink-0 mt-0.5">{OVERALL_ICON[state]}</span>
82
86
  </div>
83
87
 
84
- <p className={`font-mono text-[10px] mb-2 truncate ${selected ? "text-gray-300" : "text-gray-400"}`}>
88
+ <p
89
+ className={`font-mono text-[10px] mb-2 truncate ${selected ? 'text-gray-300' : 'text-gray-400'}`}
90
+ >
85
91
  {spec.name}
86
92
  </p>
87
93
 
@@ -92,10 +98,12 @@ function SpecKanbanCard({
92
98
  <div
93
99
  key={s.id}
94
100
  title={`${s.id}: ${s.state}`}
95
- className={`w-2 h-2 rounded-full border border-black ${selected ? "border-gray-400" : ""} ${DOT_COLORS[s.state]}`}
101
+ className={`w-2 h-2 rounded-full border border-black ${selected ? 'border-gray-400' : ''} ${DOT_COLORS[s.state]}`}
96
102
  />
97
103
  ))}
98
- <span className={`text-[10px] font-bold uppercase ml-1 ${selected ? "text-gray-300" : "text-gray-400"}`}>
104
+ <span
105
+ className={`text-[10px] font-bold uppercase ml-1 ${selected ? 'text-gray-300' : 'text-gray-400'}`}
106
+ >
99
107
  {OVERALL_LABEL[state]}
100
108
  </span>
101
109
  </div>
@@ -105,38 +113,13 @@ function SpecKanbanCard({
105
113
  }
106
114
 
107
115
  export function SpecKanbanColumn({ selectedSpecName, onSelectSpec }: SpecKanbanColumnProps) {
108
- const [specs, setSpecs] = useState<SpecMeta[]>([]);
109
- const [loading, setLoading] = useState(true);
110
-
111
- useEffect(() => {
112
- async function load() {
113
- try {
114
- const res = await fetch("/api/specs");
115
- const data: SpecMeta[] = await res.json();
116
- // Fetch statuses for each spec in parallel
117
- const withStatuses = await Promise.all(
118
- data.map(async (spec) => {
119
- try {
120
- const r = await fetch(`/api/specs/${spec.name}`);
121
- const detail = await r.json();
122
- return { ...spec, statuses: detail.statuses };
123
- } catch {
124
- return spec;
125
- }
126
- })
127
- );
128
- setSpecs(withStatuses);
129
- } catch (e) {
130
- console.error(e);
131
- } finally {
132
- setLoading(false);
133
- }
134
- }
135
- load();
136
- }, []);
116
+ const { data: specs = [], isLoading: loading } = useSpecs();
137
117
 
138
118
  return (
139
- <div className="flex flex-col h-full min-h-0 border-r-4 border-black bg-[--color-bg]" style={{ width: 220, flexShrink: 0 }}>
119
+ <div
120
+ className="flex flex-col h-full min-h-0 border-r-4 border-black bg-[--color-bg]"
121
+ style={{ width: 220, flexShrink: 0 }}
122
+ >
140
123
  {/* Column header */}
141
124
  <div className="flex items-center justify-between px-4 py-3 border-b-4 border-black bg-[--color-secondary] flex-shrink-0">
142
125
  <h2 className="font-black uppercase text-sm tracking-wide">Specs</h2>
@@ -145,13 +128,11 @@ export function SpecKanbanColumn({ selectedSpecName, onSelectSpec }: SpecKanbanC
145
128
 
146
129
  {/* Cards */}
147
130
  <div className="flex-1 overflow-y-auto p-3 flex flex-col gap-2">
148
- {loading && (
149
- <p className="text-xs text-gray-400 font-mono px-1">Loading...</p>
150
- )}
131
+ {loading && <p className="text-xs text-gray-400 font-mono px-1">Loading...</p>}
151
132
  {!loading && specs.length === 0 && (
152
133
  <p className="text-xs text-gray-400 font-mono px-1">No specs yet.</p>
153
134
  )}
154
- {specs.map((spec) => (
135
+ {(specs as SpecMeta[]).map((spec) => (
155
136
  <SpecKanbanCard
156
137
  key={spec.name}
157
138
  spec={spec}