@atlashub/smartstack-cli 4.80.0 → 5.0.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 (1008) hide show
  1. package/.documentation/agents.html +124 -585
  2. package/.documentation/ba-develop.html +852 -0
  3. package/.documentation/ba-skills.html +465 -0
  4. package/.documentation/business-analyse.html +385 -1570
  5. package/.documentation/cli-commands.html +162 -799
  6. package/.documentation/commands.html +902 -1338
  7. package/.documentation/css/styles.css +34 -1
  8. package/.documentation/efcore.html +161 -2599
  9. package/.documentation/gitflow.html +62 -105
  10. package/.documentation/hooks.html +94 -343
  11. package/.documentation/index.html +116 -385
  12. package/.documentation/init.html +217 -1566
  13. package/.documentation/installation.html +121 -1470
  14. package/.documentation/license.html +90 -450
  15. package/.documentation/ralph-loop.html +105 -602
  16. package/dist/index.js +9421 -79036
  17. package/dist/index.js.map +1 -1
  18. package/package.json +5 -20
  19. package/scripts/generate-docs/README.md +87 -0
  20. package/scripts/generate-docs/index.ts +175 -0
  21. package/scripts/generate-docs/lib/context-builder.ts +81 -0
  22. package/scripts/generate-docs/lib/handlebars-setup.ts +162 -0
  23. package/scripts/generate-docs/lib/markdown-parser.ts +86 -0
  24. package/scripts/generate-docs/lib/sidebar-builder.ts +80 -0
  25. package/scripts/generate-docs/lib/skill-parser.ts +171 -0
  26. package/scripts/generate-docs/lib/stats.ts +32 -0
  27. package/scripts/generate-docs/lib/version.ts +17 -0
  28. package/scripts/generate-docs/templates/layout.hbs +33 -0
  29. package/scripts/generate-docs/templates/pages/_generic.hbs +12 -0
  30. package/scripts/generate-docs/templates/pages/ba-develop.hbs +10 -0
  31. package/scripts/generate-docs/templates/pages/ba-skills.hbs +8 -0
  32. package/scripts/generate-docs/templates/pages/business-analyse.hbs +1 -0
  33. package/scripts/generate-docs/templates/pages/commands.hbs +13 -0
  34. package/scripts/generate-docs/templates/pages/gitflow.hbs +2164 -0
  35. package/scripts/generate-docs/templates/pages/index.hbs +5 -0
  36. package/scripts/generate-docs/templates/partials/breadcrumb.hbs +6 -0
  37. package/scripts/generate-docs/templates/partials/header.hbs +22 -0
  38. package/scripts/generate-docs/templates/partials/sidebar.hbs +32 -0
  39. package/scripts/generate-docs/templates/partials/skill-card.hbs +22 -0
  40. package/scripts/generate-docs/templates/partials/skill-grid.hbs +5 -0
  41. package/scripts/generate-docs/templates/partials/skill-table.hbs +18 -0
  42. package/scripts/generate-docs/templates/partials/stats-bar.hbs +20 -0
  43. package/scripts/test-migration-program-cs.mts +94 -0
  44. package/templates/agents/explore-codebase.md +2 -3
  45. package/templates/agents/explore-docs.md +5 -5
  46. package/templates/hooks/hooks.json +0 -9
  47. package/templates/project/Program.cs.template +17 -5
  48. package/templates/project/appsettings.json.template +208 -195
  49. package/templates/project/claude-md/api.CLAUDE.md.template +27 -2
  50. package/templates/project/patch-smartstack-theme.cjs.template +42 -0
  51. package/templates/scripts/statusline/README.md +47 -0
  52. package/templates/scripts/statusline/index.js +224 -0
  53. package/templates/skills/CLAUDE.md +235 -0
  54. package/templates/skills/ba-develop/SKILL.md +310 -0
  55. package/templates/skills/ba-develop/cli/compute-page-diff/__tests__/compute-page-diff.test.ts +177 -0
  56. package/templates/skills/ba-develop/cli/compute-page-diff/compute-diff.ts +51 -0
  57. package/templates/skills/ba-develop/cli/compute-page-diff/disk-drift.ts +55 -0
  58. package/templates/skills/ba-develop/cli/compute-page-diff/index.ts +89 -0
  59. package/templates/skills/ba-develop/cli/compute-page-diff/scan-pagespecs.ts +115 -0
  60. package/templates/skills/ba-develop/cli/compute-page-diff/types.ts +63 -0
  61. package/templates/skills/ba-develop/cli/compute-page-diff/validate.ts +20 -0
  62. package/templates/skills/ba-develop/cli/update-snapshot/__tests__/update-snapshot.test.ts +73 -0
  63. package/templates/skills/ba-develop/cli/update-snapshot/execute.ts +24 -0
  64. package/templates/skills/ba-develop/cli/update-snapshot/index.ts +61 -0
  65. package/templates/skills/ba-develop/cli/update-snapshot/types.ts +40 -0
  66. package/templates/skills/ba-develop/cli/update-snapshot/validate.ts +17 -0
  67. package/templates/skills/ba-develop/references/anti-patterns.md +101 -0
  68. package/templates/skills/ba-develop/references/auto-healing.md +191 -0
  69. package/templates/skills/ba-develop/references/commit-checkpoints.md +79 -0
  70. package/templates/skills/ba-develop/references/gates.md +380 -0
  71. package/templates/skills/ba-develop/references/output-contract.md +95 -0
  72. package/templates/skills/ba-develop/references/phases-detail.md +592 -0
  73. package/templates/skills/ba-develop-plan/SKILL.md +239 -0
  74. package/templates/skills/ba-develop-plan/cli/preflight-develop-plan/__tests__/validate.test.ts +225 -0
  75. package/templates/skills/ba-develop-plan/cli/preflight-develop-plan/index.ts +102 -0
  76. package/templates/skills/ba-develop-plan/cli/preflight-develop-plan/types.ts +121 -0
  77. package/templates/skills/ba-develop-plan/cli/preflight-develop-plan/validate.ts +261 -0
  78. package/templates/skills/business-analyse/_workflow/README.md +34 -0
  79. package/templates/skills/business-analyse/_workflow/ba-files.md +174 -0
  80. package/templates/skills/business-analyse/_workflow/code-discipline.md +104 -0
  81. package/templates/skills/business-analyse/_workflow/communication.md +63 -0
  82. package/templates/skills/business-analyse/_workflow/completeAuto-discipline.md +79 -0
  83. package/templates/skills/business-analyse/_workflow/context-documents.md +45 -0
  84. package/templates/skills/business-analyse/_workflow/doc-templates.md +318 -0
  85. package/templates/skills/business-analyse/audit-actors/SKILL.md +97 -0
  86. package/templates/skills/business-analyse/audit-cross-dimension/SKILL.md +127 -0
  87. package/templates/skills/business-analyse/audit-cross-ref-code/SKILL.md +119 -0
  88. package/templates/skills/business-analyse/audit-data-model/SKILL.md +343 -0
  89. package/templates/skills/business-analyse/audit-menu/SKILL.md +97 -0
  90. package/templates/skills/business-analyse/audit-prd/SKILL.md +479 -0
  91. package/templates/skills/business-analyse/audit-pre-dev/SKILL.md +135 -0
  92. package/templates/skills/business-analyse/audit-rbac/SKILL.md +93 -0
  93. package/templates/skills/business-analyse/audit-rules/SKILL.md +182 -0
  94. package/templates/skills/business-analyse/audit-screens/SKILL.md +169 -0
  95. package/templates/skills/business-analyse/audit-sections/SKILL.md +174 -0
  96. package/templates/skills/business-analyse/audit-use-cases/SKILL.md +245 -0
  97. package/templates/skills/business-analyse/create-actors/SKILL.md +129 -0
  98. package/templates/skills/business-analyse/create-ba-order/SKILL.md +182 -0
  99. package/templates/skills/business-analyse/create-ba-order/cli/create-ba-order/__tests__/generate.test.ts +151 -0
  100. package/templates/skills/business-analyse/create-ba-order/cli/create-ba-order/__tests__/graph.test.ts +173 -0
  101. package/templates/skills/business-analyse/create-ba-order/cli/create-ba-order/generate.ts +273 -0
  102. package/templates/skills/business-analyse/create-ba-order/cli/create-ba-order/graph.ts +193 -0
  103. package/templates/skills/business-analyse/create-ba-order/cli/create-ba-order/index.ts +108 -0
  104. package/templates/skills/business-analyse/create-ba-order/cli/create-ba-order/types.ts +106 -0
  105. package/templates/skills/business-analyse/create-ba-order/cli/create-ba-order/validate.ts +79 -0
  106. package/templates/skills/business-analyse/create-business-rules/SKILL.md +302 -0
  107. package/templates/skills/business-analyse/create-business-rules/levels/access-rules.md +105 -0
  108. package/templates/skills/business-analyse/create-business-rules/levels/elaborate.md +193 -0
  109. package/templates/skills/business-analyse/create-business-rules/levels/identify.md +157 -0
  110. package/templates/skills/business-analyse/create-business-rules/levels/link.md +86 -0
  111. package/templates/skills/business-analyse/create-data-model/SKILL.md +319 -0
  112. package/templates/skills/business-analyse/create-data-model/levels/attributes.md +130 -0
  113. package/templates/skills/business-analyse/create-data-model/levels/identify.md +100 -0
  114. package/templates/skills/business-analyse/create-data-model/levels/relationships.md +97 -0
  115. package/templates/skills/business-analyse/create-menu/SKILL.md +191 -0
  116. package/templates/skills/business-analyse/create-menu/levels/applications.md +85 -0
  117. package/templates/skills/business-analyse/create-menu/levels/modules.md +81 -0
  118. package/templates/skills/business-analyse/create-menu/levels/resources.md +75 -0
  119. package/templates/skills/business-analyse/create-menu/levels/sections.md +82 -0
  120. package/templates/skills/business-analyse/create-plan-development/SKILL.md +93 -0
  121. package/templates/skills/business-analyse/create-plan-development/cli/create-plan-development/__tests__/graph.test.ts +271 -0
  122. package/templates/skills/business-analyse/create-plan-development/cli/create-plan-development/__tests__/parse.test.ts +177 -0
  123. package/templates/skills/business-analyse/create-plan-development/cli/create-plan-development/generate.ts +317 -0
  124. package/templates/skills/business-analyse/create-plan-development/cli/create-plan-development/graph.ts +233 -0
  125. package/templates/skills/business-analyse/create-plan-development/cli/create-plan-development/index.ts +106 -0
  126. package/templates/skills/business-analyse/create-plan-development/cli/create-plan-development/parse.ts +346 -0
  127. package/templates/skills/business-analyse/create-plan-development/cli/create-plan-development/types.ts +160 -0
  128. package/templates/skills/business-analyse/create-plan-development/cli/create-plan-development/validate.ts +118 -0
  129. package/templates/skills/business-analyse/create-prd/SKILL.md +228 -0
  130. package/templates/skills/business-analyse/create-rbac/SKILL.md +255 -0
  131. package/templates/skills/business-analyse/create-rbac/levels/detail.md +86 -0
  132. package/templates/skills/business-analyse/create-rbac/levels/discovery.md +63 -0
  133. package/templates/skills/business-analyse/create-rbac/levels/review.md +66 -0
  134. package/templates/skills/business-analyse/create-screen/SKILL.md +386 -0
  135. package/templates/skills/business-analyse/create-screen/levels/dashboard-screens.md +94 -0
  136. package/templates/skills/business-analyse/create-screen/levels/form-screens.md +142 -0
  137. package/templates/skills/business-analyse/create-screen/levels/home-screens.md +151 -0
  138. package/templates/skills/business-analyse/create-screen/levels/kanban-screens.md +86 -0
  139. package/templates/skills/business-analyse/create-screen/levels/list-screens.md +134 -0
  140. package/templates/skills/business-analyse/create-screen/references/post-check.md +101 -0
  141. package/templates/skills/business-analyse/create-screen/references/react-templates.md +252 -0
  142. package/templates/skills/business-analyse/create-screen/references/smartcomponents.md +419 -0
  143. package/templates/skills/business-analyse/create-screen/references/type-mapping.md +150 -0
  144. package/templates/skills/business-analyse/create-use-case/SKILL.md +347 -0
  145. package/templates/skills/business-analyse/create-use-case/levels/detail.md +136 -0
  146. package/templates/skills/business-analyse/create-use-case/levels/discovery.md +110 -0
  147. package/templates/skills/business-analyse/loop/SKILL.md +401 -0
  148. package/templates/skills/business-analyse/modeling-detail/SKILL.md +241 -0
  149. package/templates/skills/business-analyse/modeling-inventory/SKILL.md +174 -0
  150. package/templates/skills/business-analyse/reconcile-menu/SKILL.md +180 -0
  151. package/templates/skills/business-analyse/reconcile-menu/cli/reconcile-menu/__tests__/clean.test.ts +266 -0
  152. package/templates/skills/business-analyse/reconcile-menu/cli/reconcile-menu/__tests__/detect.test.ts +231 -0
  153. package/templates/skills/business-analyse/reconcile-menu/cli/reconcile-menu/__tests__/scan.test.ts +154 -0
  154. package/templates/skills/business-analyse/reconcile-menu/cli/reconcile-menu/clean.ts +319 -0
  155. package/templates/skills/business-analyse/reconcile-menu/cli/reconcile-menu/detect.ts +256 -0
  156. package/templates/skills/business-analyse/reconcile-menu/cli/reconcile-menu/index.ts +126 -0
  157. package/templates/skills/business-analyse/reconcile-menu/cli/reconcile-menu/scan.ts +175 -0
  158. package/templates/skills/business-analyse/reconcile-menu/cli/reconcile-menu/types.ts +136 -0
  159. package/templates/skills/business-analyse/reconcile-menu/cli/reconcile-menu/validate.ts +32 -0
  160. package/templates/skills/check-version/SKILL.md +196 -196
  161. package/templates/skills/cli-app-sync/SKILL.md +9 -9
  162. package/templates/skills/cli-app-sync/references/comparison-map.md +4 -4
  163. package/templates/skills/cli-app-sync/references/diff-entities.md +6 -6
  164. package/templates/skills/conventions/SKILL.md +64 -0
  165. package/templates/skills/dev-start/SKILL.md +190 -237
  166. package/templates/skills/development/SKILL.md +87 -0
  167. package/templates/skills/development/audit/SKILL.md +156 -0
  168. package/templates/skills/development/audit/routing-dynamic/SKILL.md +196 -0
  169. package/templates/skills/development/audit-dev-api/SKILL.md +331 -0
  170. package/templates/skills/development/audit-dev-api/cli/audit-dev-api/__tests__/end-to-end.test.ts +364 -0
  171. package/templates/skills/development/audit-dev-api/cli/audit-dev-api/audit.ts +646 -0
  172. package/templates/skills/development/audit-dev-api/cli/audit-dev-api/index.ts +140 -0
  173. package/templates/skills/development/audit-dev-api/cli/audit-dev-api/types.ts +158 -0
  174. package/templates/skills/development/audit-dev-api/cli/audit-dev-api/validate.ts +45 -0
  175. package/templates/skills/development/audit-dev-core/SKILL.md +182 -0
  176. package/templates/skills/development/audit-dev-data/SKILL.md +195 -0
  177. package/templates/skills/development/audit-dev-domain/SKILL.md +184 -0
  178. package/templates/skills/development/audit-dev-frontend/SKILL.md +530 -0
  179. package/templates/skills/development/audit-dev-frontend/cli/audit-dev-actions-alignment/__tests__/end-to-end.test.ts +202 -0
  180. package/templates/skills/development/audit-dev-frontend/cli/audit-dev-actions-alignment/apply.ts +31 -0
  181. package/templates/skills/development/audit-dev-frontend/cli/audit-dev-actions-alignment/audit.ts +734 -0
  182. package/templates/skills/development/audit-dev-frontend/cli/audit-dev-actions-alignment/index.ts +125 -0
  183. package/templates/skills/development/audit-dev-frontend/cli/audit-dev-actions-alignment/types.ts +165 -0
  184. package/templates/skills/development/audit-dev-frontend/cli/audit-dev-actions-alignment/validate.ts +36 -0
  185. package/templates/skills/development/audit-dev-frontend/cli/audit-dev-frontend/__tests__/dev-ui-022.test.ts +193 -0
  186. package/templates/skills/development/audit-dev-frontend/cli/audit-dev-frontend/apply.ts +374 -0
  187. package/templates/skills/development/audit-dev-frontend/cli/audit-dev-frontend/audit.ts +1126 -0
  188. package/templates/skills/development/audit-dev-frontend/cli/audit-dev-frontend/index.ts +141 -0
  189. package/templates/skills/development/audit-dev-frontend/cli/audit-dev-frontend/types.ts +218 -0
  190. package/templates/skills/development/audit-dev-frontend/cli/audit-dev-frontend/validate.ts +80 -0
  191. package/templates/skills/development/audit-dev-tests/SKILL.md +82 -0
  192. package/templates/skills/development/audit-dev-tests/cli/audit-dev-tests/__tests__/audit.test.ts +220 -0
  193. package/templates/skills/development/audit-dev-tests/cli/audit-dev-tests/audit.ts +185 -0
  194. package/templates/skills/development/audit-dev-tests/cli/audit-dev-tests/index.ts +84 -0
  195. package/templates/skills/development/audit-dev-tests/cli/audit-dev-tests/types.ts +48 -0
  196. package/templates/skills/development/audit-dev-tests/cli/audit-dev-tests/validate.ts +36 -0
  197. package/templates/skills/development/audit-dev-wire/SKILL.md +144 -0
  198. package/templates/skills/development/audit-dev-wire/cli/audit-dev-wire/__tests__/audit.test.ts +171 -0
  199. package/templates/skills/development/audit-dev-wire/cli/audit-dev-wire/audit.ts +307 -0
  200. package/templates/skills/development/audit-dev-wire/cli/audit-dev-wire/index.ts +139 -0
  201. package/templates/skills/development/audit-dev-wire/cli/audit-dev-wire/types.ts +110 -0
  202. package/templates/skills/development/audit-dev-wire/cli/audit-dev-wire/validate.ts +16 -0
  203. package/templates/skills/development/backend/business-layer/SKILL.md +255 -0
  204. package/templates/skills/development/backend/business-layer/cli/scaffold-business/__tests__/generate.test.ts +254 -0
  205. package/templates/skills/development/backend/business-layer/cli/scaffold-business/generate.ts +842 -0
  206. package/templates/skills/development/backend/business-layer/cli/scaffold-business/index.ts +56 -0
  207. package/templates/skills/development/backend/business-layer/cli/scaffold-business/types.ts +112 -0
  208. package/templates/skills/development/backend/business-layer/cli/scaffold-business/validate.ts +24 -0
  209. package/templates/skills/development/backend/controller/SKILL.md +154 -0
  210. package/templates/skills/development/backend/controller/cli/scaffold-controller/__tests__/generate.test.ts +345 -0
  211. package/templates/skills/development/backend/controller/cli/scaffold-controller/generate.ts +280 -0
  212. package/templates/skills/development/backend/controller/cli/scaffold-controller/index.ts +49 -0
  213. package/templates/skills/development/backend/controller/cli/scaffold-controller/types.ts +72 -0
  214. package/templates/skills/development/backend/controller/cli/scaffold-controller/validate.ts +14 -0
  215. package/templates/skills/development/backend/core-seed/SKILL.md +177 -0
  216. package/templates/skills/development/backend/core-seed/cli/scaffold-core-seed/__tests__/build-spec.test.ts +163 -0
  217. package/templates/skills/development/backend/core-seed/cli/scaffold-core-seed/__tests__/generate.test.ts +330 -0
  218. package/templates/skills/development/backend/core-seed/cli/scaffold-core-seed/__tests__/validate.test.ts +126 -0
  219. package/templates/skills/development/backend/core-seed/cli/scaffold-core-seed/build-spec.ts +287 -0
  220. package/templates/skills/development/backend/core-seed/cli/scaffold-core-seed/generate.ts +826 -0
  221. package/templates/skills/development/backend/core-seed/cli/scaffold-core-seed/index.ts +188 -0
  222. package/templates/skills/development/backend/core-seed/cli/scaffold-core-seed/types.ts +163 -0
  223. package/templates/skills/development/backend/core-seed/cli/scaffold-core-seed/validate.ts +129 -0
  224. package/templates/skills/development/backend/data-layer/SKILL.md +163 -0
  225. package/templates/skills/development/backend/data-layer/cli/scaffold-entity/__tests__/generate.test.ts +155 -0
  226. package/templates/skills/development/backend/data-layer/cli/scaffold-entity/generate.ts +232 -0
  227. package/templates/skills/development/backend/data-layer/cli/scaffold-entity/index.ts +34 -0
  228. package/templates/skills/development/backend/data-layer/cli/scaffold-entity/types.ts +60 -0
  229. package/templates/skills/development/backend/data-layer/cli/scaffold-entity/validate.ts +42 -0
  230. package/templates/skills/development/backend/data-layer/cli/scaffold-migration/execute.ts +13 -0
  231. package/templates/skills/development/backend/data-layer/cli/scaffold-migration/index.ts +25 -0
  232. package/templates/skills/development/backend/data-layer/cli/scaffold-migration/types.ts +11 -0
  233. package/templates/skills/development/backend/data-layer/cli/scaffold-migration/validate.ts +9 -0
  234. package/templates/skills/development/backend/screen-controller/SKILL.md +169 -0
  235. package/templates/skills/development/backend/screen-controller/cli/scaffold-screen-controller/__tests__/generate.test.ts +329 -0
  236. package/templates/skills/development/backend/screen-controller/cli/scaffold-screen-controller/__tests__/hub-views.test.ts +105 -0
  237. package/templates/skills/development/backend/screen-controller/cli/scaffold-screen-controller/__tests__/parse-pagespec.test.ts +137 -0
  238. package/templates/skills/development/backend/screen-controller/cli/scaffold-screen-controller/generate.ts +437 -0
  239. package/templates/skills/development/backend/screen-controller/cli/scaffold-screen-controller/index.ts +108 -0
  240. package/templates/skills/development/backend/screen-controller/cli/scaffold-screen-controller/parse-pagespec.ts +104 -0
  241. package/templates/skills/development/backend/screen-controller/cli/scaffold-screen-controller/types.ts +101 -0
  242. package/templates/skills/development/backend/screen-controller/cli/scaffold-screen-controller/validate.ts +26 -0
  243. package/templates/skills/development/backend/seed-data/SKILL.md +91 -0
  244. package/templates/skills/development/backend/seed-data/cli/scaffold-seed/generate.ts +471 -0
  245. package/templates/skills/development/backend/seed-data/cli/scaffold-seed/index.ts +74 -0
  246. package/templates/skills/development/backend/seed-data/cli/scaffold-seed/types.ts +104 -0
  247. package/templates/skills/development/backend/seed-data/cli/scaffold-seed/validate.ts +63 -0
  248. package/templates/skills/development/backend/structure/SKILL.md +47 -0
  249. package/templates/skills/development/debug/SKILL.md +62 -0
  250. package/templates/skills/development/debug/audit-bug/SKILL.md +185 -0
  251. package/templates/skills/development/debug/backend/SKILL.md +114 -0
  252. package/templates/skills/development/debug/discuss-bug/SKILL.md +193 -0
  253. package/templates/skills/development/debug/fix-bug/SKILL.md +172 -0
  254. package/templates/skills/development/debug/frontend/SKILL.md +215 -0
  255. package/templates/skills/development/frontend/api-client/SKILL.md +158 -0
  256. package/templates/skills/development/frontend/api-client/cli/scaffold-api-client/__tests__/generate.test.ts +1180 -0
  257. package/templates/skills/development/frontend/api-client/cli/scaffold-api-client/__tests__/screen-strata-contract.test.ts +261 -0
  258. package/templates/skills/development/frontend/api-client/cli/scaffold-api-client/__tests__/url-parity.test.ts +201 -0
  259. package/templates/skills/development/frontend/api-client/cli/scaffold-api-client/generate.ts +875 -0
  260. package/templates/skills/development/frontend/api-client/cli/scaffold-api-client/index.ts +36 -0
  261. package/templates/skills/development/frontend/api-client/cli/scaffold-api-client/types.ts +251 -0
  262. package/templates/skills/development/frontend/api-client/cli/scaffold-api-client/validate.ts +10 -0
  263. package/templates/skills/development/frontend/auth/SKILL.md +77 -0
  264. package/templates/skills/development/frontend/auth/cli/scaffold-frontend-auth/generate.ts +306 -0
  265. package/templates/skills/development/frontend/auth/cli/scaffold-frontend-auth/index.ts +179 -0
  266. package/templates/skills/development/frontend/auth/cli/scaffold-frontend-auth/types.ts +22 -0
  267. package/templates/skills/development/frontend/auth/cli/scaffold-frontend-auth/validate.ts +37 -0
  268. package/templates/skills/development/frontend/component/SKILL.md +347 -0
  269. package/templates/skills/development/frontend/component/cli/scaffold-component/__tests__/generate.test.ts +1237 -0
  270. package/templates/skills/development/frontend/component/cli/scaffold-component/generate.ts +1923 -0
  271. package/templates/skills/development/frontend/component/cli/scaffold-component/index.ts +155 -0
  272. package/templates/skills/development/frontend/component/cli/scaffold-component/types.ts +290 -0
  273. package/templates/skills/development/frontend/component/cli/scaffold-component/validate.ts +16 -0
  274. package/templates/skills/development/frontend/component/cli/validate-page/__tests__/execute.test.ts +231 -0
  275. package/templates/skills/development/frontend/component/cli/validate-page/execute.ts +598 -0
  276. package/templates/skills/development/frontend/component/cli/validate-page/index.ts +88 -0
  277. package/templates/skills/development/frontend/component/cli/validate-page/types.ts +58 -0
  278. package/templates/skills/development/frontend/component/cli/validate-page/validate.ts +28 -0
  279. package/templates/skills/development/frontend/component/patterns/README.md +42 -0
  280. package/templates/skills/development/frontend/component/patterns/detail-page.md +133 -0
  281. package/templates/skills/development/frontend/component/patterns/entity-card.md +148 -0
  282. package/templates/skills/development/frontend/component/patterns/form-page.md +191 -0
  283. package/templates/skills/development/frontend/component/patterns/kanban-board.md +195 -0
  284. package/templates/skills/development/frontend/component/patterns/list-page.md +175 -0
  285. package/templates/skills/development/frontend/extension-config/SKILL.md +108 -0
  286. package/templates/skills/development/frontend/extension-config/cli/scaffold-extension-config/generate.ts +64 -0
  287. package/templates/skills/development/frontend/extension-config/cli/scaffold-extension-config/index.ts +70 -0
  288. package/templates/skills/development/frontend/extension-config/cli/scaffold-extension-config/types.ts +27 -0
  289. package/templates/skills/development/frontend/extension-config/cli/scaffold-extension-config/validate.ts +16 -0
  290. package/templates/skills/development/frontend/layout/SKILL.md +52 -0
  291. package/templates/skills/development/frontend/layout/cli/scaffold-layout/__tests__/generate.test.ts +66 -0
  292. package/templates/skills/development/frontend/layout/cli/scaffold-layout/generate.ts +175 -0
  293. package/templates/skills/development/frontend/layout/cli/scaffold-layout/index.ts +104 -0
  294. package/templates/skills/development/frontend/layout/cli/scaffold-layout/types.ts +17 -0
  295. package/templates/skills/development/frontend/layout/cli/scaffold-layout/validate.ts +37 -0
  296. package/templates/skills/development/frontend/routes/SKILL.md +152 -0
  297. package/templates/skills/development/frontend/routes/cli/aggregate-component-registry/generate.ts +216 -0
  298. package/templates/skills/development/frontend/routes/cli/aggregate-component-registry/index.ts +121 -0
  299. package/templates/skills/development/frontend/routes/cli/aggregate-component-registry/types.ts +69 -0
  300. package/templates/skills/development/frontend/routes/cli/aggregate-component-registry/validate.ts +23 -0
  301. package/templates/skills/development/frontend/routes/cli/scaffold-routes/__tests__/generate.test.ts +292 -0
  302. package/templates/skills/development/frontend/routes/cli/scaffold-routes/generate.ts +270 -0
  303. package/templates/skills/development/frontend/routes/cli/scaffold-routes/index.ts +42 -0
  304. package/templates/skills/development/frontend/routes/cli/scaffold-routes/types.ts +114 -0
  305. package/templates/skills/development/frontend/routes/cli/scaffold-routes/validate.ts +68 -0
  306. package/templates/skills/development/frontend/structure/SKILL.md +119 -0
  307. package/templates/skills/development/frontend/theme/SKILL.md +48 -0
  308. package/templates/skills/development/frontend/theme/cli/scaffold-theme/__tests__/generate.test.ts +53 -0
  309. package/templates/skills/development/frontend/theme/cli/scaffold-theme/generate.ts +129 -0
  310. package/templates/skills/development/frontend/theme/cli/scaffold-theme/index.ts +102 -0
  311. package/templates/skills/development/frontend/theme/cli/scaffold-theme/types.ts +48 -0
  312. package/templates/skills/development/frontend/theme/cli/scaffold-theme/validate.ts +37 -0
  313. package/templates/skills/development/frontend/ui-polish/SKILL.md +192 -0
  314. package/templates/skills/development/frontend/ui-polish/cli/ui-polish/__tests__/r18.test.ts +153 -0
  315. package/templates/skills/development/frontend/ui-polish/cli/ui-polish/__tests__/r19.test.ts +307 -0
  316. package/templates/skills/development/frontend/ui-polish/cli/ui-polish/__tests__/r20.test.ts +167 -0
  317. package/templates/skills/development/frontend/ui-polish/cli/ui-polish/__tests__/shared-scan.test.ts +262 -0
  318. package/templates/skills/development/frontend/ui-polish/cli/ui-polish/apply.ts +580 -0
  319. package/templates/skills/development/frontend/ui-polish/cli/ui-polish/audit.ts +825 -0
  320. package/templates/skills/development/frontend/ui-polish/cli/ui-polish/index.ts +133 -0
  321. package/templates/skills/development/frontend/ui-polish/cli/ui-polish/types.ts +121 -0
  322. package/templates/skills/development/frontend/ui-polish/cli/ui-polish/validate.ts +73 -0
  323. package/templates/skills/development/frontend/ui-polish/tokens.json +292 -0
  324. package/templates/skills/development/frontend/ui-primitives/SKILL.md +88 -0
  325. package/templates/skills/development/frontend/ui-primitives/cli/scaffold-ui-primitives/__tests__/generate.test.ts +158 -0
  326. package/templates/skills/development/frontend/ui-primitives/cli/scaffold-ui-primitives/generate.ts +345 -0
  327. package/templates/skills/development/frontend/ui-primitives/cli/scaffold-ui-primitives/index.ts +142 -0
  328. package/templates/skills/development/frontend/ui-primitives/cli/scaffold-ui-primitives/types.ts +27 -0
  329. package/templates/skills/development/frontend/ui-primitives/cli/scaffold-ui-primitives/validate.ts +37 -0
  330. package/templates/skills/development/run/SKILL.md +61 -0
  331. package/templates/skills/development/run/backend/SKILL.md +106 -0
  332. package/templates/skills/development/run/frontend/SKILL.md +116 -0
  333. package/templates/skills/development/smoke-test/SKILL.md +99 -0
  334. package/templates/skills/development/smoke-test/cli/run-smoke/execute.ts +424 -0
  335. package/templates/skills/development/smoke-test/cli/run-smoke/index.ts +75 -0
  336. package/templates/skills/development/smoke-test/cli/run-smoke/types.ts +100 -0
  337. package/templates/skills/development/testing/SKILL.md +148 -0
  338. package/templates/skills/development/testing/cli/scaffold-tests/generate.ts +530 -0
  339. package/templates/skills/development/testing/cli/scaffold-tests/index.ts +83 -0
  340. package/templates/skills/development/testing/cli/scaffold-tests/types.ts +51 -0
  341. package/templates/skills/development/testing/cli/scaffold-tests/validate.ts +33 -0
  342. package/templates/skills/development/testing/cli/scaffold-tests-from-ac/__tests__/generate.test.ts +188 -0
  343. package/templates/skills/development/testing/cli/scaffold-tests-from-ac/__tests__/parse-ac.test.ts +191 -0
  344. package/templates/skills/development/testing/cli/scaffold-tests-from-ac/generate.ts +190 -0
  345. package/templates/skills/development/testing/cli/scaffold-tests-from-ac/index.ts +138 -0
  346. package/templates/skills/development/testing/cli/scaffold-tests-from-ac/parse-ac.ts +172 -0
  347. package/templates/skills/development/testing/cli/scaffold-tests-from-ac/types.ts +104 -0
  348. package/templates/skills/development/testing/cli/scaffold-tests-from-ac/validate.ts +57 -0
  349. package/templates/skills/development/testing/cli/test-report/execute.ts +140 -0
  350. package/templates/skills/development/testing/cli/test-report/index.ts +96 -0
  351. package/templates/skills/development/testing/cli/test-report/types.ts +52 -0
  352. package/templates/skills/development/testing/cli/test-report/validate.ts +26 -0
  353. package/templates/skills/development/testing/fix-build/SKILL.md +81 -0
  354. package/templates/skills/development/testing/smoke-http/SKILL.md +123 -0
  355. package/templates/skills/development/testing/smoke-http/cli/smoke-http/execute.ts +129 -0
  356. package/templates/skills/development/testing/smoke-http/cli/smoke-http/index.ts +113 -0
  357. package/templates/skills/development/testing/smoke-http/cli/smoke-http/types.ts +62 -0
  358. package/templates/skills/development/testing/smoke-http/cli/smoke-http/validate.ts +36 -0
  359. package/templates/skills/development/testing/ui-test/SKILL.md +128 -0
  360. package/templates/skills/development/testing/ui-test/cli/build-manifest/generate.ts +129 -0
  361. package/templates/skills/development/testing/ui-test/cli/build-manifest/index.ts +80 -0
  362. package/templates/skills/development/testing/ui-test/cli/build-manifest/types.ts +72 -0
  363. package/templates/skills/development/testing/ui-test/cli/build-manifest/validate.ts +44 -0
  364. package/templates/skills/development/testing/ui-test/cli/run-ui-test/execute.ts +136 -0
  365. package/templates/skills/development/testing/ui-test/cli/run-ui-test/index.ts +75 -0
  366. package/templates/skills/development/testing/ui-test/cli/run-ui-test/lib/dev-browser-driver.ts +250 -0
  367. package/templates/skills/development/testing/ui-test/cli/run-ui-test/templates/delete.js.hbs +83 -0
  368. package/templates/skills/development/testing/ui-test/cli/run-ui-test/templates/detail.js.hbs +87 -0
  369. package/templates/skills/development/testing/ui-test/cli/run-ui-test/templates/edit.js.hbs +91 -0
  370. package/templates/skills/development/testing/ui-test/cli/run-ui-test/templates/form-submit.js.hbs +82 -0
  371. package/templates/skills/development/testing/ui-test/cli/run-ui-test/templates/list.js.hbs +125 -0
  372. package/templates/skills/development/testing/ui-test/cli/run-ui-test/templates/permission-negative.js.hbs +65 -0
  373. package/templates/skills/development/testing/ui-test/cli/run-ui-test/types.ts +57 -0
  374. package/templates/skills/development/testing/ui-test/cli/run-ui-test/validate.ts +56 -0
  375. package/templates/skills/documentation/SKILL.md +168 -139
  376. package/templates/skills/documentation/cli/extract-doc/__tests__/forbidden.test.ts +136 -0
  377. package/templates/skills/documentation/cli/extract-doc/__tests__/overflow.test.ts +76 -0
  378. package/templates/skills/documentation/cli/extract-doc/__tests__/required.test.ts +147 -0
  379. package/templates/skills/documentation/cli/extract-doc/extract.ts +657 -0
  380. package/templates/skills/documentation/cli/extract-doc/index.ts +102 -0
  381. package/templates/skills/documentation/cli/extract-doc/types.ts +133 -0
  382. package/templates/skills/documentation/cli/extract-doc/validate.ts +35 -0
  383. package/templates/skills/documentation/cli/scaffold-doc/generate.ts +198 -0
  384. package/templates/skills/documentation/cli/scaffold-doc/index.ts +61 -0
  385. package/templates/skills/documentation/cli/scaffold-doc/types.ts +72 -0
  386. package/templates/skills/documentation/cli/scaffold-doc/validate.ts +33 -0
  387. package/templates/skills/documentation/data-schema.md +18 -38
  388. package/templates/skills/documentation/steps/step-01-scan.md +59 -113
  389. package/templates/skills/documentation/steps/step-02-generate.md +158 -231
  390. package/templates/skills/documentation/steps/step-03-validate.md +101 -280
  391. package/templates/skills/documentation/templates.md +403 -92
  392. package/templates/skills/efcore/SKILL.md +88 -308
  393. package/templates/skills/efcore/_shared.md +140 -0
  394. package/templates/skills/efcore/agents/create.md +69 -0
  395. package/templates/skills/efcore/agents/db-update.md +58 -0
  396. package/templates/skills/efcore/agents/list.md +35 -0
  397. package/templates/skills/efcore/agents/rebase-snapshot.md +50 -0
  398. package/templates/skills/efcore/agents/recreate-db.md +78 -0
  399. package/templates/skills/efcore/agents/squash.md +78 -0
  400. package/templates/skills/efcore/agents/status.md +35 -0
  401. package/templates/skills/efcore/cli/create/execute.ts +164 -0
  402. package/templates/skills/efcore/cli/create/index.ts +51 -0
  403. package/templates/skills/efcore/cli/create/types.ts +35 -0
  404. package/templates/skills/efcore/cli/create/validate.ts +29 -0
  405. package/templates/skills/efcore/cli/lib/detect-dbcontexts.ts +195 -0
  406. package/templates/skills/efcore/cli/lib/ef-runner.ts +144 -0
  407. package/templates/skills/efcore/cli/lib/migration-name.ts +56 -0
  408. package/templates/skills/efcore/cli/lib/parse-csproj-version.ts +45 -0
  409. package/templates/skills/efcore/cli/list/execute.ts +83 -0
  410. package/templates/skills/efcore/cli/list/index.ts +60 -0
  411. package/templates/skills/efcore/cli/list/types.ts +46 -0
  412. package/templates/skills/efcore/cli/list/validate.ts +20 -0
  413. package/templates/skills/efcore/cli/rebase-snapshot/execute.ts +21 -0
  414. package/templates/skills/efcore/cli/rebase-snapshot/index.ts +53 -0
  415. package/templates/skills/efcore/cli/rebase-snapshot/types.ts +20 -0
  416. package/templates/skills/efcore/cli/rebase-snapshot/validate.ts +25 -0
  417. package/templates/skills/efcore/cli/squash/execute.ts +298 -0
  418. package/templates/skills/efcore/cli/squash/index.ts +57 -0
  419. package/templates/skills/efcore/cli/squash/types.ts +38 -0
  420. package/templates/skills/efcore/cli/squash/validate.ts +30 -0
  421. package/templates/skills/efcore/cli/status/execute.ts +152 -0
  422. package/templates/skills/efcore/cli/status/index.ts +45 -0
  423. package/templates/skills/efcore/cli/status/types.ts +32 -0
  424. package/templates/skills/efcore/cli/status/validate.ts +20 -0
  425. package/templates/skills/external/context7/SKILL.md +121 -0
  426. package/templates/skills/external/dev-browser/SKILL.md +134 -0
  427. package/templates/skills/gitflow/SKILL.md +139 -392
  428. package/templates/skills/gitflow/_shared.md +24 -620
  429. package/templates/skills/gitflow/agents/abort.md +47 -0
  430. package/templates/skills/gitflow/agents/cleanup.md +50 -0
  431. package/templates/skills/gitflow/agents/commit.md +41 -0
  432. package/templates/skills/gitflow/agents/finish.md +47 -0
  433. package/templates/skills/gitflow/agents/init.md +41 -0
  434. package/templates/skills/gitflow/agents/merge.md +44 -0
  435. package/templates/skills/gitflow/agents/pr.md +39 -0
  436. package/templates/skills/gitflow/agents/start.md +42 -0
  437. package/templates/skills/gitflow/agents/status.md +39 -0
  438. package/templates/skills/gitflow/agents/sync.md +38 -0
  439. package/templates/skills/gitflow/cli/abort/execute.ts +61 -0
  440. package/templates/skills/gitflow/cli/abort/index.ts +116 -0
  441. package/templates/skills/gitflow/cli/abort/types.ts +21 -0
  442. package/templates/skills/gitflow/cli/abort/validate.ts +38 -0
  443. package/templates/skills/gitflow/cli/cleanup/execute.ts +107 -0
  444. package/templates/skills/gitflow/cli/cleanup/index.ts +122 -0
  445. package/templates/skills/gitflow/cli/cleanup/types.ts +26 -0
  446. package/templates/skills/gitflow/cli/cleanup/validate.ts +33 -0
  447. package/templates/skills/gitflow/cli/commit/execute.ts +146 -0
  448. package/templates/skills/gitflow/cli/commit/index.ts +77 -0
  449. package/templates/skills/gitflow/cli/commit/types.ts +36 -0
  450. package/templates/skills/gitflow/cli/commit/validate.ts +38 -0
  451. package/templates/skills/gitflow/cli/finish/execute.ts +127 -0
  452. package/templates/skills/gitflow/cli/finish/index.ts +106 -0
  453. package/templates/skills/gitflow/cli/finish/types.ts +25 -0
  454. package/templates/skills/gitflow/cli/finish/validate.ts +44 -0
  455. package/templates/skills/gitflow/cli/generate-msg/execute.ts +51 -0
  456. package/templates/skills/gitflow/cli/generate-msg/index.ts +73 -0
  457. package/templates/skills/gitflow/cli/generate-msg/types.ts +37 -0
  458. package/templates/skills/gitflow/cli/generate-msg/validate.ts +42 -0
  459. package/templates/skills/gitflow/cli/init/execute.ts +186 -0
  460. package/templates/skills/gitflow/cli/init/index.ts +127 -0
  461. package/templates/skills/gitflow/cli/init/types.ts +63 -0
  462. package/templates/skills/gitflow/cli/init/validate.ts +56 -0
  463. package/templates/skills/gitflow/cli/lib/branch.ts +83 -0
  464. package/templates/skills/gitflow/cli/lib/config.ts +149 -0
  465. package/templates/skills/gitflow/cli/lib/efcore.ts +136 -0
  466. package/templates/skills/gitflow/cli/lib/git.ts +212 -0
  467. package/templates/skills/gitflow/cli/lib/output.ts +49 -0
  468. package/templates/skills/gitflow/cli/lib/paths.ts +54 -0
  469. package/templates/skills/gitflow/cli/lib/platform.ts +44 -0
  470. package/templates/skills/gitflow/cli/lib/provider.ts +147 -0
  471. package/templates/skills/gitflow/cli/lib/types.ts +189 -0
  472. package/templates/skills/gitflow/cli/lib/version.ts +152 -0
  473. package/templates/skills/gitflow/cli/lib/worktree.ts +170 -0
  474. package/templates/skills/gitflow/cli/merge/execute.ts +93 -0
  475. package/templates/skills/gitflow/cli/merge/index.ts +109 -0
  476. package/templates/skills/gitflow/cli/merge/types.ts +24 -0
  477. package/templates/skills/gitflow/cli/merge/validate.ts +33 -0
  478. package/templates/skills/gitflow/cli/pr/execute.ts +131 -0
  479. package/templates/skills/gitflow/cli/pr/index.ts +115 -0
  480. package/templates/skills/gitflow/cli/pr/types.ts +27 -0
  481. package/templates/skills/gitflow/cli/pr/validate.ts +27 -0
  482. package/templates/skills/gitflow/cli/start/execute.ts +98 -0
  483. package/templates/skills/gitflow/cli/start/index.ts +75 -0
  484. package/templates/skills/gitflow/cli/start/types.ts +26 -0
  485. package/templates/skills/gitflow/cli/start/validate.ts +47 -0
  486. package/templates/skills/gitflow/cli/status/execute.ts +251 -0
  487. package/templates/skills/gitflow/cli/status/index.ts +154 -0
  488. package/templates/skills/gitflow/cli/status/types.ts +75 -0
  489. package/templates/skills/gitflow/cli/status/validate.ts +38 -0
  490. package/templates/skills/gitflow/cli/sync/execute.ts +84 -0
  491. package/templates/skills/gitflow/cli/sync/index.ts +75 -0
  492. package/templates/skills/gitflow/cli/sync/types.ts +25 -0
  493. package/templates/skills/gitflow/cli/sync/validate.ts +34 -0
  494. package/templates/skills/gitflow/commit-message.md +46 -0
  495. package/templates/skills/init/SKILL.md +54 -0
  496. package/templates/skills/lib/__tests__/canonical-hash.test.ts +45 -0
  497. package/templates/skills/lib/__tests__/page-spec-actions.test.ts +232 -0
  498. package/templates/skills/lib/canonical-hash.ts +40 -0
  499. package/templates/skills/lib/detector.ts +243 -0
  500. package/templates/skills/lib/dotnet.ts +238 -0
  501. package/templates/skills/lib/frontend-fixers.ts +151 -0
  502. package/templates/skills/lib/fs.ts +238 -0
  503. package/templates/skills/lib/git.ts +134 -0
  504. package/templates/skills/lib/graph.ts +138 -0
  505. package/templates/skills/lib/navroute-parser.ts +51 -0
  506. package/templates/skills/lib/output.ts +117 -0
  507. package/templates/skills/lib/page-spec-actions.ts +350 -0
  508. package/templates/skills/lib/skill-slug.ts +121 -0
  509. package/templates/skills/lib/string-utils.ts +140 -0
  510. package/templates/skills/lib/template-loader.ts +115 -0
  511. package/templates/skills/lib/url-conventions.ts +151 -0
  512. package/templates/skills/package.json +5 -0
  513. package/templates/skills/quick-search/SKILL.md +99 -99
  514. package/templates/skills/review/SKILL.md +56 -0
  515. package/templates/skills/smoke-generation/SKILL.md +18 -16
  516. package/templates/skills/ui-components/SKILL.md +136 -457
  517. package/templates/skills/upgrade/SKILL.md +36 -0
  518. package/templates/skills/utils/SKILL.md +45 -44
  519. package/templates/skills/utils/subcommands/test-web-config.md +152 -152
  520. package/templates/skills/utils/subcommands/test-web.md +123 -123
  521. package/templates/skills/validate-feature/SKILL.md +102 -101
  522. package/templates/skills/validate-feature/references/api-smoke-tests.md +140 -140
  523. package/templates/skills/validate-feature/references/db-validation-checks.md +180 -180
  524. package/templates/skills/validate-feature/steps/step-00-dependencies.md +121 -121
  525. package/templates/skills/validate-feature/steps/step-01-compile.md +39 -39
  526. package/templates/skills/validate-feature/steps/step-02-unit-tests.md +45 -45
  527. package/templates/skills/validate-feature/steps/step-03-integration-tests.md +53 -53
  528. package/templates/skills/validate-feature/steps/step-04-api-smoke.md +94 -94
  529. package/templates/skills/validate-feature/steps/step-05-db-validation.md +149 -149
  530. package/templates/skills/validation/conventions/SKILL.md +193 -0
  531. package/templates/skills/validation/conventions/cli/validate-conventions/execute.ts +368 -0
  532. package/templates/skills/validation/conventions/cli/validate-conventions/index.ts +67 -0
  533. package/templates/skills/validation/conventions/cli/validate-conventions/types.ts +91 -0
  534. package/templates/skills/validation/conventions/cli/validate-conventions/validate.ts +36 -0
  535. package/templates/skills/validation/cross-validate/SKILL.md +83 -0
  536. package/templates/skills/validation/cross-validate/cli/execute.ts +576 -0
  537. package/templates/skills/validation/cross-validate/cli/index.ts +87 -0
  538. package/templates/skills/validation/cross-validate/cli/types.ts +85 -0
  539. package/templates/skills/validation/cross-validate/cli/validate.ts +54 -0
  540. package/templates/skills/validation/eslint/SKILL.md +65 -0
  541. package/templates/skills/validation/eslint/cli/execute.ts +413 -0
  542. package/templates/skills/validation/eslint/cli/index.ts +102 -0
  543. package/templates/skills/validation/eslint/cli/types.ts +48 -0
  544. package/templates/skills/validation/eslint/cli/validate.ts +43 -0
  545. package/templates/skills/validation/project-inventory/SKILL.md +166 -0
  546. package/templates/skills/validation/project-inventory/cli/project-inventory/execute.ts +397 -0
  547. package/templates/skills/validation/project-inventory/cli/project-inventory/index.ts +80 -0
  548. package/templates/skills/validation/project-inventory/cli/project-inventory/types.ts +89 -0
  549. package/templates/skills/validation/project-inventory/cli/project-inventory/validate.ts +35 -0
  550. package/templates/skills/validation/readiness-report/SKILL.md +109 -0
  551. package/templates/skills/validation/readiness-report/cli/execute.ts +236 -0
  552. package/templates/skills/validation/readiness-report/cli/index.ts +234 -0
  553. package/templates/skills/validation/readiness-report/cli/types.ts +61 -0
  554. package/templates/skills/validation/readiness-report/cli/validate.ts +54 -0
  555. package/templates/skills/validation/roslyn/SKILL.md +103 -0
  556. package/templates/skills/validation/roslyn/cli/execute.ts +616 -0
  557. package/templates/skills/validation/roslyn/cli/index.ts +86 -0
  558. package/templates/skills/validation/roslyn/cli/types.ts +50 -0
  559. package/templates/skills/validation/roslyn/cli/validate.ts +43 -0
  560. package/.documentation/apex.html +0 -649
  561. package/dist/mcp-entry.mjs +0 -68888
  562. package/dist/mcp-entry.mjs.map +0 -1
  563. package/scripts/extract-api-endpoints.ts +0 -325
  564. package/scripts/extract-business-rules.ts +0 -440
  565. package/scripts/generate-doc-with-mock-ui.ts +0 -804
  566. package/templates/agents/ba-reader.md +0 -386
  567. package/templates/agents/ba-writer.md +0 -810
  568. package/templates/agents/efcore/migration.md +0 -204
  569. package/templates/agents/efcore/rebase-snapshot.md +0 -202
  570. package/templates/agents/efcore/squash.md +0 -269
  571. package/templates/agents/gitflow/abort.md +0 -45
  572. package/templates/agents/gitflow/cleanup.md +0 -107
  573. package/templates/agents/gitflow/commit.md +0 -236
  574. package/templates/agents/gitflow/exec.md +0 -48
  575. package/templates/agents/gitflow/finish.md +0 -146
  576. package/templates/agents/gitflow/init-clone.md +0 -199
  577. package/templates/agents/gitflow/init-detect.md +0 -137
  578. package/templates/agents/gitflow/init-validate.md +0 -225
  579. package/templates/agents/gitflow/init.md +0 -509
  580. package/templates/agents/gitflow/merge.md +0 -145
  581. package/templates/agents/gitflow/plan.md +0 -42
  582. package/templates/agents/gitflow/pr.md +0 -191
  583. package/templates/agents/gitflow/review.md +0 -49
  584. package/templates/agents/gitflow/start.md +0 -147
  585. package/templates/agents/gitflow/status.md +0 -95
  586. package/templates/agents/mcp-healthcheck.md +0 -163
  587. package/templates/hooks/docs-drift-check.md +0 -96
  588. package/templates/hooks/ef-migration-check.md +0 -139
  589. package/templates/hooks/mcp-check.md +0 -64
  590. package/templates/hooks/ralph-mcp-logger.sh +0 -46
  591. package/templates/mcp-scaffolding/component.tsx.hbs +0 -318
  592. package/templates/mcp-scaffolding/controller.cs.hbs +0 -118
  593. package/templates/mcp-scaffolding/entity-extension.cs.hbs +0 -239
  594. package/templates/mcp-scaffolding/frontend/api-client.ts.hbs +0 -117
  595. package/templates/mcp-scaffolding/frontend/nav-routes.ts.hbs +0 -133
  596. package/templates/mcp-scaffolding/migrations/seed-roles.cs.hbs +0 -261
  597. package/templates/mcp-scaffolding/service-extension.cs.hbs +0 -53
  598. package/templates/mcp-scaffolding/tests/controller.test.cs.hbs +0 -436
  599. package/templates/mcp-scaffolding/tests/entity.test.cs.hbs +0 -239
  600. package/templates/mcp-scaffolding/tests/repository.test.cs.hbs +0 -441
  601. package/templates/mcp-scaffolding/tests/security.test.cs.hbs +0 -442
  602. package/templates/mcp-scaffolding/tests/service.test.cs.hbs +0 -402
  603. package/templates/mcp-scaffolding/tests/validator.test.cs.hbs +0 -428
  604. package/templates/skills/_resources/config-safety.md +0 -61
  605. package/templates/skills/_resources/context-digest-template.md +0 -53
  606. package/templates/skills/_resources/doc-context-cache.md +0 -60
  607. package/templates/skills/_resources/docs-manifest-schema.md +0 -155
  608. package/templates/skills/_resources/formatting-guide.md +0 -124
  609. package/templates/skills/_resources/mcp-validate-documentation-spec.md +0 -181
  610. package/templates/skills/_shared.md +0 -228
  611. package/templates/skills/admin/SKILL.md +0 -48
  612. package/templates/skills/ai-prompt/SKILL.md +0 -171
  613. package/templates/skills/ai-prompt/references/ai-agent-modes.md +0 -89
  614. package/templates/skills/ai-prompt/references/eval-framework.md +0 -129
  615. package/templates/skills/ai-prompt/steps/step-00-init.md +0 -47
  616. package/templates/skills/ai-prompt/steps/step-01-implementation.md +0 -122
  617. package/templates/skills/apex/SKILL.md +0 -234
  618. package/templates/skills/apex/_shared.md +0 -197
  619. package/templates/skills/apex/references/analysis-methods.md +0 -178
  620. package/templates/skills/apex/references/challenge-questions.md +0 -359
  621. package/templates/skills/apex/references/checks/architecture-checks.sh +0 -154
  622. package/templates/skills/apex/references/checks/backend-checks.sh +0 -208
  623. package/templates/skills/apex/references/checks/frontend-checks.sh +0 -560
  624. package/templates/skills/apex/references/checks/infrastructure-checks.sh +0 -292
  625. package/templates/skills/apex/references/checks/security-checks.sh +0 -153
  626. package/templates/skills/apex/references/checks/seed-checks.sh +0 -610
  627. package/templates/skills/apex/references/code-generation.md +0 -412
  628. package/templates/skills/apex/references/core-seed-data.md +0 -1502
  629. package/templates/skills/apex/references/domain-events-pattern.md +0 -45
  630. package/templates/skills/apex/references/entity-hooks-pattern.md +0 -68
  631. package/templates/skills/apex/references/error-classification.md +0 -168
  632. package/templates/skills/apex/references/frontend-route-wiring-app-tsx.md +0 -91
  633. package/templates/skills/apex/references/licensing-enforcement.md +0 -52
  634. package/templates/skills/apex/references/parallel-execution.md +0 -187
  635. package/templates/skills/apex/references/person-extension-pattern.md +0 -619
  636. package/templates/skills/apex/references/post-checks.md +0 -162
  637. package/templates/skills/apex/references/smartstack-api.md +0 -591
  638. package/templates/skills/apex/references/smartstack-frontend-compliance.md +0 -616
  639. package/templates/skills/apex/references/smartstack-frontend.md +0 -442
  640. package/templates/skills/apex/references/smartstack-layers.md +0 -551
  641. package/templates/skills/apex/steps/step-00-init.md +0 -405
  642. package/templates/skills/apex/steps/step-01-analyze.md +0 -210
  643. package/templates/skills/apex/steps/step-02-plan.md +0 -303
  644. package/templates/skills/apex/steps/step-03-execute.md +0 -194
  645. package/templates/skills/apex/steps/step-03a-layer0-domain.md +0 -144
  646. package/templates/skills/apex/steps/step-03b-layer1-seed.md +0 -339
  647. package/templates/skills/apex/steps/step-03c-layer2-backend.md +0 -401
  648. package/templates/skills/apex/steps/step-03d-layer3-frontend.md +0 -562
  649. package/templates/skills/apex/steps/step-03e-layer4-devdata.md +0 -46
  650. package/templates/skills/apex/steps/step-04-examine.md +0 -404
  651. package/templates/skills/apex/steps/step-05-deep-review.md +0 -140
  652. package/templates/skills/apex/steps/step-06-resolve.md +0 -120
  653. package/templates/skills/apex/steps/step-07-tests.md +0 -271
  654. package/templates/skills/apex/steps/step-08-run-tests.md +0 -135
  655. package/templates/skills/apex-verify/SKILL.md +0 -110
  656. package/templates/skills/apex-verify/references/audit-rules.md +0 -50
  657. package/templates/skills/apex-verify/steps/step-00-init.md +0 -119
  658. package/templates/skills/apex-verify/steps/step-01-nav-audit.md +0 -96
  659. package/templates/skills/apex-verify/steps/step-02-crud-audit.md +0 -127
  660. package/templates/skills/apex-verify/steps/step-03-perm-audit.md +0 -119
  661. package/templates/skills/apex-verify/steps/step-04-route-audit.md +0 -98
  662. package/templates/skills/apex-verify/steps/step-05-report.md +0 -110
  663. package/templates/skills/application/SKILL.md +0 -241
  664. package/templates/skills/application/references/application-roles-template.md +0 -228
  665. package/templates/skills/application/references/backend-controller-hierarchy.md +0 -68
  666. package/templates/skills/application/references/backend-entity-seeding.md +0 -73
  667. package/templates/skills/application/references/backend-seeding-and-dto-output.md +0 -83
  668. package/templates/skills/application/references/backend-table-prefix-mapping.md +0 -80
  669. package/templates/skills/application/references/backend-verification.md +0 -88
  670. package/templates/skills/application/references/contexts-cheatsheet.md +0 -86
  671. package/templates/skills/application/references/extensions-system.md +0 -158
  672. package/templates/skills/application/references/frontend-i18n-and-output.md +0 -67
  673. package/templates/skills/application/references/frontend-route-naming.md +0 -123
  674. package/templates/skills/application/references/frontend-route-wiring-app-tsx.md +0 -91
  675. package/templates/skills/application/references/frontend-verification.md +0 -158
  676. package/templates/skills/application/references/init-parameter-detection.md +0 -121
  677. package/templates/skills/application/references/migration-checklist-troubleshooting.md +0 -88
  678. package/templates/skills/application/references/nav-fallback-procedure.md +0 -198
  679. package/templates/skills/application/references/provider-template.md +0 -191
  680. package/templates/skills/application/references/roles-client-project-handling.md +0 -55
  681. package/templates/skills/application/references/roles-fallback-procedure.md +0 -144
  682. package/templates/skills/application/references/smartstack-provider.md +0 -118
  683. package/templates/skills/application/references/test-coverage-requirements.md +0 -213
  684. package/templates/skills/application/references/test-frontend.md +0 -73
  685. package/templates/skills/application/references/test-prerequisites.md +0 -72
  686. package/templates/skills/application/references/themes-db-driven.md +0 -484
  687. package/templates/skills/application/steps/step-00-init.md +0 -130
  688. package/templates/skills/application/steps/step-01-navigation.md +0 -170
  689. package/templates/skills/application/steps/step-02-permissions.md +0 -196
  690. package/templates/skills/application/steps/step-03-roles.md +0 -182
  691. package/templates/skills/application/steps/step-03b-provider.md +0 -134
  692. package/templates/skills/application/steps/step-04-backend.md +0 -174
  693. package/templates/skills/application/steps/step-05-frontend.md +0 -189
  694. package/templates/skills/application/steps/step-06-migration.md +0 -189
  695. package/templates/skills/application/steps/step-07-tests.md +0 -356
  696. package/templates/skills/application/steps/step-08-documentation.md +0 -137
  697. package/templates/skills/application/templates-backend.md +0 -463
  698. package/templates/skills/application/templates-frontend.md +0 -950
  699. package/templates/skills/application/templates-i18n.md +0 -520
  700. package/templates/skills/application/templates-seed.md +0 -1110
  701. package/templates/skills/audit-route/SKILL.md +0 -107
  702. package/templates/skills/audit-route/references/routing-pattern.md +0 -131
  703. package/templates/skills/audit-route/steps/step-00-init.md +0 -128
  704. package/templates/skills/audit-route/steps/step-01-inventory.md +0 -157
  705. package/templates/skills/audit-route/steps/step-02-conformity.md +0 -193
  706. package/templates/skills/audit-route/steps/step-03-report.md +0 -201
  707. package/templates/skills/business-analyse/SKILL.md +0 -252
  708. package/templates/skills/business-analyse/_shared.md +0 -276
  709. package/templates/skills/business-analyse/patterns/suggestion-catalog.md +0 -560
  710. package/templates/skills/business-analyse/questionnaire/01-context.md +0 -43
  711. package/templates/skills/business-analyse/questionnaire/02-stakeholders-scope.md +0 -111
  712. package/templates/skills/business-analyse/questionnaire/03-data-ui.md +0 -125
  713. package/templates/skills/business-analyse/questionnaire/04-risks-metrics.md +0 -6
  714. package/templates/skills/business-analyse/questionnaire/05-cross-module.md +0 -69
  715. package/templates/skills/business-analyse/questionnaire.md +0 -156
  716. package/templates/skills/business-analyse/react/application-viewer.md +0 -242
  717. package/templates/skills/business-analyse/react/components.md +0 -532
  718. package/templates/skills/business-analyse/react/i18n-template.md +0 -306
  719. package/templates/skills/business-analyse/react/schema.md +0 -831
  720. package/templates/skills/business-analyse/references/03-json-schemas.md +0 -221
  721. package/templates/skills/business-analyse/references/03-post-check-validation.md +0 -208
  722. package/templates/skills/business-analyse/references/03-smartstack-entity-guards.md +0 -32
  723. package/templates/skills/business-analyse/references/04-cross-module-validation.md +0 -95
  724. package/templates/skills/business-analyse/references/04-file-allocation.md +0 -162
  725. package/templates/skills/business-analyse/references/04-naming-audit-checks.md +0 -174
  726. package/templates/skills/business-analyse/references/04-semantic-validation-matrix.md +0 -118
  727. package/templates/skills/business-analyse/references/acceptance-criteria.md +0 -164
  728. package/templates/skills/business-analyse/references/analysis-semantic-checks.md +0 -190
  729. package/templates/skills/business-analyse/references/canonical-json-formats.md +0 -204
  730. package/templates/skills/business-analyse/references/compilation-structure-cards.md +0 -297
  731. package/templates/skills/business-analyse/references/consolidation-structural-checks.md +0 -124
  732. package/templates/skills/business-analyse/references/detection-strategies.md +0 -424
  733. package/templates/skills/business-analyse/references/domain-research-playbook.md +0 -234
  734. package/templates/skills/business-analyse/references/entity-architecture-decision.md +0 -240
  735. package/templates/skills/business-analyse/references/entity-sourcing-presentation.md +0 -166
  736. package/templates/skills/business-analyse/references/init-resume-logic.md +0 -70
  737. package/templates/skills/business-analyse/references/init-schema-deployment.md +0 -65
  738. package/templates/skills/business-analyse/references/module-completeness-challenge.md +0 -174
  739. package/templates/skills/business-analyse/references/multi-app-detection.md +0 -149
  740. package/templates/skills/business-analyse/references/naming-conventions.md +0 -253
  741. package/templates/skills/business-analyse/references/portal-classification.md +0 -52
  742. package/templates/skills/business-analyse/references/robustness-checks.md +0 -426
  743. package/templates/skills/business-analyse/references/ui-dashboard-spec.md +0 -85
  744. package/templates/skills/business-analyse/references/ui-resource-cards.md +0 -259
  745. package/templates/skills/business-analyse/references/validation-checklist.md +0 -378
  746. package/templates/skills/business-analyse/schemas/application-schema.json +0 -481
  747. package/templates/skills/business-analyse/schemas/feature-schema.json +0 -53
  748. package/templates/skills/business-analyse/schemas/index-schema.json +0 -47
  749. package/templates/skills/business-analyse/schemas/project-schema.json +0 -481
  750. package/templates/skills/business-analyse/schemas/sections/analysis-schema.json +0 -245
  751. package/templates/skills/business-analyse/schemas/sections/discovery-schema.json +0 -80
  752. package/templates/skills/business-analyse/schemas/sections/handoff-schema.json +0 -82
  753. package/templates/skills/business-analyse/schemas/sections/metadata-schema.json +0 -70
  754. package/templates/skills/business-analyse/schemas/sections/specification-schema.json +0 -567
  755. package/templates/skills/business-analyse/schemas/sections/validation-schema.json +0 -93
  756. package/templates/skills/business-analyse/schemas/shared/common-defs.json +0 -227
  757. package/templates/skills/business-analyse/steps/step-00-init.md +0 -463
  758. package/templates/skills/business-analyse/steps/step-01-cadrage.md +0 -901
  759. package/templates/skills/business-analyse/steps/step-02-structure.md +0 -315
  760. package/templates/skills/business-analyse/steps/step-03-specify.md +0 -986
  761. package/templates/skills/business-analyse/steps/step-04-consolidate.md +0 -928
  762. package/templates/skills/business-analyse/templates/tpl-frd.md +0 -168
  763. package/templates/skills/business-analyse/templates/tpl-handoff.md +0 -189
  764. package/templates/skills/business-analyse/templates/tpl-launch-displays.md +0 -59
  765. package/templates/skills/business-analyse/templates-frd.md +0 -476
  766. package/templates/skills/business-analyse/templates-react.md +0 -574
  767. package/templates/skills/business-analyse-design/SKILL.md +0 -101
  768. package/templates/skills/business-analyse-design/references/screens-post-check.md +0 -221
  769. package/templates/skills/business-analyse-design/references/screens-type-mapping.md +0 -138
  770. package/templates/skills/business-analyse-design/references/smartcomponents-templates.md +0 -225
  771. package/templates/skills/business-analyse-design/references/spec-auto-inference.md +0 -117
  772. package/templates/skills/business-analyse-design/steps/step-01-screens.md +0 -108
  773. package/templates/skills/business-analyse-design/steps/step-02-wireframes.md +0 -155
  774. package/templates/skills/business-analyse-design/steps/step-03-navigation.md +0 -189
  775. package/templates/skills/business-analyse-develop/SKILL.md +0 -250
  776. package/templates/skills/business-analyse-develop/references/category-completeness.md +0 -326
  777. package/templates/skills/business-analyse-develop/references/category-rules.md +0 -109
  778. package/templates/skills/business-analyse-develop/references/compact-loop.md +0 -478
  779. package/templates/skills/business-analyse-develop/references/handoff-quality-gate.md +0 -132
  780. package/templates/skills/business-analyse-develop/references/init-resume-recovery.md +0 -183
  781. package/templates/skills/business-analyse-develop/references/module-transition.md +0 -246
  782. package/templates/skills/business-analyse-develop/references/multi-module-queue.md +0 -171
  783. package/templates/skills/business-analyse-develop/references/parallel-execution.md +0 -246
  784. package/templates/skills/business-analyse-develop/references/prd-v3-transformation.md +0 -326
  785. package/templates/skills/business-analyse-develop/references/quality-gates.md +0 -160
  786. package/templates/skills/business-analyse-develop/references/report-reconciliation.md +0 -140
  787. package/templates/skills/business-analyse-develop/references/report-template.md +0 -142
  788. package/templates/skills/business-analyse-develop/references/section-splitting.md +0 -439
  789. package/templates/skills/business-analyse-develop/references/task-transform-legacy.md +0 -256
  790. package/templates/skills/business-analyse-develop/references/team-orchestration.md +0 -547
  791. package/templates/skills/business-analyse-develop/steps/step-00-init.md +0 -242
  792. package/templates/skills/business-analyse-develop/steps/step-01-task.md +0 -182
  793. package/templates/skills/business-analyse-develop/steps/step-01-v4-execute.md +0 -139
  794. package/templates/skills/business-analyse-develop/steps/step-02-execute.md +0 -216
  795. package/templates/skills/business-analyse-develop/steps/step-02-v4-verify.md +0 -176
  796. package/templates/skills/business-analyse-develop/steps/step-03-commit.md +0 -107
  797. package/templates/skills/business-analyse-develop/steps/step-04-check.md +0 -419
  798. package/templates/skills/business-analyse-develop/steps/step-05-report.md +0 -137
  799. package/templates/skills/business-analyse-handoff/SKILL.md +0 -101
  800. package/templates/skills/business-analyse-handoff/references/acceptance-criteria.md +0 -318
  801. package/templates/skills/business-analyse-handoff/references/agent-handoff-transform-prompt.md +0 -211
  802. package/templates/skills/business-analyse-handoff/references/context-isolation-pattern.md +0 -47
  803. package/templates/skills/business-analyse-handoff/references/entity-canonicalization.md +0 -158
  804. package/templates/skills/business-analyse-handoff/references/entity-domain-mapping.md +0 -115
  805. package/templates/skills/business-analyse-handoff/references/handoff-file-inventory.md +0 -49
  806. package/templates/skills/business-analyse-handoff/references/handoff-file-templates.md +0 -197
  807. package/templates/skills/business-analyse-handoff/references/handoff-global-validation.md +0 -142
  808. package/templates/skills/business-analyse-handoff/references/handoff-mappings.md +0 -108
  809. package/templates/skills/business-analyse-handoff/references/handoff-seeddata-generation.md +0 -314
  810. package/templates/skills/business-analyse-handoff/references/prd-generation.md +0 -362
  811. package/templates/skills/business-analyse-handoff/references/prd-validation-checks.md +0 -125
  812. package/templates/skills/business-analyse-handoff/references/project-index-update.md +0 -98
  813. package/templates/skills/business-analyse-handoff/references/readiness-scoring.md +0 -95
  814. package/templates/skills/business-analyse-handoff/schemas/handoff-schema.json +0 -95
  815. package/templates/skills/business-analyse-handoff/steps/step-00-validate.md +0 -158
  816. package/templates/skills/business-analyse-handoff/steps/step-01-transform.md +0 -85
  817. package/templates/skills/business-analyse-handoff/steps/step-02-export.md +0 -169
  818. package/templates/skills/business-analyse-handoff/templates/tpl-progress.md +0 -172
  819. package/templates/skills/business-analyse-html/SKILL.md +0 -89
  820. package/templates/skills/business-analyse-html/html/ba-interactive.html +0 -6134
  821. package/templates/skills/business-analyse-html/html/build-html.js +0 -137
  822. package/templates/skills/business-analyse-html/html/src/partials/cadrage-context.html +0 -34
  823. package/templates/skills/business-analyse-html/html/src/partials/cadrage-scope.html +0 -27
  824. package/templates/skills/business-analyse-html/html/src/partials/cadrage-stakeholders.html +0 -55
  825. package/templates/skills/business-analyse-html/html/src/partials/cadrage-success.html +0 -34
  826. package/templates/skills/business-analyse-html/html/src/partials/consol-datamodel.html +0 -8
  827. package/templates/skills/business-analyse-html/html/src/partials/consol-flows.html +0 -29
  828. package/templates/skills/business-analyse-html/html/src/partials/consol-interactions.html +0 -8
  829. package/templates/skills/business-analyse-html/html/src/partials/consol-permissions.html +0 -8
  830. package/templates/skills/business-analyse-html/html/src/partials/decomp-dependencies.html +0 -38
  831. package/templates/skills/business-analyse-html/html/src/partials/decomp-modules.html +0 -43
  832. package/templates/skills/business-analyse-html/html/src/partials/handoff-summary.html +0 -24
  833. package/templates/skills/business-analyse-html/html/src/partials/module-spec-container.html +0 -4
  834. package/templates/skills/business-analyse-html/html/src/scripts/01-data-init.js +0 -313
  835. package/templates/skills/business-analyse-html/html/src/scripts/02-navigation.js +0 -316
  836. package/templates/skills/business-analyse-html/html/src/scripts/03-render-cadrage.js +0 -162
  837. package/templates/skills/business-analyse-html/html/src/scripts/04-render-modules.js +0 -203
  838. package/templates/skills/business-analyse-html/html/src/scripts/05-render-specs.js +0 -866
  839. package/templates/skills/business-analyse-html/html/src/scripts/06-render-consolidation.js +0 -196
  840. package/templates/skills/business-analyse-html/html/src/scripts/06-render-mockups.js +0 -564
  841. package/templates/skills/business-analyse-html/html/src/scripts/07-render-handoff.js +0 -108
  842. package/templates/skills/business-analyse-html/html/src/scripts/08-editing.js +0 -137
  843. package/templates/skills/business-analyse-html/html/src/scripts/09-export.js +0 -138
  844. package/templates/skills/business-analyse-html/html/src/scripts/10-comments.js +0 -221
  845. package/templates/skills/business-analyse-html/html/src/scripts/11-review-panel.js +0 -167
  846. package/templates/skills/business-analyse-html/html/src/scripts/12-render-diagrams.js +0 -162
  847. package/templates/skills/business-analyse-html/html/src/styles/01-variables.css +0 -38
  848. package/templates/skills/business-analyse-html/html/src/styles/02-layout.css +0 -216
  849. package/templates/skills/business-analyse-html/html/src/styles/03-navigation.css +0 -120
  850. package/templates/skills/business-analyse-html/html/src/styles/04-cards.css +0 -194
  851. package/templates/skills/business-analyse-html/html/src/styles/05-modules.css +0 -518
  852. package/templates/skills/business-analyse-html/html/src/styles/06-wireframes.css +0 -263
  853. package/templates/skills/business-analyse-html/html/src/styles/07-comments.css +0 -184
  854. package/templates/skills/business-analyse-html/html/src/styles/08-review-panel.css +0 -241
  855. package/templates/skills/business-analyse-html/html/src/styles/09-mockups-html.css +0 -220
  856. package/templates/skills/business-analyse-html/html/src/styles/10-diagrams.css +0 -73
  857. package/templates/skills/business-analyse-html/html/src/template.html +0 -449
  858. package/templates/skills/business-analyse-html/references/02-embedded-artifacts-building.md +0 -144
  859. package/templates/skills/business-analyse-html/references/02-feature-data-building.md +0 -143
  860. package/templates/skills/business-analyse-html/references/02-mapping-tables.md +0 -442
  861. package/templates/skills/business-analyse-html/references/02-normalization-helpers.md +0 -139
  862. package/templates/skills/business-analyse-html/references/02-screen-format-detection.md +0 -283
  863. package/templates/skills/business-analyse-html/references/02-self-check-validation.md +0 -199
  864. package/templates/skills/business-analyse-html/references/data-build.md +0 -215
  865. package/templates/skills/business-analyse-html/references/data-mapping.md +0 -452
  866. package/templates/skills/business-analyse-html/references/output-modes.md +0 -119
  867. package/templates/skills/business-analyse-html/references/wireframe-svg-style-guide.md +0 -335
  868. package/templates/skills/business-analyse-html/steps/step-01-collect.md +0 -140
  869. package/templates/skills/business-analyse-html/steps/step-02-build-data.md +0 -76
  870. package/templates/skills/business-analyse-html/steps/step-03-render.md +0 -95
  871. package/templates/skills/business-analyse-html/steps/step-04-verify.md +0 -224
  872. package/templates/skills/business-analyse-quick/SKILL.md +0 -807
  873. package/templates/skills/business-analyse-quick/references/domain-heuristics.md +0 -172
  874. package/templates/skills/business-analyse-quick/references/prd-schema.md +0 -268
  875. package/templates/skills/business-analyse-review/SKILL.md +0 -71
  876. package/templates/skills/business-analyse-review/references/review-data-mapping.md +0 -367
  877. package/templates/skills/business-analyse-review/steps/step-00-init.md +0 -111
  878. package/templates/skills/business-analyse-review/steps/step-01-apply.md +0 -304
  879. package/templates/skills/business-analyse-status/SKILL.md +0 -136
  880. package/templates/skills/cc-agent/SKILL.md +0 -129
  881. package/templates/skills/cc-agent/references/agent-behavior-patterns.md +0 -95
  882. package/templates/skills/cc-agent/references/agent-frontmatter.md +0 -213
  883. package/templates/skills/cc-agent/references/permission-modes.md +0 -102
  884. package/templates/skills/cc-agent/references/tools-reference.md +0 -144
  885. package/templates/skills/cc-agent/steps/step-00-init.md +0 -134
  886. package/templates/skills/cc-agent/steps/step-01-design.md +0 -186
  887. package/templates/skills/cc-agent/steps/step-02-generate.md +0 -131
  888. package/templates/skills/cc-agent/steps/step-03-validate.md +0 -130
  889. package/templates/skills/cc-agent/templates/agent-categorized.md +0 -67
  890. package/templates/skills/cc-agent/templates/agent-standalone.md +0 -56
  891. package/templates/skills/cc-agent/templates/agent-with-skills.md +0 -94
  892. package/templates/skills/cc-audit/SKILL.md +0 -108
  893. package/templates/skills/cc-audit/references/agent-checklist.md +0 -91
  894. package/templates/skills/cc-audit/references/hook-checklist.md +0 -110
  895. package/templates/skills/cc-audit/references/skill-checklist.md +0 -70
  896. package/templates/skills/cc-audit/steps/step-00-init.md +0 -98
  897. package/templates/skills/cc-audit/steps/step-01-scan.md +0 -142
  898. package/templates/skills/cc-audit/steps/step-02-analyze.md +0 -158
  899. package/templates/skills/cc-audit/steps/step-03-report.md +0 -142
  900. package/templates/skills/cc-skill/SKILL.md +0 -134
  901. package/templates/skills/cc-skill/references/best-practices.md +0 -167
  902. package/templates/skills/cc-skill/references/frontmatter-reference.md +0 -182
  903. package/templates/skills/cc-skill/references/skill-patterns.md +0 -199
  904. package/templates/skills/cc-skill/steps/step-00-init.md +0 -119
  905. package/templates/skills/cc-skill/steps/step-01-design.md +0 -199
  906. package/templates/skills/cc-skill/steps/step-02-generate.md +0 -145
  907. package/templates/skills/cc-skill/steps/step-03-steps.md +0 -151
  908. package/templates/skills/cc-skill/steps/step-04-validate.md +0 -124
  909. package/templates/skills/cc-skill/templates/skill-forked.md +0 -85
  910. package/templates/skills/cc-skill/templates/skill-progressive.md +0 -102
  911. package/templates/skills/cc-skill/templates/skill-simple.md +0 -75
  912. package/templates/skills/cc-skill/templates/step-template.md +0 -82
  913. package/templates/skills/controller/SKILL.md +0 -162
  914. package/templates/skills/controller/postman-templates.md +0 -614
  915. package/templates/skills/controller/references/controller-code-templates.md +0 -162
  916. package/templates/skills/controller/references/mcp-scaffold-workflow.md +0 -237
  917. package/templates/skills/controller/references/permission-sync-templates.md +0 -149
  918. package/templates/skills/controller/steps/step-00-init.md +0 -193
  919. package/templates/skills/controller/steps/step-01-analyze.md +0 -152
  920. package/templates/skills/controller/steps/step-02-plan.md +0 -176
  921. package/templates/skills/controller/steps/step-03-generate.md +0 -189
  922. package/templates/skills/controller/steps/step-04-perms.md +0 -80
  923. package/templates/skills/controller/steps/step-05-validate.md +0 -107
  924. package/templates/skills/controller/templates.md +0 -1556
  925. package/templates/skills/debug/SKILL.md +0 -70
  926. package/templates/skills/debug/references/team-protocol.md +0 -232
  927. package/templates/skills/debug/steps/step-00-init.md +0 -57
  928. package/templates/skills/debug/steps/step-01-analyze.md +0 -219
  929. package/templates/skills/debug/steps/step-02-resolve.md +0 -85
  930. package/templates/skills/efcore/references/database-operations.md +0 -66
  931. package/templates/skills/efcore/references/reset-operations.md +0 -81
  932. package/templates/skills/efcore/references/seed-methods.md +0 -86
  933. package/templates/skills/efcore/references/shared-init-functions.md +0 -250
  934. package/templates/skills/efcore/references/sql-objects-injection.md +0 -19
  935. package/templates/skills/efcore/references/troubleshooting.md +0 -81
  936. package/templates/skills/efcore/references/zero-downtime-patterns.md +0 -229
  937. package/templates/skills/explore/SKILL.md +0 -98
  938. package/templates/skills/feature-full/SKILL.md +0 -111
  939. package/templates/skills/feature-full/steps/step-00-init.md +0 -57
  940. package/templates/skills/feature-full/steps/step-01-implementation.md +0 -133
  941. package/templates/skills/gitflow/phases/abort.md +0 -189
  942. package/templates/skills/gitflow/phases/cleanup.md +0 -264
  943. package/templates/skills/gitflow/phases/status.md +0 -192
  944. package/templates/skills/gitflow/references/commit-message-generation.md +0 -58
  945. package/templates/skills/gitflow/references/commit-migration-validation.md +0 -53
  946. package/templates/skills/gitflow/references/finish-cleanup.md +0 -55
  947. package/templates/skills/gitflow/references/finish-version-bumping.md +0 -45
  948. package/templates/skills/gitflow/references/init-config-template.md +0 -143
  949. package/templates/skills/gitflow/references/init-environment-detection.md +0 -41
  950. package/templates/skills/gitflow/references/init-name-normalization.md +0 -118
  951. package/templates/skills/gitflow/references/init-questions.md +0 -193
  952. package/templates/skills/gitflow/references/init-structure-creation.md +0 -75
  953. package/templates/skills/gitflow/references/init-version-detection.md +0 -23
  954. package/templates/skills/gitflow/references/init-workspace-detection.md +0 -43
  955. package/templates/skills/gitflow/references/merge-ci-status.md +0 -36
  956. package/templates/skills/gitflow/references/merge-execution.md +0 -62
  957. package/templates/skills/gitflow/references/merge-pr-context.md +0 -76
  958. package/templates/skills/gitflow/references/plan-template.md +0 -69
  959. package/templates/skills/gitflow/references/pr-build-checks.md +0 -60
  960. package/templates/skills/gitflow/references/pr-generation.md +0 -58
  961. package/templates/skills/gitflow/references/start-branch-normalization.md +0 -28
  962. package/templates/skills/gitflow/references/start-efcore-preflight.md +0 -70
  963. package/templates/skills/gitflow/references/start-local-config.md +0 -113
  964. package/templates/skills/gitflow/references/start-worktree-creation.md +0 -50
  965. package/templates/skills/gitflow/references/sync-push-verify.md +0 -44
  966. package/templates/skills/gitflow/references/sync-rebase-conflicts.md +0 -38
  967. package/templates/skills/gitflow/steps/step-commit.md +0 -199
  968. package/templates/skills/gitflow/steps/step-finish.md +0 -147
  969. package/templates/skills/gitflow/steps/step-init.md +0 -230
  970. package/templates/skills/gitflow/steps/step-merge.md +0 -85
  971. package/templates/skills/gitflow/steps/step-plan.md +0 -151
  972. package/templates/skills/gitflow/steps/step-pr.md +0 -247
  973. package/templates/skills/gitflow/steps/step-start.md +0 -195
  974. package/templates/skills/gitflow/steps/step-sync.md +0 -161
  975. package/templates/skills/gitflow/templates/config.json +0 -72
  976. package/templates/skills/mcp/SKILL.md +0 -62
  977. package/templates/skills/mcp/steps/step-01-healthcheck.md +0 -108
  978. package/templates/skills/mcp/steps/step-02-tools.md +0 -73
  979. package/templates/skills/migrate/SKILL.md +0 -312
  980. package/templates/skills/migrate/references/v3.34-to-v3.46.md +0 -289
  981. package/templates/skills/notification/SKILL.md +0 -173
  982. package/templates/skills/refactor/SKILL.md +0 -56
  983. package/templates/skills/refactor/steps/step-01-discover.md +0 -60
  984. package/templates/skills/refactor/steps/step-02-execute.md +0 -67
  985. package/templates/skills/review-code/SKILL.md +0 -95
  986. package/templates/skills/review-code/references/clean-code-principles.md +0 -292
  987. package/templates/skills/review-code/references/code-quality-metrics.md +0 -174
  988. package/templates/skills/review-code/references/feedback-patterns.md +0 -149
  989. package/templates/skills/review-code/references/owasp-api-top10.md +0 -243
  990. package/templates/skills/review-code/references/security-checklist.md +0 -212
  991. package/templates/skills/review-code/references/smartstack-conventions.md +0 -568
  992. package/templates/skills/review-code/steps/step-01-smartstack.md +0 -96
  993. package/templates/skills/review-code/steps/step-02-detailed-review.md +0 -80
  994. package/templates/skills/review-code/steps/step-03-react.md +0 -44
  995. package/templates/skills/sketch/SKILL.md +0 -34
  996. package/templates/skills/ui-components/accessibility.md +0 -170
  997. package/templates/skills/ui-components/patterns/dashboard-chart.md +0 -327
  998. package/templates/skills/ui-components/patterns/data-table.md +0 -175
  999. package/templates/skills/ui-components/patterns/entity-card.md +0 -77
  1000. package/templates/skills/ui-components/patterns/grid-layout.md +0 -91
  1001. package/templates/skills/ui-components/patterns/kanban.md +0 -43
  1002. package/templates/skills/ui-components/references/component-catalog.md +0 -82
  1003. package/templates/skills/ui-components/responsive-guidelines.md +0 -278
  1004. package/templates/skills/ui-components/style-guide.md +0 -113
  1005. package/templates/skills/validate/SKILL.md +0 -181
  1006. package/templates/skills/workflow/SKILL.md +0 -196
  1007. package/templates/skills/workflow/steps/step-00-init.md +0 -57
  1008. package/templates/skills/workflow/steps/step-01-implementation.md +0 -84
@@ -0,0 +1,1923 @@
1
+ /**
2
+ * cli:scaffold-component — generate.ts
3
+ *
4
+ * Emits:
5
+ * - React pages (list, detail, form) under src/features/{module}/{entity}/pages
6
+ * conforming to the SmartStack customisation-ui baseline
7
+ * (`@/components/ui/PageTemplate` + `@/components/ui/DataTable` +
8
+ * token-based Tailwind, lucide-react icons, PermissionGuard + Slot/Fill).
9
+ * - Locale JSON files under web/{appCode}-web/src/i18n/locales/{fr,en,it,de}/{module}.json
10
+ * — one file per MODULE (the entities of a module share it). The CLI writer
11
+ * (index.ts) deep-merges into the existing file, so generating one entity never
12
+ * wipes a sibling entity's keys. Concurrent generation of two entities in the
13
+ * SAME module is therefore unsafe (read-modify-write race) — serialize within a
14
+ * module (see ba-develop/SKILL.md § Phase 3a).
15
+ *
16
+ * Baseline anatomy (derived verbatim from D:\01 - projets\SmartStack.app\features\customisation-ui
17
+ * pages administration/uiConfiguration/{ThemesListPage, PresetsListPage},
18
+ * administration/tenants/OrganisationCreatePage, administration/tenants/TenantDetailPage):
19
+ *
20
+ * - Every page is wrapped in `<PageTemplate title={t('list.title')} icon={<Icon />} actions={...}>`.
21
+ * - List views use `<DataTable data columns searchable pagination>`.
22
+ * - Detail views use card sections with `rounded-[var(--radius-card)] border
23
+ * border-[var(--border-color)] bg-[var(--bg-card)]`.
24
+ * - Form views use `.input` class for fields, primary CTA with
25
+ * `bg-[var(--color-accent-500)] hover:bg-[var(--color-accent-600)] text-white`.
26
+ * - All colors through CSS vars — no Tailwind color palette (`bg-red-500` etc.).
27
+ * - Icons from lucide-react only.
28
+ * - i18n namespace = {module}, keys nested under entity key (lowercased).
29
+ * - PermissionGuard keys: `{module}.{section}.{action}` — no appCode prefix.
30
+ * - Slot/Fill points: {entity}.header.actions, {entity}.table.columns.before/after,
31
+ * {entity}.form.fields.before/after, {entity}.detail.header/sidebar.
32
+ *
33
+ * No legacy patterns from the prior generate.ts are carried forward.
34
+ */
35
+ import type { ScaffoldComponentInput, GeneratedFile, ComponentField } from './types.js'
36
+
37
+ const LOCALES = ['fr', 'en', 'it', 'de'] as const
38
+ type Locale = (typeof LOCALES)[number]
39
+
40
+ const GENERATED_MARKER = '// @generated-by scaffold-component — DO NOT HAND-EDIT (re-run the CLI to update)\n'
41
+
42
+ /**
43
+ * Convert a flat i18n catalogue (e.g. `{ 'list.title': 'Employés' }`) to the
44
+ * nested form i18next expects in JSON files (`{ list: { title: 'Employés' } }`).
45
+ * Used in enriched mode when `spec.pageSpec.i18nKeys` is provided — the
46
+ * canonical PRD shape stores flat keys (one entry per `t('key')` call) and the
47
+ * generator materialises the nested tree at write time.
48
+ */
49
+ function flatToNested(flat: Record<string, string>): Record<string, unknown> {
50
+ const root: Record<string, unknown> = {}
51
+ for (const [dottedKey, value] of Object.entries(flat)) {
52
+ const parts = dottedKey.split('.')
53
+ let cursor: Record<string, unknown> = root
54
+ for (let i = 0; i < parts.length - 1; i++) {
55
+ const part = parts[i]!
56
+ const next = cursor[part]
57
+ if (typeof next === 'object' && next !== null && !Array.isArray(next)) {
58
+ cursor = next as Record<string, unknown>
59
+ } else {
60
+ const fresh: Record<string, unknown> = {}
61
+ cursor[part] = fresh
62
+ cursor = fresh
63
+ }
64
+ }
65
+ cursor[parts[parts.length - 1]!] = value
66
+ }
67
+ return root
68
+ }
69
+
70
+ function translateLabel(label: string, locale: Locale): string {
71
+ const dictionary: Record<string, Record<Locale, string>> = {
72
+ Loading: { fr: 'Chargement…', en: 'Loading…', it: 'Caricamento…', de: 'Wird geladen…' },
73
+ NotFound: { fr: 'Introuvable', en: 'Not found', it: 'Non trovato', de: 'Nicht gefunden' },
74
+ Empty: { fr: 'Aucune donnée', en: 'No data', it: 'Nessun dato', de: 'Keine Daten' },
75
+ Create: { fr: 'Créer', en: 'Create', it: 'Crea', de: 'Erstellen' },
76
+ Edit: { fr: 'Modifier', en: 'Edit', it: 'Modifica', de: 'Bearbeiten' },
77
+ Delete: { fr: 'Supprimer', en: 'Delete', it: 'Elimina', de: 'Löschen' },
78
+ Update: { fr: 'Mettre à jour', en: 'Update', it: 'Aggiorna', de: 'Aktualisieren' },
79
+ Actions: { fr: 'Actions', en: 'Actions', it: 'Azioni', de: 'Aktionen' },
80
+ Save: { fr: 'Enregistrer', en: 'Save', it: 'Salva', de: 'Speichern' },
81
+ Cancel: { fr: 'Annuler', en: 'Cancel', it: 'Annulla', de: 'Abbrechen' },
82
+ Search: { fr: 'Rechercher…', en: 'Search…', it: 'Cerca…', de: 'Suchen…' },
83
+ Subtitle: { fr: 'Gérer les', en: 'Manage', it: 'Gestisci', de: 'Verwalten' },
84
+ Error: { fr: 'Erreur', en: 'Error', it: 'Errore', de: 'Fehler' },
85
+ Dashboard: { fr: 'Tableau de bord', en: 'Dashboard', it: 'Cruscotto', de: 'Dashboard' },
86
+ StartDate: { fr: 'Date de début', en: 'Start date', it: 'Data inizio', de: 'Startdatum' },
87
+ EndDate: { fr: 'Date de fin', en: 'End date', it: 'Data fine', de: 'Enddatum' },
88
+ Alerts: { fr: 'Alertes', en: 'Alerts', it: 'Avvisi', de: 'Warnungen' },
89
+ NoAlerts: { fr: 'Aucune alerte', en: 'No alerts', it: 'Nessun avviso', de: 'Keine Warnungen' },
90
+ SeverityInfo: { fr: 'Info', en: 'Info', it: 'Info', de: 'Info' },
91
+ SeverityWarning: { fr: 'Avertissement', en: 'Warning', it: 'Avviso', de: 'Warnung' },
92
+ SeverityCritical: { fr: 'Critique', en: 'Critical', it: 'Critico', de: 'Kritisch' },
93
+ Kpis: { fr: 'Indicateurs clés', en: 'Key indicators', it: 'Indicatori chiave', de: 'Kennzahlen' },
94
+ Navigation: { fr: 'Navigation', en: 'Navigation', it: 'Navigazione', de: 'Navigation' },
95
+ }
96
+ const entry = dictionary[label]
97
+ if (!entry) throw new Error(`Missing translation for UI label '${label}' — update CLI dictionary`)
98
+ return entry[locale]
99
+ }
100
+
101
+ /** Map widget type to a sensible default Lucide icon. */
102
+ const WIDGET_TYPE_ICONS: Record<string, string> = {
103
+ kpi: 'TrendingUp',
104
+ counter: 'Hash',
105
+ 'chart-pie': 'PieChart',
106
+ 'chart-bar': 'BarChart3',
107
+ 'chart-line': 'LineChart',
108
+ list: 'List',
109
+ }
110
+
111
+ /** Convert kebab-case icon name to PascalCase Lucide import (e.g. `file-text` → `FileText`). */
112
+ function kebabToPascal(kebab: string): string {
113
+ return kebab.split('-').map(s => s.charAt(0).toUpperCase() + s.slice(1)).join('')
114
+ }
115
+
116
+ function humanize(name: string): string {
117
+ return name
118
+ .replace(/([A-Z])/g, ' $1')
119
+ .replace(/[_-]+/g, ' ')
120
+ .trim()
121
+ .replace(/^./, c => c.toUpperCase())
122
+ }
123
+
124
+ /** kebab → camelCase (`bulk-archive` → `bulkArchive`). Used for mutation
125
+ * variable names that match the api-client service member naming. */
126
+ function toCamel(code: string): string {
127
+ const parts = code.split('-')
128
+ return parts[0]! + parts.slice(1).map(p => p.charAt(0).toUpperCase() + p.slice(1)).join('')
129
+ }
130
+
131
+ function pluralize(singular: string, locale: Locale): string {
132
+ const lowercaseLast = singular.toLowerCase()
133
+ if (locale === 'it') return lowercaseLast.endsWith('a') ? singular.slice(0, -1) + 'e' : singular + 's'
134
+ return singular + 's'
135
+ }
136
+
137
+ /** PascalCase → camelCase (e.g. `FirstName` → `firstName`). */
138
+ function fieldToCamel(name: string): string {
139
+ if (name.length === 0) return name
140
+ return name.charAt(0).toLowerCase() + name.slice(1)
141
+ }
142
+
143
+ const I18N_FIELD_PARENTS = new Set(['fields', 'columns', 'filters', 'help'])
144
+
145
+ function normalizeI18nFieldKeys(flat: Record<string, string>): Record<string, string> {
146
+ const result: Record<string, string> = {}
147
+ for (const [key, value] of Object.entries(flat)) {
148
+ const parts = key.split('.')
149
+ if (parts.length >= 3) {
150
+ const parentSegment = parts[parts.length - 2]!
151
+ if (I18N_FIELD_PARENTS.has(parentSegment)) {
152
+ parts[parts.length - 1] = fieldToCamel(parts[parts.length - 1]!)
153
+ }
154
+ }
155
+ result[parts.join('.')] = value
156
+ }
157
+ return result
158
+ }
159
+
160
+ /**
161
+ * Symmetric to normalizeI18nFieldKeys() — applies the same camelCase
162
+ * normalisation rule on a single labelKey BEFORE it lands in the TSX
163
+ * `t('...')` call. Ensures the key the component requests matches the
164
+ * key the JSON catalogue ships (which goes through normalizeI18nFieldKeys
165
+ * at line ~1400). Without this symmetry, a PRD-emitted `list.columns.Code`
166
+ * (PascalCase) becomes `list.columns.code` in JSON but stays
167
+ * `list.columns.Code` in TSX → untranslated raw key on screen.
168
+ *
169
+ * Path is preserved verbatim — only the leaf is camelCased, and only when
170
+ * its parent is in I18N_FIELD_PARENTS. Idempotent: re-camelCasing a
171
+ * camelCase leaf is a no-op, so legacy pagespecs already in camelCase
172
+ * are unaffected. Action labelKeys (e.g. `list.create`, `form.actions.toggle-actif`)
173
+ * stay verbatim because their parent (`list`, `actions`) is not a field-parent —
174
+ * symmetric with normalizeI18nFieldKeys which leaves them alone too.
175
+ */
176
+ function normalizeI18nKey(key: string): string {
177
+ const parts = key.split('.')
178
+ if (parts.length >= 3) {
179
+ const parentSegment = parts[parts.length - 2]!
180
+ if (I18N_FIELD_PARENTS.has(parentSegment)) {
181
+ parts[parts.length - 1] = fieldToCamel(parts[parts.length - 1]!)
182
+ }
183
+ }
184
+ return parts.join('.')
185
+ }
186
+
187
+ function buildTranslations(spec: ScaffoldComponentInput, locale: Locale): Record<string, unknown> {
188
+ const fieldLabels: Record<string, string> = {}
189
+ for (const f of spec.fields) {
190
+ fieldLabels[fieldToCamel(f.name)] = f.label ?? humanize(f.name)
191
+ }
192
+
193
+ const entityName = humanize(spec.entity)
194
+ const pluralName = pluralize(entityName, locale)
195
+
196
+ return {
197
+ list: {
198
+ title: pluralName,
199
+ subtitle: `${translateLabel('Subtitle', locale)} ${pluralName.toLowerCase()}`,
200
+ search: translateLabel('Search', locale),
201
+ loading: translateLabel('Loading', locale),
202
+ empty: translateLabel('Empty', locale),
203
+ create: translateLabel('Create', locale),
204
+ edit: translateLabel('Edit', locale),
205
+ delete: translateLabel('Delete', locale),
206
+ actionsColumn: translateLabel('Actions', locale),
207
+ error: translateLabel('Error', locale),
208
+ columns: fieldLabels,
209
+ },
210
+ detail: {
211
+ loading: translateLabel('Loading', locale),
212
+ notFound: translateLabel('NotFound', locale),
213
+ edit: translateLabel('Edit', locale),
214
+ fields: fieldLabels,
215
+ },
216
+ form: {
217
+ createTitle: `${translateLabel('Create', locale)} ${entityName}`,
218
+ editTitle: `${translateLabel('Edit', locale)} ${entityName}`,
219
+ submitCreate: translateLabel('Create', locale),
220
+ submitUpdate: translateLabel('Update', locale),
221
+ save: translateLabel('Save', locale),
222
+ cancel: translateLabel('Cancel', locale),
223
+ fields: fieldLabels,
224
+ },
225
+ dashboard: {
226
+ title: `${translateLabel('Dashboard', locale)} ${pluralName.toLowerCase()}`,
227
+ subtitle: pluralName,
228
+ loading: translateLabel('Loading', locale),
229
+ error: translateLabel('Error', locale),
230
+ startDate: translateLabel('StartDate', locale),
231
+ endDate: translateLabel('EndDate', locale),
232
+ kpi: {} as Record<string, string>,
233
+ alerts: {
234
+ title: translateLabel('Alerts', locale),
235
+ empty: translateLabel('NoAlerts', locale),
236
+ severity: {
237
+ info: translateLabel('SeverityInfo', locale),
238
+ warning: translateLabel('SeverityWarning', locale),
239
+ critical: translateLabel('SeverityCritical', locale),
240
+ },
241
+ },
242
+ },
243
+ home: {
244
+ title: pluralName,
245
+ subtitle: `${translateLabel('Subtitle', locale)} ${pluralName.toLowerCase()}`,
246
+ kpis: translateLabel('Kpis', locale),
247
+ navigation: translateLabel('Navigation', locale),
248
+ },
249
+ breadcrumb: {
250
+ section: humanize(spec.section),
251
+ },
252
+ }
253
+ }
254
+
255
+ function defaultForField(f: ComponentField): string {
256
+ if (f.defaultValue !== undefined && f.defaultValue !== null) {
257
+ if (typeof f.defaultValue === 'string') return `'${f.defaultValue}'`
258
+ return String(f.defaultValue)
259
+ }
260
+ switch (f.type.toLowerCase()) {
261
+ case 'string': return `''`
262
+ case 'number': case 'int': case 'integer': case 'decimal': return `0`
263
+ case 'bool': case 'boolean': return `false`
264
+ case 'datetime': case 'date': return `''`
265
+ default: return `''`
266
+ }
267
+ }
268
+
269
+ function formDataTypeLiteral(fields: ComponentField[]): string {
270
+ if (fields.length === 0) return `Record<string, unknown>`
271
+ const lines = fields.map(f => ` ${fieldToCamel(f.name)}${f.required ? '' : '?'}: ${toTsType(f.type)}`)
272
+ return `{\n${lines.join('\n')}\n}`
273
+ }
274
+
275
+ function toTsType(type: string): string {
276
+ switch (type.toLowerCase()) {
277
+ case 'string': case 'guid': case 'datetime': case 'date': return 'string'
278
+ case 'number': case 'int': case 'integer': case 'decimal': return 'number'
279
+ case 'bool': case 'boolean': return 'boolean'
280
+ default: return 'string'
281
+ }
282
+ }
283
+
284
+ export function generate(spec: ScaffoldComponentInput): GeneratedFile[] {
285
+ if (spec.fields.length === 0) {
286
+ throw new Error(`scaffold-component: entity '${spec.entity}' has no fields — scaffolding would produce an empty UI`)
287
+ }
288
+
289
+ const files: GeneratedFile[] = []
290
+ const e = spec.entity
291
+ const eLower = e.charAt(0).toLowerCase() + e.slice(1)
292
+ const webRoot = spec.webRoot ?? `web/${spec.appCode}-web`
293
+ const plural = e + 's'
294
+ // Pages location. When `pageSpec.filePath` is provided, IT is the source
295
+ // of truth — derive the directory from it. This handles legacy folder
296
+ // naming (e.g. `pages/budgeting/budgets/` instead of the canonical
297
+ // `pages/budgets/budgets/`). Without this override the scaffold would
298
+ // write to a fresh directory and abandon the existing pages, leaving
299
+ // the user with two copies. Falls back to the canonical
300
+ // `src/pages/{appCode}/{module}/{section}` when no pageSpec provided.
301
+ const base = spec.pageSpec?.filePath
302
+ ? spec.pageSpec.filePath.replace(/\\/g, '/').replace(/\/[^/]+\.tsx$/i, '')
303
+ : `src/pages/${spec.appCode.toLowerCase()}/${spec.module}/${spec.section}`
304
+
305
+ // When `pageSpec.filePath` matches the current view we want to honour the
306
+ // exact file name from the PRD (e.g. `BudgetsModuleHomePage.tsx`) rather
307
+ // than the entity-derived default. The single-view-per-call invariant
308
+ // (Phase 3a passes `views: [pageSpec.view]`) guarantees the match logic
309
+ // is unambiguous: at most one view in the loop can claim the pageSpec.
310
+ function pathFor(view: string, defaultName: string): string {
311
+ if (spec.pageSpec && spec.pageSpec.view === view && spec.pageSpec.filePath) {
312
+ return spec.pageSpec.filePath.replace(/\\/g, '/')
313
+ }
314
+ return `${base}/${defaultName}`
315
+ }
316
+ const featurePath = `@/features/${spec.module}/${eLower}`
317
+ // Permissions are 4-seg: {app}.{module}.{section}.{action}. The auth
318
+ // service stores them in this shape (ba_permissions.path), and
319
+ // useAuth().hasPermission() does exact-string match. Without the
320
+ // appCode prefix, the match fails → PermissionGuard hides everything →
321
+ // pages render empty in the browser ("aucun changement" symptom).
322
+ // appCode is lowercased to match the DB convention (e.g. TestV2 → testv2).
323
+ const permKey = `${spec.module}.${spec.section}`
324
+ // `spec.section` is kebab-case (URL segment), but `scaffold-routes` emits the
325
+ // `routes` object with camelCase property keys (`typesAffaire`, not `types-affaire`)
326
+ // — so any `routes.<section>` reference in generated TSX must use the same
327
+ // transform. Without it, `routes.types-affaire.detail(item.id)` is parsed by
328
+ // JS as `routes.types - affaire.detail(item.id)` (subtraction), throwing
329
+ // `ReferenceError: affaire is not defined` at the first interaction.
330
+ const sectionCamel = toCamel(spec.section)
331
+ // List file uses the plural entity name (BudgetsListPage.tsx) to match the
332
+ // existing project convention; detail/form/dashboard stay singular.
333
+ const listFileName = `${plural}ListPage.tsx`
334
+ // Form fields exclude computed attributes — they are read-only, projected
335
+ // by the backend, and must NEVER appear as inputs.
336
+ const formFields = spec.fields.filter(f => !f.formula)
337
+ const headerField = spec.fields.find(f => !f.formula) ?? spec.fields[0]
338
+ const initialFormObj = `{\n${formFields.map(f => ` ${fieldToCamel(f.name)}: ${defaultForField(f)},`).join('\n')}\n }`
339
+
340
+ // ─── Shared custom-action infrastructure ─────────────────────────────────
341
+ // Hoisted here so list, detail, AND form blocks can all access it.
342
+ type PageActionMeta = {
343
+ code: string
344
+ kind?: 'api' | 'navigate'
345
+ scope: 'header' | 'row' | 'bulk'
346
+ labelKey: string
347
+ permission: string
348
+ variant?: string
349
+ targetRoute?: string
350
+ targetScreen?: string
351
+ endpoint?: string
352
+ httpMethod?: string
353
+ ucReference?: string
354
+ }
355
+ const STANDARD_CRUD_CODES = new Set(['create', 'edit', 'update', 'delete', 'list', 'detail', 'read'])
356
+ const isNavigate = (a: PageActionMeta): boolean => {
357
+ if (a.kind === 'navigate') return true
358
+ if (a.kind === 'api') return false
359
+ return a.code === 'open' && a.scope === 'row'
360
+ }
361
+ const isApi = (a: PageActionMeta): boolean => !isNavigate(a)
362
+ const hookCodeOf = (a: PageActionMeta): string => a.endpoint ?? a.code
363
+ const toPascal = (code: string): string =>
364
+ code.split('-').map(p => p.charAt(0).toUpperCase() + p.slice(1)).join('')
365
+
366
+ const ICON_FOR_CODE: Record<string, string> = {
367
+ open: 'ExternalLink',
368
+ close: 'XCircle',
369
+ archive: 'Archive',
370
+ restore: 'ArchiveRestore',
371
+ duplicate: 'Copy',
372
+ approve: 'Check',
373
+ reject: 'X',
374
+ validate: 'CheckCircle',
375
+ cancel: 'XCircle',
376
+ submit: 'Send',
377
+ 'submit-for-review': 'Send',
378
+ activate: 'Power',
379
+ deactivate: 'PowerOff',
380
+ assign: 'UserPlus',
381
+ unassign: 'UserMinus',
382
+ export: 'Download',
383
+ import: 'Upload',
384
+ print: 'Printer',
385
+ share: 'Share2',
386
+ lock: 'Lock',
387
+ unlock: 'Unlock',
388
+ 'toggle-actif': 'ToggleRight',
389
+ 'set-availability': 'CalendarCheck',
390
+ 'map-to-pce': 'ArrowRightLeft',
391
+ 'core-alignment': 'GitMerge',
392
+ 'analyze-impact': 'Search',
393
+ 'sync-from-pce': 'RefreshCw',
394
+ 'sync-from-swisstopo': 'MapPin',
395
+ 'open-year': 'CalendarPlus',
396
+ 'close-year': 'CalendarMinus',
397
+ 'publish-to-pce': 'Upload',
398
+ 'detect-anomalies': 'AlertTriangle',
399
+ 'bulk-map': 'Layers',
400
+ }
401
+ const iconForCode = (code: string): string => ICON_FOR_CODE[code] ?? 'ChevronRight'
402
+
403
+ const allCustomActions = ((spec.pageSpec?.actions ?? []) as PageActionMeta[])
404
+ .filter(a => !STANDARD_CRUD_CODES.has(a.code.toLowerCase()))
405
+
406
+ /** Render a header action button JSX string (used by list, detail and form). */
407
+ const renderHeaderActionButton = (a: PageActionMeta): string => {
408
+ const Icon = iconForCode(a.code)
409
+ const isPrimary = a.variant === 'primary'
410
+ const className = isPrimary
411
+ ? 'flex items-center gap-1.5 px-3 py-2 rounded-[var(--radius-button)] bg-[var(--color-accent-500)] text-white text-sm font-medium hover:bg-[var(--color-accent-600)] transition-colors'
412
+ : 'flex items-center gap-1.5 px-3 py-2 rounded-[var(--radius-button)] bg-[var(--bg-secondary)] text-[var(--text-primary)] text-sm font-medium hover:bg-[var(--bg-hover)] transition-colors'
413
+ return ` <PermissionGuard permission="${a.permission}">
414
+ <button
415
+ type="button"
416
+ onClick={() => { void handle${toPascal(a.code)}() }}
417
+ className="${className}"
418
+ >
419
+ <${Icon} className="w-4 h-4" />
420
+ {t('${eLower}.${a.labelKey}')}
421
+ </button>
422
+ </PermissionGuard>`
423
+ }
424
+
425
+ // ─── ListPage ────────────────────────────────────────────────────────────
426
+ if (spec.views.includes('list')) {
427
+ const customActions = allCustomActions
428
+ const apiActions = customActions.filter(isApi)
429
+
430
+ // pageSpec.columns is the PRD-curated list (ba_screens.config.columns →
431
+ // create-prd → pageSpec). When present, use it verbatim so the page
432
+ // shows EXACTLY what the BA asked for — no more, no less. The data model
433
+ // can carry legacy columns (e.g. `description` on the Budget entity) that
434
+ // the BA did not declare for this view; using spec.fields would surface
435
+ // them as untranslated `LIST.COLUMNS.DESCRIPTION` headers.
436
+ // Fallback: spec.fields[0..6] for legacy projects without pageSpec.columns
437
+ // (PRD predates the per-page contract).
438
+ const columnSource = spec.pageSpec?.columns?.length
439
+ ? spec.pageSpec.columns.map((c) => ({
440
+ name: fieldToCamel(c.key),
441
+ // Fix #9 — normalise leaf to match the JSON i18n catalogue
442
+ // (see normalizeI18nKey JSDoc). PRD may emit PascalCase
443
+ // (`list.columns.Code`); JSON normalises to camelCase; TSX must follow.
444
+ labelKey: normalizeI18nKey(c.labelKey),
445
+ sortable: c.sortable ?? false,
446
+ }))
447
+ : spec.fields.slice(0, 6).map((f) => ({
448
+ name: fieldToCamel(f.name),
449
+ labelKey: `list.columns.${fieldToCamel(f.name)}`,
450
+ sortable: true,
451
+ }))
452
+ const columnEntries = columnSource.map(c =>
453
+ ` { key: '${c.name}', label: t('${eLower}.${c.labelKey}'), sortable: ${c.sortable} }`
454
+ ).join(',\n')
455
+
456
+ // pageSpec.filters[] generates a FilterBar above the DataTable. Each entry
457
+ // produces ONE controlled input whose value lives in local React state.
458
+ // For text & select controls we apply client-side filtering on data?.items
459
+ // immediately; date-range UI is rendered but its filtering relies on the
460
+ // hook accepting the values (not yet plumbed — left as v2 enhancement).
461
+ const pageSpecFilters = ((spec.pageSpec?.filters ?? []) as Array<{
462
+ field: string
463
+ labelKey: string
464
+ control?: string
465
+ options?: unknown[]
466
+ }>).map(f => ({
467
+ ...f,
468
+ // Fix #9 — normalise leaf to match the JSON i18n catalogue. One pass
469
+ // here means renderFilterInput() (6 substitution sites for select/text/
470
+ // date-range/boolean labels + placeholders) does not need to be touched.
471
+ labelKey: normalizeI18nKey(f.labelKey),
472
+ }))
473
+ const hasFilters = pageSpecFilters.length > 0
474
+ const reactImport = hasFilters ? `import { useMemo, useState } from 'react'\n` : ''
475
+
476
+ function renderFilterInput(f: typeof pageSpecFilters[number]): string {
477
+ const ctrl = (f.control ?? 'text').toLowerCase()
478
+ if (ctrl === 'select') {
479
+ const opts = Array.isArray(f.options) ? f.options : []
480
+ // When the PRD declares control="select" but provides no options,
481
+ // fall back to a text input — an empty <select> is non-functional.
482
+ if (opts.length === 0) {
483
+ return ` <div className="flex flex-col gap-1 min-w-[180px]">
484
+ <label className="text-xs font-medium text-[var(--text-muted)]">{t('${eLower}.${f.labelKey}')}</label>
485
+ <input
486
+ type="text"
487
+ value={filters['${f.field}'] ?? ''}
488
+ onChange={(event) => onFilterChange('${f.field}', event.target.value)}
489
+ placeholder={t('${eLower}.${f.labelKey}')}
490
+ className="input"
491
+ />
492
+ </div>`
493
+ }
494
+ const optionLines = opts.map((opt) => {
495
+ if (typeof opt === 'string') return ` <option value="${opt}">${opt}</option>`
496
+ const o = opt as { value?: string; label?: string }
497
+ const val = String(o.value ?? '')
498
+ const lab = String(o.label ?? o.value ?? '')
499
+ return ` <option value="${val}">${lab}</option>`
500
+ }).join('\n')
501
+ return ` <div className="flex flex-col gap-1 min-w-[180px]">
502
+ <label className="text-xs font-medium text-[var(--text-muted)]">{t('${eLower}.${f.labelKey}')}</label>
503
+ <select
504
+ value={filters['${f.field}'] ?? ''}
505
+ onChange={(event) => onFilterChange('${f.field}', event.target.value)}
506
+ className="input"
507
+ >
508
+ <option value="">{t('${eLower}.list.filters.all')}</option>
509
+ ${optionLines}
510
+ </select>
511
+ </div>`
512
+ }
513
+ if (ctrl === 'date-range' || ctrl === 'daterange') {
514
+ return ` <div className="flex flex-col gap-1">
515
+ <label className="text-xs font-medium text-[var(--text-muted)]">{t('${eLower}.${f.labelKey}')}</label>
516
+ <div className="flex items-center gap-1">
517
+ <input
518
+ type="date"
519
+ value={filters['${f.field}From'] ?? ''}
520
+ onChange={(event) => onFilterChange('${f.field}From', event.target.value)}
521
+ className="input"
522
+ />
523
+ <span className="text-[var(--text-muted)]">→</span>
524
+ <input
525
+ type="date"
526
+ value={filters['${f.field}To'] ?? ''}
527
+ onChange={(event) => onFilterChange('${f.field}To', event.target.value)}
528
+ className="input"
529
+ />
530
+ </div>
531
+ </div>`
532
+ }
533
+ if (ctrl === 'boolean') {
534
+ return ` <label className="flex items-center gap-1 self-end text-sm text-[var(--text-primary)]">
535
+ <input
536
+ type="checkbox"
537
+ checked={filters['${f.field}'] === 'true'}
538
+ onChange={(event) => onFilterChange('${f.field}', event.target.checked ? 'true' : '')}
539
+ />
540
+ {t('${eLower}.${f.labelKey}')}
541
+ </label>`
542
+ }
543
+ // text + fallback
544
+ return ` <div className="flex flex-col gap-1 min-w-[180px]">
545
+ <label className="text-xs font-medium text-[var(--text-muted)]">{t('${eLower}.${f.labelKey}')}</label>
546
+ <input
547
+ type="text"
548
+ value={filters['${f.field}'] ?? ''}
549
+ onChange={(event) => onFilterChange('${f.field}', event.target.value)}
550
+ placeholder={t('${eLower}.${f.labelKey}')}
551
+ className="input"
552
+ />
553
+ </div>`
554
+ }
555
+
556
+ const filterStateInitEntries = pageSpecFilters.flatMap(f => {
557
+ const ctrl = (f.control ?? 'text').toLowerCase()
558
+ if (ctrl === 'date-range' || ctrl === 'daterange') {
559
+ return [`'${f.field}From': ''`, `'${f.field}To': ''`]
560
+ }
561
+ return [`'${f.field}': ''`]
562
+ }).join(', ')
563
+
564
+ // Bug 4 fix — DTOs emitted by scaffold-api-client are SEALED interfaces (no
565
+ // index signature), so the single cast `item as Record<string, unknown>` is
566
+ // rejected by TypeScript ("conversion may be a mistake — neither type
567
+ // sufficiently overlaps with the other"). The double-cast `as unknown as
568
+ // Record<string, unknown>` is the canonical TS idiom for crossing this
569
+ // boundary safely. We declare it once at filter-block top so each match
570
+ // expression stays readable.
571
+ const filterMatchersJs = pageSpecFilters.map(f => {
572
+ if (f.field === 'q') {
573
+ return ` if (filters['q']) {
574
+ const q = filters['q'].toLowerCase()
575
+ const match = Object.values(item as unknown as Record<string, unknown>).some(v => String(v ?? '').toLowerCase().includes(q))
576
+ if (!match) return false
577
+ }`
578
+ }
579
+ const ctrl = (f.control ?? 'text').toLowerCase()
580
+ if (ctrl === 'select' || ctrl === 'boolean') {
581
+ return ` if (filters['${f.field}'] && String((item as unknown as Record<string, unknown>)['${f.field}'] ?? '') !== filters['${f.field}']) return false`
582
+ }
583
+ if (ctrl === 'date-range' || ctrl === 'daterange') {
584
+ return ` if (filters['${f.field}From'] || filters['${f.field}To']) {
585
+ const cell = (item as unknown as Record<string, unknown>)['${f.field}']
586
+ const cellTime = cell ? new Date(String(cell)).getTime() : NaN
587
+ if (filters['${f.field}From'] && cellTime < new Date(filters['${f.field}From']).getTime()) return false
588
+ if (filters['${f.field}To'] && cellTime > new Date(filters['${f.field}To']).getTime()) return false
589
+ }`
590
+ }
591
+ // text, default
592
+ return ` if (filters['${f.field}']) {
593
+ const cell = String((item as unknown as Record<string, unknown>)['${f.field}'] ?? '').toLowerCase()
594
+ if (!cell.includes(filters['${f.field}'].toLowerCase())) return false
595
+ }`
596
+ }).join('\n')
597
+
598
+ const filterStateBlock = hasFilters
599
+ ? ` const [filters, setFilters] = useState<Record<string, string>>({ ${filterStateInitEntries} })
600
+ const onFilterChange = (key: string, value: string) => setFilters((prev) => ({ ...prev, [key]: value }))
601
+ `
602
+ : ''
603
+
604
+ const customRowActions = customActions.filter(a => a.scope === 'row')
605
+ const customHeaderActions = customActions.filter(a => a.scope === 'header')
606
+
607
+ // Hook imports — ONLY for api-bound actions. Navigate actions do not call
608
+ // any hook (they just `navigate(...)`), so importing one would produce an
609
+ // unused-symbol warning at best, a phantom 405 at worst.
610
+ const customHookImports = apiActions
611
+ .map(a => `, use${toPascal(hookCodeOf(a))}${e}`)
612
+ .join('')
613
+
614
+ // Lucide icon imports — dedup to avoid duplicate identifiers. Both api +
615
+ // navigate actions need their icon, so the union covers every button.
616
+ const usedIcons = new Set(customActions.map(a => iconForCode(a.code)))
617
+ const customIconImports = Array.from(usedIcons).map(name => `, ${name}`).join('')
618
+
619
+ // Mutation declarations — one per api-bound custom action, e.g.
620
+ // const archiveMutation = useArchiveBudget()
621
+ // Navigate actions emit nothing here.
622
+ const customMutationDecls = apiActions
623
+ .map(a => ` const ${toCamel(hookCodeOf(a))}Mutation = use${toPascal(hookCodeOf(a))}${e}()`)
624
+ .join('\n')
625
+
626
+ // Handler functions — bridge button onClick to mutation.mutateAsync (api)
627
+ // or to navigate(targetRoute) (navigate). The two branches keep the same
628
+ // signature shape so the JSX caller is uniform.
629
+ const customHandlerDecls = customActions
630
+ .map(a => {
631
+ const name = `handle${toPascal(a.code)}`
632
+ if (isNavigate(a)) {
633
+ const target = a.targetRoute
634
+ ?? (a.scope === 'row'
635
+ ? `routes.${sectionCamel}.detail(item.id)`
636
+ : `routes.${sectionCamel}.list()`)
637
+ if (a.scope === 'header') {
638
+ return ` const ${name} = () => {\n navigate(${target})\n }`
639
+ }
640
+ return ` const ${name} = (item: ${e}) => {\n navigate(${target})\n }`
641
+ }
642
+ // kind: 'api' — call the matching React Query mutation.
643
+ const hookVar = `${toCamel(hookCodeOf(a))}Mutation`
644
+ if (a.scope === 'header') {
645
+ return ` const ${name} = async () => {\n await ${hookVar}.mutateAsync()\n }`
646
+ }
647
+ // row scope (bulk omitted from this template — needs DataTable selection support)
648
+ return ` const ${name} = async (item: ${e}) => {\n await ${hookVar}.mutateAsync(item.id)\n }`
649
+ })
650
+ .join('\n\n')
651
+
652
+ const customHeaderActionsJsx = customHeaderActions.map(renderHeaderActionButton).join('\n')
653
+
654
+ // Row action buttons JSX — icon-only, danger variant uses error-bg/text.
655
+ const renderRowActionButton = (a: PageActionMeta): string => {
656
+ const Icon = iconForCode(a.code)
657
+ const isDanger = a.variant === 'danger'
658
+ const hoverBg = isDanger ? 'hover:bg-[var(--error-bg)]' : 'hover:bg-[var(--bg-hover)]'
659
+ const hoverText = isDanger ? 'hover:text-[var(--error-text)]' : 'hover:text-[var(--text-primary)]'
660
+ return ` <PermissionGuard permission="${a.permission}">
661
+ <button
662
+ type="button"
663
+ onClick={(e) => { e.stopPropagation(); void handle${toPascal(a.code)}(item) }}
664
+ className="p-1.5 rounded ${hoverBg} text-[var(--text-muted)] ${hoverText}"
665
+ title={t('${eLower}.${a.labelKey}')}
666
+ >
667
+ <${Icon} className="w-4 h-4" />
668
+ </button>
669
+ </PermissionGuard>`
670
+ }
671
+ const customRowActionsJsx = customRowActions.map(renderRowActionButton).join('\n')
672
+
673
+ const filteredItemsBlock = hasFilters
674
+ ? ` const items = data?.items ?? []
675
+ const filtered = useMemo(() => {
676
+ return items.filter((item) => {
677
+ ${filterMatchersJs}
678
+ return true
679
+ })
680
+ }, [items, filters])
681
+ `
682
+ : ` const filtered = data?.items ?? []
683
+ `
684
+
685
+ const filterBarJsx = hasFilters
686
+ ? ` <div className="mb-4 flex flex-wrap items-end gap-3 p-3 rounded-[var(--radius-card)] border border-[var(--border-color)] bg-[var(--bg-card)]">
687
+ ${pageSpecFilters.map(renderFilterInput).join('\n')}
688
+ </div>
689
+
690
+ `
691
+ : ''
692
+
693
+ files.push({
694
+ path: pathFor('list', listFileName),
695
+ content: `${GENERATED_MARKER}${reactImport}import { useNavigate } from 'react-router-dom'
696
+ import { useTranslation } from 'react-i18next'
697
+ import { Loader2, Plus, Pencil, Trash2, List${customIconImports} } from 'lucide-react'
698
+ import { Slot } from '@atlashub/smartstack'
699
+ import { PermissionGuard } from '@/components/auth/PermissionGuard'
700
+ import { PageTemplate } from '@/components/ui/PageTemplate'
701
+ import { DataTable, type DataTableColumn } from '@/components/ui/DataTable'
702
+ import { routes } from '@/extensions/${spec.module}Routes'
703
+ import { use${plural}, useDelete${e}${customHookImports} } from '${featurePath}/hooks/use${e}'
704
+ import type { ${e} } from '${featurePath}/types'
705
+
706
+ export function ${plural}ListPage() {
707
+ const { t } = useTranslation('${spec.module}')
708
+ const navigate = useNavigate()
709
+ const { data, isLoading, error } = use${plural}()
710
+ const deleteMutation = useDelete${e}()
711
+ ${customMutationDecls ? customMutationDecls + '\n' : ''}${filterStateBlock}
712
+ const handleDelete = async (item: ${e}) => {
713
+ await deleteMutation.mutateAsync(item.id)
714
+ }
715
+ ${customHandlerDecls ? '\n' + customHandlerDecls + '\n' : ''}
716
+ ${filteredItemsBlock}
717
+ const columns: DataTableColumn<${e}>[] = [
718
+ ${columnEntries},
719
+ {
720
+ key: '__actions',
721
+ label: t('${eLower}.list.actionsColumn'),
722
+ align: 'right',
723
+ render: (item) => (
724
+ <div className="flex items-center justify-end gap-1">
725
+ <PermissionGuard permission="${permKey}.update">
726
+ <button
727
+ type="button"
728
+ onClick={(e) => { e.stopPropagation(); navigate(routes.${sectionCamel}.edit(item.id)) }}
729
+ className="p-1.5 rounded hover:bg-[var(--bg-hover)] text-[var(--text-muted)] hover:text-[var(--text-primary)]"
730
+ title={t('${eLower}.list.edit')}
731
+ >
732
+ <Pencil className="w-4 h-4" />
733
+ </button>
734
+ </PermissionGuard>
735
+ <PermissionGuard permission="${permKey}.delete">
736
+ <button
737
+ type="button"
738
+ onClick={(e) => { e.stopPropagation(); void handleDelete(item) }}
739
+ className="p-1.5 rounded hover:bg-[var(--error-bg)] text-[var(--text-muted)] hover:text-[var(--error-text)]"
740
+ title={t('${eLower}.list.delete')}
741
+ >
742
+ <Trash2 className="w-4 h-4" />
743
+ </button>
744
+ </PermissionGuard>
745
+ ${customRowActionsJsx}
746
+ </div>
747
+ ),
748
+ },
749
+ ]
750
+
751
+ return (
752
+ <PermissionGuard permission="${permKey}.read">
753
+ <PageTemplate
754
+ title={t('${eLower}.list.title')}
755
+ subtitle={t('${eLower}.list.subtitle')}
756
+ icon={<List className="w-6 h-6" />}
757
+ breadcrumbs={[{ label: t('${eLower}.breadcrumb.section') }]}
758
+ actions={
759
+ <div className="flex items-center gap-2">
760
+ <PermissionGuard permission="${permKey}.create">
761
+ <button
762
+ type="button"
763
+ onClick={() => navigate(routes.${sectionCamel}.create())}
764
+ className="flex items-center gap-1.5 px-3 py-2 rounded-[var(--radius-button)] bg-[var(--color-accent-500)] text-white text-sm font-medium hover:bg-[var(--color-accent-600)] transition-colors"
765
+ >
766
+ <Plus className="w-4 h-4" />
767
+ {t('${eLower}.list.create')}
768
+ </button>
769
+ </PermissionGuard>
770
+ ${customHeaderActionsJsx}
771
+ </div>
772
+ }
773
+ >
774
+ <Slot name="${eLower}.header.actions" />
775
+
776
+ {error && (
777
+ <div className="mb-4 p-3 rounded-[var(--radius-card)] bg-[var(--error-bg)] border border-[var(--error-border)] text-sm text-[var(--error-text)]">
778
+ {t('${eLower}.list.error')}: {String(error)}
779
+ </div>
780
+ )}
781
+
782
+ ${filterBarJsx} <Slot name="${eLower}.table.columns.before" />
783
+
784
+ {isLoading ? (
785
+ <div className="flex items-center justify-center py-12 text-[var(--text-muted)]">
786
+ <Loader2 className="w-5 h-5 animate-spin mr-2" />
787
+ {t('${eLower}.list.loading')}
788
+ </div>
789
+ ) : (
790
+ <DataTable<${e}>
791
+ data={filtered}
792
+ columns={columns}
793
+ searchable
794
+ searchPlaceholder={t('${eLower}.list.search')}
795
+ pagination={{ pageSize: 20, showSizeSelector: true }}
796
+ getRowKey={(item) => item.id}
797
+ emptyMessage={t('${eLower}.list.empty')}
798
+ onRowClick={(item) => navigate(routes.${sectionCamel}.detail(item.id))}
799
+ />
800
+ )}
801
+
802
+ <Slot name="${eLower}.table.columns.after" />
803
+ </PageTemplate>
804
+ </PermissionGuard>
805
+ )
806
+ }
807
+
808
+ export default ${plural}ListPage
809
+ `,
810
+ })
811
+ }
812
+
813
+ // ─── DetailPage ──────────────────────────────────────────────────────────
814
+ if (spec.views.includes('detail')) {
815
+ // Tab support: when pageSpec.tabs[] exists, group fields into tab panels
816
+ // with URL-synced state (?tab=key). Otherwise render flat.
817
+ type TabMeta = { key: string; labelKey?: string; label?: string; fields?: string[] }
818
+ const detailTabs = (spec.pageSpec?.tabs ?? []) as TabMeta[]
819
+ const hasDetailTabs = detailTabs.length > 0
820
+
821
+ const detailExtraImport = hasDetailTabs ? `, useSearchParams` : ''
822
+
823
+ function renderFieldDl(fields: typeof spec.fields): string {
824
+ return fields.map(f => ` <div>
825
+ <dt className="text-xs font-semibold uppercase tracking-wider text-[var(--text-muted)] mb-1">
826
+ {t('${eLower}.detail.fields.${fieldToCamel(f.name)}')}
827
+ </dt>
828
+ <dd className="text-sm text-[var(--text-primary)]">
829
+ {String(data.${fieldToCamel(f.name)} ?? '')}
830
+ </dd>
831
+ </div>`).join('\n')
832
+ }
833
+
834
+ let detailTabHooks = ''
835
+ let detailBodyJsx: string
836
+
837
+ if (hasDetailTabs) {
838
+ const defaultTab = detailTabs[0].key
839
+ detailTabHooks = `
840
+ const [searchParams, setSearchParams] = useSearchParams()
841
+ const activeTab = searchParams.get('tab') ?? '${defaultTab}'
842
+ const switchTab = (tab: string) => {
843
+ const p = new URLSearchParams(searchParams)
844
+ tab === '${defaultTab}' ? p.delete('tab') : p.set('tab', tab)
845
+ setSearchParams(p, { replace: true })
846
+ }
847
+ `
848
+ const tabBarJsx = detailTabs.map(tab => {
849
+ const label = tab.labelKey ? `t('${eLower}.${tab.labelKey}')` : `'${tab.label ?? tab.key}'`
850
+ return ` <button
851
+ type="button"
852
+ onClick={() => switchTab('${tab.key}')}
853
+ className={\`px-4 py-2 text-sm font-medium border-b-2 transition-colors \${activeTab === '${tab.key}' ? 'border-[var(--color-accent-500)] text-[var(--color-accent-500)]' : 'border-transparent text-[var(--text-muted)] hover:text-[var(--text-primary)]'}\`}
854
+ >
855
+ {${label}}
856
+ </button>`
857
+ }).join('\n')
858
+
859
+ const tabPanelsJsx = detailTabs.map(tab => {
860
+ const tabFieldNames = tab.fields ?? spec.fields.map(f => f.name)
861
+ const tabFields = spec.fields.filter(f => tabFieldNames.includes(f.name))
862
+ return ` {activeTab === '${tab.key}' && (
863
+ <div className="p-6 rounded-[var(--radius-card)] border border-[var(--border-color)] bg-[var(--bg-card)]">
864
+ <dl className="grid grid-cols-1 md:grid-cols-2 gap-x-6 gap-y-4">
865
+ ${renderFieldDl(tabFields)}
866
+ </dl>
867
+ </div>
868
+ )}`
869
+ }).join('\n\n')
870
+
871
+ detailBodyJsx = ` <div className="flex gap-1 border-b border-[var(--border-color)] mb-6">
872
+ ${tabBarJsx}
873
+ </div>
874
+
875
+ ${tabPanelsJsx}`
876
+ } else {
877
+ detailBodyJsx = ` <div className="p-6 rounded-[var(--radius-card)] border border-[var(--border-color)] bg-[var(--bg-card)]">
878
+ <dl className="grid grid-cols-1 md:grid-cols-2 gap-x-6 gap-y-4">
879
+ ${renderFieldDl(spec.fields)}
880
+ </dl>
881
+ </div>`
882
+ }
883
+
884
+ // Custom header actions for detail page — rendered alongside the Edit button.
885
+ const detailHeaderActions = allCustomActions.filter(a => a.scope === 'header')
886
+ const detailApiActions = detailHeaderActions.filter(isApi)
887
+ const hasDetailActions = detailHeaderActions.length > 0
888
+
889
+ const detailCustomHookImports = detailApiActions
890
+ .map(a => `, use${toPascal(hookCodeOf(a))}${e}`)
891
+ .join('')
892
+
893
+ const detailCustomIconNames = new Set(detailHeaderActions.map(a => iconForCode(a.code)))
894
+ const detailCustomIconImports = Array.from(detailCustomIconNames).map(name => `, ${name}`).join('')
895
+
896
+ const detailCustomMutationDecls = detailApiActions
897
+ .map(a => ` const ${toCamel(hookCodeOf(a))}Mutation = use${toPascal(hookCodeOf(a))}${e}()`)
898
+ .join('\n')
899
+
900
+ const detailCustomHandlerDecls = detailHeaderActions
901
+ .map(a => {
902
+ const name = `handle${toPascal(a.code)}`
903
+ if (isNavigate(a)) {
904
+ const target = a.targetRoute ?? `routes.${sectionCamel}.list()`
905
+ return ` const ${name} = () => {\n navigate(${target})\n }` // detail actions are always header-scoped
906
+ }
907
+ const hookVar = `${toCamel(hookCodeOf(a))}Mutation`
908
+ return ` const ${name} = async () => {\n if (id) await ${hookVar}.mutateAsync(id)\n }`
909
+ })
910
+ .join('\n\n')
911
+
912
+ const detailCustomActionsJsx = hasDetailActions
913
+ ? `\n${detailHeaderActions.map(renderHeaderActionButton).join('\n')}`
914
+ : ''
915
+
916
+ files.push({
917
+ path: pathFor('detail', `${e}DetailPage.tsx`),
918
+ content: `${GENERATED_MARKER}import { useParams, useNavigate, Navigate${detailExtraImport} } from 'react-router-dom'
919
+ import { useTranslation } from 'react-i18next'
920
+ import { Loader2, Pencil, FileText${detailCustomIconImports} } from 'lucide-react'
921
+ import { Slot } from '@atlashub/smartstack'
922
+ import { PermissionGuard } from '@/components/auth/PermissionGuard'
923
+ import { PageTemplate } from '@/components/ui/PageTemplate'
924
+ import { routes } from '@/extensions/${spec.module}Routes'
925
+ import { use${e}${detailCustomHookImports} } from '${featurePath}/hooks/use${e}'
926
+
927
+ export function ${e}DetailPage() {
928
+ const { id } = useParams<{ id: string }>()
929
+ const navigate = useNavigate()
930
+ const { t } = useTranslation('${spec.module}')
931
+
932
+ if (!id) return <Navigate to=".." replace />
933
+
934
+ const { data, isLoading } = use${e}(id)
935
+ ${detailCustomMutationDecls ? detailCustomMutationDecls + '\n' : ''}${detailTabHooks}
936
+ if (isLoading) {
937
+ return (
938
+ <div className="flex items-center justify-center py-12 text-[var(--text-muted)]">
939
+ <Loader2 className="w-5 h-5 animate-spin mr-2" />
940
+ {t('${eLower}.detail.loading')}
941
+ </div>
942
+ )
943
+ }
944
+ if (!data) {
945
+ return (
946
+ <div className="p-4 rounded-[var(--radius-card)] bg-[var(--warning-bg)] border border-[var(--warning-border)] text-[var(--warning-text)]">
947
+ {t('${eLower}.detail.notFound')}
948
+ </div>
949
+ )
950
+ }
951
+
952
+ const headerTitle = String(data.${fieldToCamel(headerField?.name ?? 'id')} ?? '')
953
+ ${detailCustomHandlerDecls ? '\n' + detailCustomHandlerDecls + '\n' : ''}
954
+ return (
955
+ <PermissionGuard permission="${permKey}.read">
956
+ <PageTemplate
957
+ title={headerTitle}
958
+ icon={<FileText className="w-6 h-6" />}
959
+ breadcrumbs={[
960
+ { label: t('${eLower}.breadcrumb.section'), href: '..' },
961
+ { label: headerTitle },
962
+ ]}
963
+ actions={
964
+ <div className="flex items-center gap-2">
965
+ <PermissionGuard permission="${permKey}.update">
966
+ <button
967
+ type="button"
968
+ onClick={() => id && navigate(routes.${sectionCamel}.edit(id))}
969
+ className="flex items-center gap-1.5 px-3 py-2 rounded-[var(--radius-button)] bg-[var(--color-accent-500)] text-white text-sm font-medium hover:bg-[var(--color-accent-600)] transition-colors"
970
+ >
971
+ <Pencil className="w-4 h-4" />
972
+ {t('${eLower}.detail.edit')}
973
+ </button>
974
+ </PermissionGuard>${detailCustomActionsJsx}
975
+ </div>
976
+ }
977
+ >
978
+ <Slot name="${eLower}.detail.header" context={{ data }} />
979
+
980
+ ${detailBodyJsx}
981
+
982
+ <Slot name="${eLower}.detail.sidebar" context={{ data }} />
983
+ </PageTemplate>
984
+ </PermissionGuard>
985
+ )
986
+ }
987
+
988
+ export default ${e}DetailPage
989
+ `,
990
+ })
991
+ }
992
+
993
+ // ─── FormPage ────────────────────────────────────────────────────────────
994
+ if (spec.views.includes('form')) {
995
+ // FK fields drive the EntityLookup import + per-target lookup hook imports.
996
+ // Empty array → no extra imports, no <EntityLookup>, audit DEV-UI-022 passes
997
+ // because there's no FK to render in the first place.
998
+ const fkFormFields = formFields.filter(isFkField)
999
+ const fkLookupImport = fkFormFields.length > 0
1000
+ ? `\nimport { EntityLookup } from '@/components/ui/EntityLookup'`
1001
+ : ''
1002
+ const fkHookImports = lookupHookImportLines(fkFormFields)
1003
+ .map(line => `\n${line}`).join('')
1004
+
1005
+ // Custom header actions for the form — forms have no rows, only header scope.
1006
+ // Guarded by isEdit: you can't duplicate/archive an entity that doesn't exist yet.
1007
+ const formHeaderActions = allCustomActions.filter(a => a.scope === 'header')
1008
+ const formApiActions = formHeaderActions.filter(isApi)
1009
+ const hasFormActions = formHeaderActions.length > 0
1010
+
1011
+ const formCustomHookImports = formApiActions
1012
+ .map(a => `, use${toPascal(hookCodeOf(a))}${e}`)
1013
+ .join('')
1014
+
1015
+ const formCustomIconNames = new Set(formHeaderActions.map(a => iconForCode(a.code)))
1016
+ const formCustomIconImports = Array.from(formCustomIconNames).map(name => `, ${name}`).join('')
1017
+
1018
+ const formCustomMutationDecls = formApiActions
1019
+ .map(a => ` const ${toCamel(hookCodeOf(a))}Mutation = use${toPascal(hookCodeOf(a))}${e}()`)
1020
+ .join('\n')
1021
+
1022
+ const formCustomHandlerDecls = formHeaderActions
1023
+ .map(a => {
1024
+ const name = `handle${toPascal(a.code)}`
1025
+ if (isNavigate(a)) {
1026
+ const target = a.targetRoute ?? `routes.${sectionCamel}.list()`
1027
+ return ` const ${name} = () => {\n navigate(${target})\n }` // form actions are always header-scoped
1028
+ }
1029
+ const hookVar = `${toCamel(hookCodeOf(a))}Mutation`
1030
+ return ` const ${name} = async () => {\n if (id) await ${hookVar}.mutateAsync(id)\n }`
1031
+ })
1032
+ .join('\n\n')
1033
+
1034
+ const formActionsJsx = hasFormActions
1035
+ ? `
1036
+ actions={isEdit ? (
1037
+ <div className="flex items-center gap-2">
1038
+ ${formHeaderActions.map(renderHeaderActionButton).join('\n')}
1039
+ </div>
1040
+ ) : undefined}`
1041
+ : ''
1042
+
1043
+ files.push({
1044
+ path: pathFor('form', `${e}FormPage.tsx`),
1045
+ content: `${GENERATED_MARKER}import { useState, useEffect } from 'react'
1046
+ import type { FormEvent } from 'react'
1047
+ import { useParams, useNavigate } from 'react-router-dom'
1048
+ import { useTranslation } from 'react-i18next'
1049
+ import { Loader2, AlertTriangle, FilePen${formCustomIconImports} } from 'lucide-react'
1050
+ import { Slot } from '@atlashub/smartstack'
1051
+ import { PermissionGuard } from '@/components/auth/PermissionGuard'
1052
+ import { PageTemplate } from '@/components/ui/PageTemplate'
1053
+ import { use${e}, useCreate${e}, useUpdate${e}${formCustomHookImports} } from '${featurePath}/hooks/use${e}'${fkLookupImport}${fkHookImports}
1054
+
1055
+ type ${e}FormData = ${formDataTypeLiteral(formFields)}
1056
+
1057
+ const initial${e}FormData: ${e}FormData = ${initialFormObj}
1058
+
1059
+ export function ${e}FormPage() {
1060
+ const { id } = useParams<{ id: string }>()
1061
+ const navigate = useNavigate()
1062
+ const { t } = useTranslation('${spec.module}')
1063
+ const isEdit = !!id && id !== 'new'
1064
+ const { data: existing } = use${e}(isEdit ? id! : '')
1065
+ const createMutation = useCreate${e}()
1066
+ const updateMutation = useUpdate${e}()
1067
+ ${formCustomMutationDecls ? formCustomMutationDecls + '\n' : ''}
1068
+ const [formData, setFormData] = useState<${e}FormData>(initial${e}FormData)
1069
+ const [error, setError] = useState<string | null>(null)
1070
+
1071
+ useEffect(() => {
1072
+ if (existing) {
1073
+ setFormData(existing as ${e}FormData)
1074
+ }
1075
+ }, [existing])
1076
+
1077
+ const onChange = <K extends keyof ${e}FormData>(field: K, value: ${e}FormData[K]) => {
1078
+ setFormData(prev => ({ ...prev, [field]: value }))
1079
+ }
1080
+
1081
+ const handleSubmit = async (event: FormEvent) => {
1082
+ event.preventDefault()
1083
+ setError(null)
1084
+ try {
1085
+ if (isEdit && id) {
1086
+ await updateMutation.mutateAsync({ id, data: formData })
1087
+ } else {
1088
+ await createMutation.mutateAsync(formData)
1089
+ }
1090
+ navigate(-1)
1091
+ } catch (err) {
1092
+ setError(err instanceof Error ? err.message : String(err))
1093
+ }
1094
+ }
1095
+ ${formCustomHandlerDecls ? '\n' + formCustomHandlerDecls + '\n' : ''}
1096
+ const permission = isEdit ? '${permKey}.update' : '${permKey}.create'
1097
+ const isPending = createMutation.isPending || updateMutation.isPending
1098
+
1099
+ return (
1100
+ <PermissionGuard permission={permission}>
1101
+ <PageTemplate
1102
+ title={isEdit ? t('${eLower}.form.editTitle') : t('${eLower}.form.createTitle')}
1103
+ icon={<FilePen className="w-6 h-6" />}
1104
+ breadcrumbs={[
1105
+ { label: t('${eLower}.breadcrumb.section'), href: '..' },
1106
+ { label: isEdit ? t('${eLower}.form.editTitle') : t('${eLower}.form.createTitle') },
1107
+ ]}${formActionsJsx}
1108
+ >
1109
+ {error && (
1110
+ <div className="p-4 rounded-[var(--radius-card)] bg-[var(--error-bg)] border border-[var(--error-border)]">
1111
+ <div className="flex items-center gap-2 text-[var(--error-text)]">
1112
+ <AlertTriangle className="w-5 h-5" />
1113
+ <span>{error}</span>
1114
+ </div>
1115
+ </div>
1116
+ )}
1117
+
1118
+ <form onSubmit={handleSubmit} className="space-y-6">
1119
+ <div className="p-6 rounded-[var(--radius-card)] border border-[var(--border-color)] bg-[var(--bg-card)] space-y-4">
1120
+ <Slot name="${eLower}.form.fields.before" context={{ formData, onChange }} />
1121
+
1122
+ ${formFields.map(f => isFkField(f)
1123
+ ? renderFkLookupField(f, eLower, e)
1124
+ : renderPlainFormField(f, eLower, e)).join('\n')}
1125
+
1126
+ <Slot name="${eLower}.form.fields.after" context={{ formData, onChange }} />
1127
+ </div>
1128
+
1129
+ <div className="flex justify-end gap-2">
1130
+ <button
1131
+ type="button"
1132
+ onClick={() => navigate(-1)}
1133
+ disabled={isPending}
1134
+ className="px-6 py-2 rounded-[var(--radius-button)] bg-[var(--bg-secondary)] text-[var(--text-primary)] hover:bg-[var(--bg-hover)] disabled:opacity-50 transition-colors"
1135
+ >
1136
+ {t('${eLower}.form.cancel')}
1137
+ </button>
1138
+ <button
1139
+ type="submit"
1140
+ disabled={isPending}
1141
+ className="flex items-center gap-2 px-6 py-2 rounded-[var(--radius-button)] bg-[var(--color-accent-500)] text-white hover:bg-[var(--color-accent-600)] disabled:opacity-50 disabled:cursor-not-allowed transition-colors"
1142
+ >
1143
+ {isPending && <Loader2 className="w-4 h-4 animate-spin" />}
1144
+ {isEdit ? t('${eLower}.form.submitUpdate') : t('${eLower}.form.submitCreate')}
1145
+ </button>
1146
+ </div>
1147
+ </form>
1148
+ </PageTemplate>
1149
+ </PermissionGuard>
1150
+ )
1151
+ }
1152
+
1153
+ export default ${e}FormPage
1154
+ `,
1155
+ })
1156
+ }
1157
+
1158
+ // ─── DashboardPage ──────────────────────────────────────────────────────
1159
+ if (spec.views.includes('dashboard')) {
1160
+ files.push({
1161
+ path: pathFor('dashboard', `${e}DashboardPage.tsx`),
1162
+ content: `${GENERATED_MARKER}import { useState } from 'react'
1163
+ import { useTranslation } from 'react-i18next'
1164
+ import { Loader2, LayoutDashboard, AlertTriangle, BellRing } from 'lucide-react'
1165
+ import { Slot } from '@atlashub/smartstack'
1166
+ import { PermissionGuard } from '@/components/auth/PermissionGuard'
1167
+ import { PageTemplate } from '@/components/ui/PageTemplate'
1168
+ import { useDashboard${e} } from '${featurePath}/hooks/use${e}'
1169
+
1170
+ export function ${e}DashboardPage() {
1171
+ const { t } = useTranslation('${spec.module}')
1172
+ const [period, setPeriod] = useState<{ startDate?: string; endDate?: string }>({})
1173
+ const { consolidated, alerts, isLoading, error } = useDashboard${e}(period)
1174
+
1175
+ return (
1176
+ <PermissionGuard permission="${permKey}.read">
1177
+ <PageTemplate
1178
+ title={t('${eLower}.dashboard.title')}
1179
+ subtitle={t('${eLower}.dashboard.subtitle')}
1180
+ icon={<LayoutDashboard className="w-6 h-6" />}
1181
+ breadcrumbs={[
1182
+ { label: t('${eLower}.breadcrumb.section'), href: '..' },
1183
+ { label: t('${eLower}.dashboard.title') },
1184
+ ]}
1185
+ >
1186
+ <Slot name="${eLower}.dashboard.header" />
1187
+
1188
+ <div className="mb-4 flex items-center gap-2">
1189
+ <input
1190
+ type="date"
1191
+ value={period.startDate ?? ''}
1192
+ onChange={(event) => setPeriod((p) => ({ ...p, startDate: event.target.value || undefined }))}
1193
+ className="input"
1194
+ aria-label={t('${eLower}.dashboard.startDate')}
1195
+ />
1196
+ <input
1197
+ type="date"
1198
+ value={period.endDate ?? ''}
1199
+ onChange={(event) => setPeriod((p) => ({ ...p, endDate: event.target.value || undefined }))}
1200
+ className="input"
1201
+ aria-label={t('${eLower}.dashboard.endDate')}
1202
+ />
1203
+ </div>
1204
+
1205
+ {error && (
1206
+ <div className="mb-4 p-3 rounded-[var(--radius-card)] bg-[var(--error-bg)] border border-[var(--error-border)] text-sm text-[var(--error-text)]">
1207
+ <div className="flex items-center gap-2">
1208
+ <AlertTriangle className="w-4 h-4" />
1209
+ <span>{t('${eLower}.dashboard.error')}: {String(error)}</span>
1210
+ </div>
1211
+ </div>
1212
+ )}
1213
+
1214
+ {isLoading ? (
1215
+ <div className="flex items-center justify-center py-12 text-[var(--text-muted)]">
1216
+ <Loader2 className="w-5 h-5 animate-spin mr-2" />
1217
+ {t('${eLower}.dashboard.loading')}
1218
+ </div>
1219
+ ) : (
1220
+ <>
1221
+ <Slot name="${eLower}.dashboard.kpis.before" context={{ consolidated }} />
1222
+
1223
+ <div className="grid grid-cols-1 md:grid-cols-3 gap-4 mb-6">
1224
+ {Object.entries(consolidated?.metrics ?? {}).map(([key, value]) => (
1225
+ <div
1226
+ key={key}
1227
+ className="p-4 rounded-[var(--radius-card)] border border-[var(--border-color)] bg-[var(--bg-card)]"
1228
+ >
1229
+ <div className="text-xs font-semibold uppercase tracking-wider text-[var(--text-muted)]">
1230
+ {t(\`${eLower}.dashboard.kpi.\${key}\`)}
1231
+ </div>
1232
+ <div className="text-2xl font-semibold text-[var(--text-primary)] mt-1">
1233
+ {String(value)}
1234
+ </div>
1235
+ </div>
1236
+ ))}
1237
+ </div>
1238
+
1239
+ <Slot name="${eLower}.dashboard.kpis.after" context={{ consolidated }} />
1240
+
1241
+ <section>
1242
+ <h2 className="flex items-center gap-2 text-sm font-semibold text-[var(--text-primary)] mb-2">
1243
+ <BellRing className="w-4 h-4" />
1244
+ {t('${eLower}.dashboard.alerts.title')}
1245
+ </h2>
1246
+ {(alerts ?? []).length === 0 ? (
1247
+ <div className="p-3 rounded-[var(--radius-card)] border border-[var(--border-color)] bg-[var(--bg-card)] text-sm text-[var(--text-muted)]">
1248
+ {t('${eLower}.dashboard.alerts.empty')}
1249
+ </div>
1250
+ ) : (
1251
+ <ul className="space-y-2">
1252
+ {(alerts ?? []).map((alert) => (
1253
+ <li
1254
+ key={alert.id}
1255
+ className="p-3 rounded-[var(--radius-card)] border border-[var(--border-color)] bg-[var(--bg-card)] flex items-start justify-between gap-3"
1256
+ >
1257
+ <div>
1258
+ <div
1259
+ className="text-xs font-semibold uppercase tracking-wider"
1260
+ style={{
1261
+ color:
1262
+ alert.severity === 'critical'
1263
+ ? 'var(--error-text)'
1264
+ : alert.severity === 'warning'
1265
+ ? 'var(--warning-text)'
1266
+ : 'var(--text-muted)',
1267
+ }}
1268
+ aria-label={t(\`${eLower}.dashboard.alerts.severity.\${alert.severity}\`)}
1269
+ >
1270
+ {alert.severity}
1271
+ </div>
1272
+ <div className="text-sm text-[var(--text-primary)]">{alert.message}</div>
1273
+ </div>
1274
+ <time className="text-xs text-[var(--text-muted)] whitespace-nowrap">{alert.createdAt}</time>
1275
+ </li>
1276
+ ))}
1277
+ </ul>
1278
+ )}
1279
+ </section>
1280
+ </>
1281
+ )}
1282
+
1283
+ <Slot name="${eLower}.dashboard.footer" />
1284
+ </PageTemplate>
1285
+ </PermissionGuard>
1286
+ )
1287
+ }
1288
+
1289
+ export default ${e}DashboardPage
1290
+ `,
1291
+ })
1292
+ }
1293
+
1294
+ // ─── KanbanPage (Bug 7) ──────────────────────────────────────────────────
1295
+ // Workflow-driven view: groups entities by a status-like field into fixed
1296
+ // columns. Cards link to the detail page. Drag-and-drop is opt-in via
1297
+ // `kanbanConfig.dnd` and requires a `move` customAction on the api-client
1298
+ // side (otherwise scaffold-api-client never emits the `useMoveDemande` hook).
1299
+ if (spec.views.includes('kanban')) {
1300
+ const cfg = spec.kanbanConfig
1301
+ if (!cfg) {
1302
+ throw new Error(
1303
+ `scaffold-component: entity '${spec.entity}' lists 'kanban' in views but kanbanConfig is missing — declare it at the spec level so the generator knows which field defines the columns.`,
1304
+ )
1305
+ }
1306
+ const groupByCamel = fieldToCamel(cfg.groupBy)
1307
+ // titleField defaults to the first non-formula field that exists on the entity.
1308
+ const fallbackTitle = spec.fields.find(f => !f.formula)?.name ?? 'id'
1309
+ const titleCamel = fieldToCamel(cfg.titleField ?? fallbackTitle)
1310
+ const hasSubtitle = Boolean(cfg.subtitleField)
1311
+ const subtitleCamel = hasSubtitle ? fieldToCamel(cfg.subtitleField!) : ''
1312
+ const dnd = cfg.dnd === true
1313
+ // Column metadata baked into the page — keeps the order deterministic
1314
+ // (BA controls it, NOT the alphabetical order of grouped keys).
1315
+ const kanbanColumns = cfg.columns.map(c => ` { key: '${c.key}', labelKey: '${c.labelKey}' }`).join(',\n')
1316
+ const moveImport = dnd ? `, useMove${e}` : ''
1317
+ const moveHookDecl = dnd ? ` const moveMutation = useMove${e}()\n` : ''
1318
+ const dropHandler = dnd
1319
+ ? `
1320
+ const handleDrop = async (id: string, newStatus: string) => {
1321
+ await moveMutation.mutateAsync({ id, payload: { ${groupByCamel}: newStatus } as never })
1322
+ }`
1323
+ : ''
1324
+ const cardDragProps = dnd
1325
+ ? `
1326
+ draggable
1327
+ onDragStart={(event) => event.dataTransfer.setData('text/plain', item.id)}`
1328
+ : ''
1329
+ const columnDropProps = dnd
1330
+ ? `
1331
+ onDragOver={(event) => event.preventDefault()}
1332
+ onDrop={(event) => { void handleDrop(event.dataTransfer.getData('text/plain'), col.key) }}`
1333
+ : ''
1334
+ files.push({
1335
+ path: pathFor('kanban', `${e}KanbanPage.tsx`),
1336
+ content: `${GENERATED_MARKER}import { useMemo } from 'react'
1337
+ import { useNavigate } from 'react-router-dom'
1338
+ import { useTranslation } from 'react-i18next'
1339
+ import { Loader2, Trello } from 'lucide-react'
1340
+ import { Slot } from '@atlashub/smartstack'
1341
+ import { PermissionGuard } from '@/components/auth/PermissionGuard'
1342
+ import { PageTemplate } from '@/components/ui/PageTemplate'
1343
+ import { routes } from '@/extensions/${spec.module}Routes'
1344
+ import { use${plural}${moveImport} } from '${featurePath}/hooks/use${e}'
1345
+ import type { ${e} } from '${featurePath}/types'
1346
+
1347
+ const KANBAN_COLUMNS = [
1348
+ ${kanbanColumns}
1349
+ ] as const
1350
+
1351
+ export function ${e}KanbanPage() {
1352
+ const { t } = useTranslation('${spec.module}')
1353
+ const navigate = useNavigate()
1354
+ const { data, isLoading, error } = use${plural}()
1355
+ ${moveHookDecl}
1356
+ // Group items into buckets keyed by KANBAN_COLUMNS[i].key. Items whose
1357
+ // ${groupByCamel} does not match any declared column land in the "unassigned"
1358
+ // bucket displayed at the end. Keeps schema drift visible instead of silently
1359
+ // dropping cards.
1360
+ const buckets = useMemo(() => {
1361
+ const items = data?.items ?? []
1362
+ const map = new Map<string, ${e}[]>()
1363
+ for (const col of KANBAN_COLUMNS) map.set(col.key, [])
1364
+ const unassigned: ${e}[] = []
1365
+ for (const item of items) {
1366
+ const key = String((item as unknown as Record<string, unknown>)['${groupByCamel}'] ?? '')
1367
+ const target = map.get(key)
1368
+ if (target) target.push(item)
1369
+ else unassigned.push(item)
1370
+ }
1371
+ return { map, unassigned }
1372
+ }, [data])
1373
+ ${dropHandler}
1374
+
1375
+ return (
1376
+ <PermissionGuard permission="${permKey}.read">
1377
+ <PageTemplate
1378
+ title={t('${eLower}.kanban.title')}
1379
+ subtitle={t('${eLower}.kanban.subtitle')}
1380
+ icon={<Trello className="w-6 h-6" />}
1381
+ breadcrumbs={[
1382
+ { label: t('${eLower}.breadcrumb.section'), href: '..' },
1383
+ { label: t('${eLower}.kanban.title') },
1384
+ ]}
1385
+ >
1386
+ <Slot name="${eLower}.kanban.header" />
1387
+
1388
+ {error && (
1389
+ <div className="mb-4 p-3 rounded-[var(--radius-card)] bg-[var(--error-bg)] border border-[var(--error-border)] text-sm text-[var(--error-text)]">
1390
+ {t('${eLower}.kanban.error')}: {String(error)}
1391
+ </div>
1392
+ )}
1393
+
1394
+ {isLoading ? (
1395
+ <div className="flex items-center justify-center py-12 text-[var(--text-muted)]">
1396
+ <Loader2 className="w-5 h-5 animate-spin mr-2" />
1397
+ {t('${eLower}.kanban.loading')}
1398
+ </div>
1399
+ ) : (
1400
+ <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4">
1401
+ {KANBAN_COLUMNS.map((col) => (
1402
+ <div
1403
+ key={col.key}
1404
+ className="p-3 rounded-[var(--radius-card)] border border-[var(--border-color)] bg-[var(--bg-card)] min-h-[200px]"${columnDropProps}
1405
+ >
1406
+ <h3 className="text-sm font-semibold uppercase tracking-wider text-[var(--text-muted)] mb-3">
1407
+ {t(\`${eLower}.\${col.labelKey}\`)}
1408
+ <span className="ml-2 text-xs text-[var(--text-muted)]">
1409
+ ({buckets.map.get(col.key)?.length ?? 0})
1410
+ </span>
1411
+ </h3>
1412
+ <div className="space-y-2">
1413
+ {(buckets.map.get(col.key) ?? []).map((item) => (
1414
+ <button
1415
+ key={item.id}
1416
+ type="button"
1417
+ onClick={() => navigate(routes.${sectionCamel}.detail(item.id))}
1418
+ className="w-full text-left p-3 rounded-[var(--radius-card)] border border-[var(--border-color)] bg-[var(--bg-elevated)] hover:border-[var(--color-accent-500)] transition-colors"${cardDragProps}
1419
+ >
1420
+ <div className="text-sm font-medium text-[var(--text-primary)]">
1421
+ {String(item.${titleCamel} ?? '')}
1422
+ </div>${hasSubtitle ? `
1423
+ <div className="text-xs text-[var(--text-muted)] mt-1">
1424
+ {String(item.${subtitleCamel} ?? '')}
1425
+ </div>` : ''}
1426
+ </button>
1427
+ ))}
1428
+ </div>
1429
+ </div>
1430
+ ))}
1431
+ {buckets.unassigned.length > 0 && (
1432
+ <div className="p-3 rounded-[var(--radius-card)] border border-dashed border-[var(--warning-border)] bg-[var(--warning-bg)] min-h-[200px]">
1433
+ <h3 className="text-sm font-semibold uppercase tracking-wider text-[var(--warning-text)] mb-3">
1434
+ {t('${eLower}.kanban.unassigned')}
1435
+ <span className="ml-2 text-xs">({buckets.unassigned.length})</span>
1436
+ </h3>
1437
+ <div className="space-y-2">
1438
+ {buckets.unassigned.map((item) => (
1439
+ <button
1440
+ key={item.id}
1441
+ type="button"
1442
+ onClick={() => navigate(routes.${sectionCamel}.detail(item.id))}
1443
+ className="w-full text-left p-3 rounded-[var(--radius-card)] border border-[var(--border-color)] bg-[var(--bg-elevated)] hover:border-[var(--color-accent-500)] transition-colors"
1444
+ >
1445
+ <div className="text-sm font-medium text-[var(--text-primary)]">
1446
+ {String(item.${titleCamel} ?? '')}
1447
+ </div>
1448
+ </button>
1449
+ ))}
1450
+ </div>
1451
+ </div>
1452
+ )}
1453
+ </div>
1454
+ )}
1455
+
1456
+ <Slot name="${eLower}.kanban.footer" />
1457
+ </PageTemplate>
1458
+ </PermissionGuard>
1459
+ )
1460
+ }
1461
+
1462
+ export default ${e}KanbanPage
1463
+ `,
1464
+ })
1465
+ }
1466
+
1467
+ // ─── ReconductionPage (Bug 7) ────────────────────────────────────────────
1468
+ // Bulk-renewal pattern: shows eligible entities in a multi-select table with
1469
+ // a single "Reconduct selected" header action. Optional twin action "Refuse"
1470
+ // when reconductionConfig.withRefuse is true. The bulk hooks must exist in
1471
+ // api-client output (declare customActions `reconduct` and `refuse-reconduction`
1472
+ // with scope:'bulk' to wire the buttons).
1473
+ if (spec.views.includes('reconduction')) {
1474
+ const rcfg = spec.reconductionConfig ?? { actionLabelKey: 'reconduction.action.reconduct', withRefuse: false }
1475
+ const refuseImport = rcfg.withRefuse ? `, useRefuseReconduction${plural}` : ''
1476
+ const refuseHookDecl = rcfg.withRefuse
1477
+ ? ` const refuseMutation = useRefuseReconduction${plural}()\n`
1478
+ : ''
1479
+ const refuseHandler = rcfg.withRefuse
1480
+ ? `
1481
+ const handleRefuse = async () => {
1482
+ if (selectedIds.length === 0) return
1483
+ await refuseMutation.mutateAsync(selectedIds)
1484
+ setSelectedIds([])
1485
+ }`
1486
+ : ''
1487
+ const refuseButton = rcfg.withRefuse
1488
+ ? `
1489
+ <button
1490
+ type="button"
1491
+ onClick={() => void handleRefuse()}
1492
+ disabled={selectedIds.length === 0 || refuseMutation.isPending}
1493
+ className="px-3 py-2 rounded-[var(--radius-button)] border border-[var(--border-color)] text-sm font-medium text-[var(--text-primary)] hover:bg-[var(--bg-hover)] disabled:opacity-50"
1494
+ >
1495
+ {t('${eLower}.reconduction.action.refuse')}
1496
+ </button>`
1497
+ : ''
1498
+ files.push({
1499
+ path: pathFor('reconduction', `${e}ReconductionPage.tsx`),
1500
+ content: `${GENERATED_MARKER}import { useState } from 'react'
1501
+ import { useTranslation } from 'react-i18next'
1502
+ import { Loader2, RefreshCw } from 'lucide-react'
1503
+ import { Slot } from '@atlashub/smartstack'
1504
+ import { PermissionGuard } from '@/components/auth/PermissionGuard'
1505
+ import { PageTemplate } from '@/components/ui/PageTemplate'
1506
+ import { use${plural}, useReconduct${plural}${refuseImport} } from '${featurePath}/hooks/use${e}'
1507
+ import type { ${e} } from '${featurePath}/types'
1508
+
1509
+ export function ${e}ReconductionPage() {
1510
+ const { t } = useTranslation('${spec.module}')
1511
+ const { data, isLoading, error } = use${plural}()
1512
+ const reconductMutation = useReconduct${plural}()
1513
+ ${refuseHookDecl} const [selectedIds, setSelectedIds] = useState<string[]>([])
1514
+
1515
+ const toggle = (id: string) =>
1516
+ setSelectedIds((prev) =>
1517
+ prev.includes(id) ? prev.filter((x) => x !== id) : [...prev, id],
1518
+ )
1519
+
1520
+ const handleReconduct = async () => {
1521
+ if (selectedIds.length === 0) return
1522
+ await reconductMutation.mutateAsync(selectedIds)
1523
+ setSelectedIds([])
1524
+ }${refuseHandler}
1525
+
1526
+ const items: ${e}[] = data?.items ?? []
1527
+
1528
+ return (
1529
+ <PermissionGuard permission="${permKey}.update">
1530
+ <PageTemplate
1531
+ title={t('${eLower}.reconduction.title')}
1532
+ subtitle={t('${eLower}.reconduction.subtitle')}
1533
+ icon={<RefreshCw className="w-6 h-6" />}
1534
+ breadcrumbs={[
1535
+ { label: t('${eLower}.breadcrumb.section'), href: '..' },
1536
+ { label: t('${eLower}.reconduction.title') },
1537
+ ]}
1538
+ actions={
1539
+ <div className="flex items-center gap-2">
1540
+ <button
1541
+ type="button"
1542
+ onClick={() => void handleReconduct()}
1543
+ disabled={selectedIds.length === 0 || reconductMutation.isPending}
1544
+ className="flex items-center gap-1.5 px-3 py-2 rounded-[var(--radius-button)] bg-[var(--color-accent-500)] text-white text-sm font-medium hover:bg-[var(--color-accent-600)] disabled:opacity-50"
1545
+ >
1546
+ <RefreshCw className="w-4 h-4" />
1547
+ {t('${eLower}.${rcfg.actionLabelKey}')}
1548
+ {selectedIds.length > 0 && (
1549
+ <span className="ml-1 text-xs opacity-80">({selectedIds.length})</span>
1550
+ )}
1551
+ </button>${refuseButton}
1552
+ </div>
1553
+ }
1554
+ >
1555
+ <Slot name="${eLower}.reconduction.header" />
1556
+
1557
+ {error && (
1558
+ <div className="mb-4 p-3 rounded-[var(--radius-card)] bg-[var(--error-bg)] border border-[var(--error-border)] text-sm text-[var(--error-text)]">
1559
+ {t('${eLower}.reconduction.error')}: {String(error)}
1560
+ </div>
1561
+ )}
1562
+
1563
+ {isLoading ? (
1564
+ <div className="flex items-center justify-center py-12 text-[var(--text-muted)]">
1565
+ <Loader2 className="w-5 h-5 animate-spin mr-2" />
1566
+ {t('${eLower}.reconduction.loading')}
1567
+ </div>
1568
+ ) : items.length === 0 ? (
1569
+ <div className="p-4 rounded-[var(--radius-card)] border border-[var(--border-color)] bg-[var(--bg-card)] text-sm text-[var(--text-muted)]">
1570
+ {t('${eLower}.reconduction.empty')}
1571
+ </div>
1572
+ ) : (
1573
+ <div className="overflow-x-auto rounded-[var(--radius-card)] border border-[var(--border-color)]">
1574
+ <table className="w-full text-sm">
1575
+ <thead className="bg-[var(--bg-elevated)] text-[var(--text-muted)]">
1576
+ <tr>
1577
+ <th className="p-3 text-left w-12">
1578
+ <input
1579
+ type="checkbox"
1580
+ checked={selectedIds.length === items.length && items.length > 0}
1581
+ onChange={() =>
1582
+ setSelectedIds(selectedIds.length === items.length ? [] : items.map((x) => x.id))
1583
+ }
1584
+ aria-label={t('${eLower}.reconduction.selectAll')}
1585
+ />
1586
+ </th>
1587
+ <th className="p-3 text-left font-semibold uppercase text-xs tracking-wider">
1588
+ {t('${eLower}.reconduction.columns.identifier')}
1589
+ </th>
1590
+ </tr>
1591
+ </thead>
1592
+ <tbody>
1593
+ {items.map((item) => (
1594
+ <tr
1595
+ key={item.id}
1596
+ className="border-t border-[var(--border-color)] hover:bg-[var(--bg-hover)]"
1597
+ >
1598
+ <td className="p-3">
1599
+ <input
1600
+ type="checkbox"
1601
+ checked={selectedIds.includes(item.id)}
1602
+ onChange={() => toggle(item.id)}
1603
+ aria-label={item.id}
1604
+ />
1605
+ </td>
1606
+ <td className="p-3 text-[var(--text-primary)]">{item.id}</td>
1607
+ </tr>
1608
+ ))}
1609
+ </tbody>
1610
+ </table>
1611
+ </div>
1612
+ )}
1613
+
1614
+ <Slot name="${eLower}.reconduction.footer" />
1615
+ </PageTemplate>
1616
+ </PermissionGuard>
1617
+ )
1618
+ }
1619
+
1620
+ export default ${e}ReconductionPage
1621
+ `,
1622
+ })
1623
+ }
1624
+
1625
+ // ─── Hub pages (AppHome / ModuleHome / SectionHome) ────────────────────
1626
+ // Landing pages with concrete KPI cards + navigation links read from
1627
+ // pageSpec.widgets[] / pageSpec.quickLinks[] (enriched mode). When no
1628
+ // pageSpec data is available (legacy mode), Slot placeholders are emitted.
1629
+ // Slots are kept as EXTENSION POINTS (before/after) in both modes.
1630
+ type HubViewKind = 'app-home' | 'module-home' | 'section-home'
1631
+ type HubViewSuffix = 'AppHome' | 'ModuleHome' | 'SectionHome'
1632
+ const hubViews: Array<[HubViewKind, HubViewSuffix]> = [
1633
+ ['app-home', 'AppHome'],
1634
+ ['module-home', 'ModuleHome'],
1635
+ ['section-home', 'SectionHome'],
1636
+ ]
1637
+ for (const [view, suffix] of hubViews) {
1638
+ if (!spec.views.includes(view)) continue
1639
+ const componentName = `${plural}${suffix}Page`
1640
+
1641
+ // Extract widgets + quickLinks from pageSpec (enriched mode)
1642
+ const widgets = spec.pageSpec?.widgets ?? []
1643
+ const quickLinks = spec.pageSpec?.quickLinks ?? []
1644
+ const hasWidgets = widgets.length > 0
1645
+ const hasQuickLinks = quickLinks.length > 0
1646
+
1647
+ // Collect Lucide icon imports from the data
1648
+ const iconSet = new Set(['LayoutGrid', 'ChevronRight'])
1649
+ for (const w of widgets) {
1650
+ iconSet.add(w.icon ? kebabToPascal(w.icon) : (WIDGET_TYPE_ICONS[w.type ?? 'kpi'] ?? 'TrendingUp'))
1651
+ }
1652
+ for (const ql of quickLinks) {
1653
+ if (ql.icon) iconSet.add(kebabToPascal(ql.icon))
1654
+ }
1655
+ const iconImports = Array.from(iconSet).sort().join(', ')
1656
+
1657
+ // Build KPI card markup — concrete StatCard divs or fallback Slot
1658
+ let kpiContent: string
1659
+ if (hasWidgets) {
1660
+ const cards = widgets.map(w => {
1661
+ const icon = w.icon ? kebabToPascal(w.icon) : (WIDGET_TYPE_ICONS[w.type ?? 'kpi'] ?? 'TrendingUp')
1662
+ const label = w.labelKey ? `t('${eLower}.${w.labelKey}')` : `'${w.label ?? w.key}'`
1663
+ return ` <div className="p-4 rounded-[var(--radius-card)] border border-[var(--border-color)] bg-[var(--bg-card)]">
1664
+ <div className="flex items-center justify-between">
1665
+ <span className="text-sm font-medium text-[var(--text-muted)]">{${label}}</span>
1666
+ <${icon} className="h-5 w-5 text-[var(--color-accent-500)]" />
1667
+ </div>
1668
+ <div className="mt-2 text-2xl font-bold text-[var(--text-primary)]">—</div>
1669
+ </div>`
1670
+ })
1671
+ // Keep the Slot after concrete cards for additive extensions
1672
+ cards.push(` <Slot name="${eLower}.home.kpis" />`)
1673
+ kpiContent = cards.join('\n')
1674
+ } else {
1675
+ kpiContent = ` <Slot name="${eLower}.home.kpis" />`
1676
+ }
1677
+
1678
+ // Build QuickLink card markup — concrete nav buttons or fallback Slot
1679
+ let navContent: string
1680
+ if (hasQuickLinks) {
1681
+ const cards = quickLinks.map(ql => {
1682
+ const icon = ql.icon ? kebabToPascal(ql.icon) : 'ChevronRight'
1683
+ const label = ql.labelKey ? `t('${eLower}.${ql.labelKey}')` : `'${ql.label ?? ql.key}'`
1684
+ return ` <button
1685
+ type="button"
1686
+ onClick={() => navigate('#')}
1687
+ className="flex items-center gap-3 p-4 rounded-[var(--radius-card)] border border-[var(--border-color)] bg-[var(--bg-card)] hover:border-[var(--color-accent-500)] transition-colors text-left w-full"
1688
+ >
1689
+ <${icon} className="h-5 w-5 text-[var(--color-accent-500)] shrink-0" />
1690
+ <span className="text-sm font-medium text-[var(--text-primary)] flex-1">{${label}}</span>
1691
+ <ChevronRight className="h-4 w-4 text-[var(--text-muted)]" />
1692
+ </button>`
1693
+ })
1694
+ // Keep the Slot after concrete cards for additive extensions
1695
+ cards.push(` <Slot name="${eLower}.home.navigation" />`)
1696
+ navContent = cards.join('\n')
1697
+ } else {
1698
+ navContent = ` <Slot name="${eLower}.home.navigation" />`
1699
+ }
1700
+
1701
+ files.push({
1702
+ path: pathFor(view, `${componentName}.tsx`),
1703
+ content: `${GENERATED_MARKER}import { useNavigate } from 'react-router-dom'
1704
+ import { useTranslation } from 'react-i18next'
1705
+ import { ${iconImports} } from 'lucide-react'
1706
+ import { Slot } from '@atlashub/smartstack'
1707
+ import { PermissionGuard } from '@/components/auth/PermissionGuard'
1708
+ import { PageTemplate } from '@/components/ui/PageTemplate'
1709
+
1710
+ export function ${componentName}() {
1711
+ const { t } = useTranslation('${spec.module}')
1712
+ const navigate = useNavigate()
1713
+
1714
+ return (
1715
+ <PermissionGuard permission="${permKey}.read">
1716
+ <PageTemplate
1717
+ title={t('${eLower}.home.title')}
1718
+ subtitle={t('${eLower}.home.subtitle')}
1719
+ icon={<LayoutGrid className="w-6 h-6" />}
1720
+ >
1721
+ <Slot name="${eLower}.home.header" />
1722
+
1723
+ <section className="mb-6">
1724
+ <h2 className="text-sm font-semibold uppercase tracking-wider text-[var(--text-muted)] mb-3">
1725
+ {t('${eLower}.home.kpis')}
1726
+ </h2>
1727
+ <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4">
1728
+ ${kpiContent}
1729
+ </div>
1730
+ </section>
1731
+
1732
+ <Slot name="${eLower}.home.kpis.after" />
1733
+
1734
+ <section>
1735
+ <h2 className="text-sm font-semibold uppercase tracking-wider text-[var(--text-muted)] mb-3">
1736
+ {t('${eLower}.home.navigation')}
1737
+ </h2>
1738
+ <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
1739
+ ${navContent}
1740
+ </div>
1741
+ </section>
1742
+
1743
+ <Slot name="${eLower}.home.footer" />
1744
+ </PageTemplate>
1745
+ </PermissionGuard>
1746
+ )
1747
+ }
1748
+
1749
+ export default ${componentName}
1750
+ `,
1751
+ })
1752
+ }
1753
+
1754
+ // ─── i18n JSON per locale ────────────────────────────────────────────────
1755
+ // Enriched mode: pageSpec.i18nKeys is the source of truth (parallel key sets
1756
+ // across the 4 locales, validated upstream by persist-prd). Flat keys get
1757
+ // materialised to the nested form i18next consumes.
1758
+ // Legacy mode: buildTranslations() infers from the field dictionary.
1759
+ // i18n catalogues land at `src/i18n/locales/{locale}/{module}.json` —
1760
+ // module-level (one JSON per module, NOT per entity), matching the
1761
+ // SmartStack project layout where i18n config.ts loads namespaces by
1762
+ // module. The CLI writer (index.ts) merges with the existing JSON if
1763
+ // any so the 132 keys already present (custom common/list/form/...)
1764
+ // are preserved.
1765
+ //
1766
+ // For hub views (app-home/module-home/section-home), the template always
1767
+ // references `home.title`, `home.subtitle`, `home.kpis`, `home.navigation`.
1768
+ // PRD pagespecs may omit these — inject them as fallbacks so the page
1769
+ // never shows raw i18n keys.
1770
+ const isHubView = spec.views.some(v => v === 'app-home' || v === 'module-home' || v === 'section-home')
1771
+ for (const locale of LOCALES) {
1772
+ let i18nFlat: Record<string, string>
1773
+ if (spec.pageSpec) {
1774
+ i18nFlat = normalizeI18nFieldKeys({ ...(spec.pageSpec.i18nKeys[locale as keyof typeof spec.pageSpec.i18nKeys] ?? {}) })
1775
+ // Inject missing home keys from the legacy builder
1776
+ if (isHubView) {
1777
+ const legacyHome = buildTranslations(spec, locale as Locale).home as Record<string, string> | undefined
1778
+ if (legacyHome) {
1779
+ for (const [k, v] of Object.entries(legacyHome)) {
1780
+ const flatKey = `home.${k}`
1781
+ if (!i18nFlat[flatKey]) i18nFlat[flatKey] = v
1782
+ }
1783
+ }
1784
+ }
1785
+ } else {
1786
+ i18nFlat = {}
1787
+ }
1788
+ const innerPayload = spec.pageSpec
1789
+ ? flatToNested(i18nFlat)
1790
+ : buildTranslations(spec, locale as Locale)
1791
+ // Wrap under entity key so multiple entities in the same module
1792
+ // coexist without collision: { contact: { list: {...} }, company: { list: {...} } }
1793
+ const payload = { [eLower]: innerPayload }
1794
+ files.push({
1795
+ path: `${webRoot}/src/i18n/locales/${locale}/${spec.module}.json`,
1796
+ content: JSON.stringify(payload, null, 2) + '\n',
1797
+ })
1798
+ }
1799
+
1800
+ return files
1801
+ }
1802
+
1803
+ function toHtmlInputType(type: string): string {
1804
+ switch (type.toLowerCase()) {
1805
+ case 'number': case 'int': case 'integer': case 'decimal': return 'number'
1806
+ case 'bool': case 'boolean': return 'checkbox'
1807
+ case 'datetime': return 'datetime-local'
1808
+ case 'date': return 'date'
1809
+ default: return 'text'
1810
+ }
1811
+ }
1812
+
1813
+ function fromInputValue(type: string, expr: string): string {
1814
+ switch (type.toLowerCase()) {
1815
+ case 'number': case 'int': case 'integer': case 'decimal': return `Number(${expr})`
1816
+ case 'bool': case 'boolean': return `(event.target as HTMLInputElement).checked`
1817
+ default: return expr
1818
+ }
1819
+ }
1820
+
1821
+ // ─── FK lookup helpers — power <EntityLookup> emission on Form pages ──────
1822
+
1823
+ /**
1824
+ * A field is a FK when the orchestrator (ba-develop Phase 3a) has resolved
1825
+ * `fkTo` for it. The shape check is conservative: even if scaffold-component
1826
+ * is called legacy-mode without `fkTo` populated, the field still falls back
1827
+ * to a plain `<input>` and audit DEV-UI-022 catches the regression.
1828
+ */
1829
+ function isFkField(f: ComponentField): boolean {
1830
+ return Boolean(f.fkTo)
1831
+ }
1832
+
1833
+ /** PascalCase → kebab-case plural: `Department` → `departments`. */
1834
+ function lookupPluralKebab(entity: string): string {
1835
+ const plural = entity.endsWith('s') ? entity : `${entity}s`
1836
+ return plural
1837
+ .replace(/([a-z0-9])([A-Z])/g, '$1-$2')
1838
+ .replace(/_+/g, '-')
1839
+ .toLowerCase()
1840
+ }
1841
+
1842
+ /** Default endpoint when `fkTo.apiEndpoint` isn't overridden by the caller. */
1843
+ function defaultLookupEndpoint(fkTo: NonNullable<ComponentField['fkTo']>): string {
1844
+ if (fkTo.apiEndpoint) return fkTo.apiEndpoint
1845
+ return `/api/${fkTo.module}/${lookupPluralKebab(fkTo.entity)}/lookup`
1846
+ }
1847
+
1848
+ /**
1849
+ * Build the conditional `import { useXxxLookup } from '@/features/.../...'`
1850
+ * lines for the Form/Detail pages. One line per unique target entity.
1851
+ * Empty array → emit nothing.
1852
+ */
1853
+ function lookupHookImportLines(fkFields: ComponentField[]): string[] {
1854
+ const seen = new Map<string, string>()
1855
+ for (const f of fkFields) {
1856
+ if (!f.fkTo) continue
1857
+ const key = `${f.fkTo.module}/${f.fkTo.entity}`
1858
+ if (seen.has(key)) continue
1859
+ const targetLower = f.fkTo.entity.charAt(0).toLowerCase() + f.fkTo.entity.slice(1)
1860
+ seen.set(
1861
+ key,
1862
+ `import { use${f.fkTo.entity}Lookup } from '@/features/${f.fkTo.module}/${targetLower}/hooks/use${f.fkTo.entity}'`,
1863
+ )
1864
+ }
1865
+ return [...seen.values()]
1866
+ }
1867
+
1868
+ /**
1869
+ * Emit the JSX for ONE form field when it carries `fkTo`. Wraps
1870
+ * <EntityLookup> — no `<input>`, no `<select>`, no datalist. The FK value is
1871
+ * persisted as a string (Guid) in formData; the onChange contract matches the
1872
+ * generated `<E>FormData` field type (string | null).
1873
+ */
1874
+ function renderFkLookupField(f: ComponentField, eLower: string, e: string): string {
1875
+ if (!f.fkTo) throw new Error(`renderFkLookupField called on non-FK field: ${f.name}`)
1876
+ const camelName = fieldToCamel(f.name)
1877
+ const endpoint = defaultLookupEndpoint(f.fkTo)
1878
+ return ` <EntityLookup
1879
+ apiEndpoint="${endpoint}"
1880
+ value={(formData.${camelName} as string | null) ?? null}
1881
+ onChange={(id) => onChange('${camelName}', (id ?? '') as ${e}FormData['${camelName}'])}
1882
+ label={t('${eLower}.form.fields.${camelName}')}
1883
+ ${f.required ? 'required' : ''}
1884
+ disabled={isPending}
1885
+ />`
1886
+ }
1887
+
1888
+ /**
1889
+ * Emit the JSX for ONE non-FK form field — the legacy `<input>` path. Mirrors
1890
+ * the historical inline template verbatim so the diff stays minimal when
1891
+ * pages mix FK and non-FK fields.
1892
+ */
1893
+ function renderPlainFormField(f: ComponentField, eLower: string, e: string): string {
1894
+ const camelName = fieldToCamel(f.name)
1895
+ const isBool = /^bool(ean)?$/i.test(f.type)
1896
+ if (isBool) {
1897
+ return ` <label htmlFor="${camelName}" className="flex items-center gap-2 text-sm font-medium text-[var(--text-primary)]">
1898
+ <input
1899
+ id="${camelName}"
1900
+ type="checkbox"
1901
+ checked={!!formData.${camelName}}
1902
+ onChange={event => onChange('${camelName}', (event.target as HTMLInputElement).checked as ${e}FormData['${camelName}'])}
1903
+ disabled={isPending}
1904
+ className="rounded border-[var(--border-color)]"
1905
+ />
1906
+ {t('${eLower}.form.fields.${camelName}')}
1907
+ </label>`
1908
+ }
1909
+ return ` <div>
1910
+ <label htmlFor="${camelName}" className="block text-sm font-medium text-[var(--text-primary)] mb-1">
1911
+ {t('${eLower}.form.fields.${camelName}')}${f.required ? ` <span className="text-[var(--error-text)]">*</span>` : ''}
1912
+ </label>
1913
+ <input
1914
+ id="${camelName}"
1915
+ type="${toHtmlInputType(f.type)}"
1916
+ value={String(formData.${camelName} ?? '')}
1917
+ onChange={event => onChange('${camelName}', ${fromInputValue(f.type, `event.target.value`)} as ${e}FormData['${camelName}'])}
1918
+ ${f.required ? 'required' : ''}
1919
+ disabled={isPending}
1920
+ className="input w-full disabled:opacity-50"
1921
+ />
1922
+ </div>`
1923
+ }