create-flowdular 0.2.3 → 0.2.5

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 (271) hide show
  1. package/README.md +11 -0
  2. package/agent-template/.agents/skills/agent-tool-design/SKILL.md +203 -0
  3. package/agent-template/.agents/skills/auth-security-review/SKILL.md +90 -0
  4. package/agent-template/.agents/skills/auto-review/SKILL.md +103 -0
  5. package/agent-template/.agents/skills/bug-hunt/SKILL.md +104 -0
  6. package/agent-template/.agents/skills/business-agent-design/SKILL.md +182 -0
  7. package/agent-template/.agents/skills/cli-extension/SKILL.md +108 -0
  8. package/agent-template/.agents/skills/core-extend/SKILL.md +99 -0
  9. package/agent-template/.agents/skills/database-adapter/SKILL.md +198 -0
  10. package/agent-template/.agents/skills/database-adapter/references/first-run-and-matrix.md +105 -0
  11. package/agent-template/.agents/skills/migration-authoring/SKILL.md +161 -0
  12. package/agent-template/.agents/skills/module-new/SKILL.md +171 -0
  13. package/agent-template/.agents/skills/module-update/SKILL.md +91 -0
  14. package/agent-template/.agents/skills/perf-audit/SKILL.md +98 -0
  15. package/agent-template/.agents/skills/release-eject-pr/SKILL.md +107 -0
  16. package/agent-template/.agents/skills/spec-approval/SKILL.md +106 -0
  17. package/agent-template/.agents/skills/test-hardening/SKILL.md +79 -0
  18. package/agent-template/.agents/skills/translations-i18n/SKILL.md +78 -0
  19. package/agent-template/.agents/skills/ux-design/SKILL.md +92 -0
  20. package/agent-template/.agents/skills/variables/SKILL.md +156 -0
  21. package/agent-template/.agents/skills/workflow-development/SKILL.md +192 -0
  22. package/agent-template/.ai/README.md +62 -0
  23. package/agent-template/.ai/agents/README.md +27 -0
  24. package/agent-template/.ai/agents/module-executor.md +36 -0
  25. package/agent-template/.ai/agents/reviewer.md +23 -0
  26. package/agent-template/.ai/agents/sandbox/agentic-engineer.md +31 -0
  27. package/agent-template/.ai/agents/sandbox/backend-engineer.md +36 -0
  28. package/agent-template/.ai/agents/sandbox/business-manager.md +23 -0
  29. package/agent-template/.ai/agents/sandbox/frontend-engineer.md +27 -0
  30. package/agent-template/.ai/agents/sandbox/ux-designer.md +23 -0
  31. package/agent-template/.ai/agents/spec-author.md +29 -0
  32. package/agent-template/.ai/blueprints/add-migration/README.md +5 -0
  33. package/agent-template/.ai/blueprints/add-migration/allowed-paths.yaml +23 -0
  34. package/agent-template/.ai/blueprints/add-migration/blueprint.json +14 -0
  35. package/agent-template/.ai/blueprints/add-migration/examples/invalid/input-destructive.json +6 -0
  36. package/agent-template/.ai/blueprints/add-migration/examples/invalid/plan-unnumbered-file.json +9 -0
  37. package/agent-template/.ai/blueprints/add-migration/examples/valid/input.json +6 -0
  38. package/agent-template/.ai/blueprints/add-migration/examples/valid/plan.json +9 -0
  39. package/agent-template/.ai/blueprints/add-migration/gates.yaml +30 -0
  40. package/agent-template/.ai/blueprints/add-migration/input.schema.json +23 -0
  41. package/agent-template/.ai/blueprints/add-migration/plan.schema.json +54 -0
  42. package/agent-template/.ai/blueprints/add-migration/required-files.yaml +18 -0
  43. package/agent-template/.ai/blueprints/add-migration/spec-requirements.yaml +13 -0
  44. package/agent-template/.ai/blueprints/add-migration/steps.yaml +62 -0
  45. package/agent-template/.ai/blueprints/author-spec/README.md +5 -0
  46. package/agent-template/.ai/blueprints/author-spec/allowed-paths.yaml +7 -0
  47. package/agent-template/.ai/blueprints/author-spec/blueprint.json +14 -0
  48. package/agent-template/.ai/blueprints/author-spec/examples/invalid/input-missing-outcome.json +5 -0
  49. package/agent-template/.ai/blueprints/author-spec/examples/valid/input.json +6 -0
  50. package/agent-template/.ai/blueprints/author-spec/gates.yaml +13 -0
  51. package/agent-template/.ai/blueprints/author-spec/input.schema.json +20 -0
  52. package/agent-template/.ai/blueprints/author-spec/plan.schema.json +14 -0
  53. package/agent-template/.ai/blueprints/author-spec/required-files.yaml +6 -0
  54. package/agent-template/.ai/blueprints/author-spec/spec-requirements.yaml +35 -0
  55. package/agent-template/.ai/blueprints/author-spec/steps.yaml +28 -0
  56. package/agent-template/.ai/blueprints/author-spec/templates/module.yaml +45 -0
  57. package/agent-template/.ai/blueprints/bug-fix/README.md +5 -0
  58. package/agent-template/.ai/blueprints/bug-fix/allowed-paths.yaml +29 -0
  59. package/agent-template/.ai/blueprints/bug-fix/blueprint.json +14 -0
  60. package/agent-template/.ai/blueprints/bug-fix/examples/invalid/input-no-symptom.json +4 -0
  61. package/agent-template/.ai/blueprints/bug-fix/examples/invalid/plan-no-test.json +8 -0
  62. package/agent-template/.ai/blueprints/bug-fix/examples/valid/input.json +6 -0
  63. package/agent-template/.ai/blueprints/bug-fix/examples/valid/plan.json +8 -0
  64. package/agent-template/.ai/blueprints/bug-fix/gates.yaml +30 -0
  65. package/agent-template/.ai/blueprints/bug-fix/input.schema.json +25 -0
  66. package/agent-template/.ai/blueprints/bug-fix/plan.schema.json +53 -0
  67. package/agent-template/.ai/blueprints/bug-fix/required-files.yaml +7 -0
  68. package/agent-template/.ai/blueprints/bug-fix/spec-requirements.yaml +7 -0
  69. package/agent-template/.ai/blueprints/bug-fix/steps.yaml +51 -0
  70. package/agent-template/.ai/blueprints/core-extend/README.md +5 -0
  71. package/agent-template/.ai/blueprints/core-extend/allowed-paths.yaml +49 -0
  72. package/agent-template/.ai/blueprints/core-extend/blueprint.json +14 -0
  73. package/agent-template/.ai/blueprints/core-extend/examples/invalid/input-unknown-package.json +5 -0
  74. package/agent-template/.ai/blueprints/core-extend/examples/invalid/plan-missing-gates.json +7 -0
  75. package/agent-template/.ai/blueprints/core-extend/examples/valid/input.json +6 -0
  76. package/agent-template/.ai/blueprints/core-extend/examples/valid/plan.json +10 -0
  77. package/agent-template/.ai/blueprints/core-extend/gates.yaml +16 -0
  78. package/agent-template/.ai/blueprints/core-extend/input.schema.json +54 -0
  79. package/agent-template/.ai/blueprints/core-extend/plan.schema.json +39 -0
  80. package/agent-template/.ai/blueprints/core-extend/required-files.yaml +36 -0
  81. package/agent-template/.ai/blueprints/core-extend/spec-requirements.yaml +17 -0
  82. package/agent-template/.ai/blueprints/core-extend/steps.yaml +54 -0
  83. package/agent-template/.ai/blueprints/edit-module/README.md +9 -0
  84. package/agent-template/.ai/blueprints/edit-module/allowed-paths.yaml +27 -0
  85. package/agent-template/.ai/blueprints/edit-module/blueprint.json +20 -0
  86. package/agent-template/.ai/blueprints/edit-module/examples/invalid/input-unknown-change.json +5 -0
  87. package/agent-template/.ai/blueprints/edit-module/examples/invalid/plan-touches-platform.json +15 -0
  88. package/agent-template/.ai/blueprints/edit-module/examples/valid/input.json +5 -0
  89. package/agent-template/.ai/blueprints/edit-module/examples/valid/plan.json +25 -0
  90. package/agent-template/.ai/blueprints/edit-module/gates.yaml +30 -0
  91. package/agent-template/.ai/blueprints/edit-module/input.schema.json +31 -0
  92. package/agent-template/.ai/blueprints/edit-module/plan.schema.json +65 -0
  93. package/agent-template/.ai/blueprints/edit-module/required-files.yaml +80 -0
  94. package/agent-template/.ai/blueprints/edit-module/spec-requirements.yaml +15 -0
  95. package/agent-template/.ai/blueprints/edit-module/steps.yaml +115 -0
  96. package/agent-template/.ai/blueprints/new-module/README.md +7 -0
  97. package/agent-template/.ai/blueprints/new-module/allowed-paths.yaml +27 -0
  98. package/agent-template/.ai/blueprints/new-module/blueprint.json +20 -0
  99. package/agent-template/.ai/blueprints/new-module/examples/invalid/input-spec-outside-modules.json +4 -0
  100. package/agent-template/.ai/blueprints/new-module/examples/invalid/plan-unknown-gate.json +8 -0
  101. package/agent-template/.ai/blueprints/new-module/examples/valid/input.json +5 -0
  102. package/agent-template/.ai/blueprints/new-module/examples/valid/plan.json +22 -0
  103. package/agent-template/.ai/blueprints/new-module/gates.yaml +30 -0
  104. package/agent-template/.ai/blueprints/new-module/input.schema.json +21 -0
  105. package/agent-template/.ai/blueprints/new-module/plan.schema.json +58 -0
  106. package/agent-template/.ai/blueprints/new-module/required-files.yaml +73 -0
  107. package/agent-template/.ai/blueprints/new-module/spec-requirements.yaml +30 -0
  108. package/agent-template/.ai/blueprints/new-module/steps.yaml +138 -0
  109. package/agent-template/.ai/blueprints/release/README.md +5 -0
  110. package/agent-template/.ai/blueprints/release/allowed-paths.yaml +19 -0
  111. package/agent-template/.ai/blueprints/release/blueprint.json +14 -0
  112. package/agent-template/.ai/blueprints/release/examples/invalid/input-bad-version.json +4 -0
  113. package/agent-template/.ai/blueprints/release/examples/invalid/plan-bad-branch.json +7 -0
  114. package/agent-template/.ai/blueprints/release/examples/valid/input.json +5 -0
  115. package/agent-template/.ai/blueprints/release/examples/valid/plan.json +20 -0
  116. package/agent-template/.ai/blueprints/release/gates.yaml +20 -0
  117. package/agent-template/.ai/blueprints/release/input.schema.json +24 -0
  118. package/agent-template/.ai/blueprints/release/plan.schema.json +46 -0
  119. package/agent-template/.ai/blueprints/release/required-files.yaml +19 -0
  120. package/agent-template/.ai/blueprints/release/spec-requirements.yaml +8 -0
  121. package/agent-template/.ai/blueprints/release/steps.yaml +47 -0
  122. package/agent-template/.ai/blueprints/security-review/README.md +5 -0
  123. package/agent-template/.ai/blueprints/security-review/allowed-paths.yaml +6 -0
  124. package/agent-template/.ai/blueprints/security-review/blueprint.json +14 -0
  125. package/agent-template/.ai/blueprints/security-review/examples/invalid/input-unknown-kind.json +4 -0
  126. package/agent-template/.ai/blueprints/security-review/examples/invalid/plan-finding-without-scenario.json +14 -0
  127. package/agent-template/.ai/blueprints/security-review/examples/valid/input.json +4 -0
  128. package/agent-template/.ai/blueprints/security-review/examples/valid/plan.json +19 -0
  129. package/agent-template/.ai/blueprints/security-review/gates.yaml +22 -0
  130. package/agent-template/.ai/blueprints/security-review/input.schema.json +20 -0
  131. package/agent-template/.ai/blueprints/security-review/plan.schema.json +65 -0
  132. package/agent-template/.ai/blueprints/security-review/required-files.yaml +6 -0
  133. package/agent-template/.ai/blueprints/security-review/spec-requirements.yaml +9 -0
  134. package/agent-template/.ai/blueprints/security-review/steps.yaml +38 -0
  135. package/agent-template/.ai/examples/README.md +8 -0
  136. package/agent-template/.ai/examples/bad/client-imports-server/README.md +20 -0
  137. package/agent-template/.ai/examples/bad/client-imports-server/api.ts +12 -0
  138. package/agent-template/.ai/examples/bad/missing-acl/README.md +23 -0
  139. package/agent-template/.ai/examples/bad/missing-acl/endpoints.ts +12 -0
  140. package/agent-template/.ai/examples/bad/tenant-from-body/README.md +19 -0
  141. package/agent-template/.ai/examples/bad/tenant-from-body/endpoints.ts +33 -0
  142. package/agent-template/.ai/examples/client-contribution/CustomerListView.tsrx +34 -0
  143. package/agent-template/.ai/examples/client-contribution/README.md +11 -0
  144. package/agent-template/.ai/examples/client-contribution/contribution.tsrx +48 -0
  145. package/agent-template/.ai/examples/client-contribution/index.ts +20 -0
  146. package/agent-template/.ai/examples/client-contribution/permissions.ts +8 -0
  147. package/agent-template/.ai/examples/customer-cli-extension/README.md +14 -0
  148. package/agent-template/.ai/examples/customer-cli-extension/commands.json +17 -0
  149. package/agent-template/.ai/examples/customer-cli-extension/index.ts +36 -0
  150. package/agent-template/.ai/examples/module-create/task-packet.json +11 -0
  151. package/agent-template/.ai/guides/application-development.md +97 -0
  152. package/agent-template/.ai/policies/capabilities.yaml +164 -0
  153. package/agent-template/.ai/policies/model-routing.yaml +72 -0
  154. package/agent-template/.ai/policies/path-ownership.yaml +65 -0
  155. package/agent-template/.ai/policies/task-budgets.yaml +37 -0
  156. package/agent-template/.ai/references/catalog/LICENSE +21 -0
  157. package/agent-template/.ai/references/catalog/migrations/0001_catalog_core.down.sql +2 -0
  158. package/agent-template/.ai/references/catalog/migrations/0001_catalog_core.up.sql +21 -0
  159. package/agent-template/.ai/references/catalog/migrations/0002_catalog_history.down.sql +3 -0
  160. package/agent-template/.ai/references/catalog/migrations/0002_catalog_history.up.sql +20 -0
  161. package/agent-template/.ai/references/catalog/migrations/0003_catalog_history_service_actors.down.sql +3 -0
  162. package/agent-template/.ai/references/catalog/migrations/0003_catalog_history_service_actors.up.sql +36 -0
  163. package/agent-template/.ai/references/catalog/migrations/0004_catalog_idempotency_ledger.down.sql +3 -0
  164. package/agent-template/.ai/references/catalog/migrations/0004_catalog_idempotency_ledger.up.sql +19 -0
  165. package/agent-template/.ai/references/catalog/migrations/README.md +3 -0
  166. package/agent-template/.ai/references/catalog/module.json +27 -0
  167. package/agent-template/.ai/references/catalog/package.json +49 -0
  168. package/agent-template/.ai/references/catalog/spec/module.yaml +86 -0
  169. package/agent-template/.ai/references/catalog/src/acl/permissions.ts +6 -0
  170. package/agent-template/.ai/references/catalog/src/agent/tools.ts +164 -0
  171. package/agent-template/.ai/references/catalog/src/api/endpoints.ts +243 -0
  172. package/agent-template/.ai/references/catalog/src/client/CatalogHistoryDrawer.tsrx +123 -0
  173. package/agent-template/.ai/references/catalog/src/client/CatalogItemForm.tsrx +190 -0
  174. package/agent-template/.ai/references/catalog/src/client/CatalogView.tsrx +473 -0
  175. package/agent-template/.ai/references/catalog/src/client/api.ts +111 -0
  176. package/agent-template/.ai/references/catalog/src/client/contribution.tsrx +61 -0
  177. package/agent-template/.ai/references/catalog/src/client/index.ts +18 -0
  178. package/agent-template/.ai/references/catalog/src/client/navigation-copy.ts +9 -0
  179. package/agent-template/.ai/references/catalog/src/client/state.ts +24 -0
  180. package/agent-template/.ai/references/catalog/src/domain/types.ts +32 -0
  181. package/agent-template/.ai/references/catalog/src/domain/variables.ts +111 -0
  182. package/agent-template/.ai/references/catalog/src/index.ts +31 -0
  183. package/agent-template/.ai/references/catalog/src/platform.ts +35 -0
  184. package/agent-template/.ai/references/catalog/src/server/index.ts +4 -0
  185. package/agent-template/.ai/references/catalog/src/server/runtime.ts +86 -0
  186. package/agent-template/.ai/references/catalog/src/services/catalog-service.ts +306 -0
  187. package/agent-template/.ai/references/catalog/src/services/database-repository.ts +440 -0
  188. package/agent-template/.ai/references/catalog/src/services/index.ts +4 -0
  189. package/agent-template/.ai/references/catalog/src/services/migration.ts +171 -0
  190. package/agent-template/.ai/references/catalog/src/services/repository.ts +36 -0
  191. package/agent-template/.ai/references/catalog/src/services/target-idempotency.ts +59 -0
  192. package/agent-template/.ai/references/catalog/tests/agent-tools.test.ts +277 -0
  193. package/agent-template/.ai/references/catalog/tests/endpoints.test.ts +320 -0
  194. package/agent-template/.ai/references/catalog/tests/idempotency.test.ts +297 -0
  195. package/agent-template/.ai/references/catalog/tests/migrations.test.ts +149 -0
  196. package/agent-template/.ai/references/catalog/tests/module.test.ts +271 -0
  197. package/agent-template/.ai/references/catalog/tests/support/database.ts +76 -0
  198. package/agent-template/.ai/references/catalog/translations/en.json +101 -0
  199. package/agent-template/.ai/references/catalog/translations/pl.json +101 -0
  200. package/agent-template/.ai/references/catalog/tsconfig.json +15 -0
  201. package/agent-template/.ai/references/catalog/vitest.config.ts +16 -0
  202. package/agent-template/.ai/references/catalog.provenance.json +55 -0
  203. package/agent-template/.ai/rules/flowdular.md +86 -0
  204. package/agent-template/.ai/skills/README.md +36 -0
  205. package/agent-template/.ai/skills/agent-tool-design/SKILL.md +209 -0
  206. package/agent-template/.ai/skills/auth-security-review/SKILL.md +96 -0
  207. package/agent-template/.ai/skills/auto-review/SKILL.md +112 -0
  208. package/agent-template/.ai/skills/bug-hunt/SKILL.md +110 -0
  209. package/agent-template/.ai/skills/business-agent-design/SKILL.md +188 -0
  210. package/agent-template/.ai/skills/cli-extension/SKILL.md +114 -0
  211. package/agent-template/.ai/skills/core-extend/SKILL.md +104 -0
  212. package/agent-template/.ai/skills/database-adapter/SKILL.md +204 -0
  213. package/agent-template/.ai/skills/database-adapter/references/first-run-and-matrix.md +105 -0
  214. package/agent-template/.ai/skills/migration-authoring/SKILL.md +167 -0
  215. package/agent-template/.ai/skills/module-new/SKILL.md +180 -0
  216. package/agent-template/.ai/skills/module-update/SKILL.md +100 -0
  217. package/agent-template/.ai/skills/perf-audit/SKILL.md +105 -0
  218. package/agent-template/.ai/skills/release-eject-pr/SKILL.md +113 -0
  219. package/agent-template/.ai/skills/spec-approval/SKILL.md +112 -0
  220. package/agent-template/.ai/skills/test-hardening/SKILL.md +86 -0
  221. package/agent-template/.ai/skills/translations-i18n/SKILL.md +85 -0
  222. package/agent-template/.ai/skills/ux-design/SKILL.md +97 -0
  223. package/agent-template/.ai/skills/variables/SKILL.md +164 -0
  224. package/agent-template/.ai/skills/workflow-development/SKILL.md +199 -0
  225. package/agent-template/.claude/skills/agent-tool-design/SKILL.md +203 -0
  226. package/agent-template/.claude/skills/auth-security-review/SKILL.md +90 -0
  227. package/agent-template/.claude/skills/auto-review/SKILL.md +103 -0
  228. package/agent-template/.claude/skills/bug-hunt/SKILL.md +104 -0
  229. package/agent-template/.claude/skills/business-agent-design/SKILL.md +182 -0
  230. package/agent-template/.claude/skills/cli-extension/SKILL.md +108 -0
  231. package/agent-template/.claude/skills/core-extend/SKILL.md +99 -0
  232. package/agent-template/.claude/skills/database-adapter/SKILL.md +198 -0
  233. package/agent-template/.claude/skills/database-adapter/references/first-run-and-matrix.md +105 -0
  234. package/agent-template/.claude/skills/migration-authoring/SKILL.md +161 -0
  235. package/agent-template/.claude/skills/module-new/SKILL.md +171 -0
  236. package/agent-template/.claude/skills/module-update/SKILL.md +91 -0
  237. package/agent-template/.claude/skills/perf-audit/SKILL.md +98 -0
  238. package/agent-template/.claude/skills/release-eject-pr/SKILL.md +107 -0
  239. package/agent-template/.claude/skills/spec-approval/SKILL.md +106 -0
  240. package/agent-template/.claude/skills/test-hardening/SKILL.md +79 -0
  241. package/agent-template/.claude/skills/translations-i18n/SKILL.md +78 -0
  242. package/agent-template/.claude/skills/ux-design/SKILL.md +92 -0
  243. package/agent-template/.claude/skills/variables/SKILL.md +156 -0
  244. package/agent-template/.claude/skills/workflow-development/SKILL.md +192 -0
  245. package/agent-template/AGENTS.md +77 -0
  246. package/agent-template/CLAUDE.md +77 -0
  247. package/agent-template/docs/adr/0001-development-reload.md +16 -0
  248. package/agent-template/docs/adr/0002-durable-agent-execution.md +21 -0
  249. package/agent-template/docs/adr/0003-module-settings.md +22 -0
  250. package/agent-template/docs/adr/0004-enterprise-access-and-audit.md +36 -0
  251. package/agent-template/docs/adr/0005-sandbox-runtime-and-coding-agents.md +81 -0
  252. package/agent-template/docs/adr/0006-agentic-workflows.md +1702 -0
  253. package/agent-template/docs/adr/0007-module-owned-agents.md +429 -0
  254. package/agent-template/docs/adr/0008-database-adapter-contract.md +90 -0
  255. package/agent-template/docs/agent-contract.md +45 -0
  256. package/agent-template/docs/configuration.md +122 -0
  257. package/agent-template/docs/database-adapters.md +346 -0
  258. package/agent-template/docs/design-system.md +217 -0
  259. package/agent-template/docs/modules.md +146 -0
  260. package/agent-template/platform/scripts/build.mjs +38 -0
  261. package/agent-template/rulesync.jsonc +11 -0
  262. package/dist/bin.js +3 -1
  263. package/package.json +3 -2
  264. package/template/default/.prettierignore +9 -0
  265. package/template/default/README.md +12 -0
  266. package/template/default/flowdular.json +3 -3
  267. package/template/default/modules/example/package.json +1 -1
  268. package/template/default/package.json +6 -2
  269. package/template/default/platform/octane.config.ts +17 -6
  270. package/template/default/platform/package.json +3 -2
  271. package/template/default/platform/scripts/dev.mjs +39 -6
@@ -0,0 +1,107 @@
1
+ ---
2
+ name: release-eject-pr
3
+ description: >-
4
+ Land a module or core change: the sandbox eject sequence, the repository
5
+ verification gates, the git branch and PR conventions, and the post-merge
6
+ scope grant.
7
+ ---
8
+ # Eject, verify, deliver
9
+
10
+ Two paths reach the same place. The sandbox path is chat, gates, preview, eject. The direct path is a skill in your own coding tool, `pnpm verify`, a pull request. Both end with the module enabled through the CLI and its scopes granted.
11
+
12
+ ## 1. Sandbox eject (`packages/sandbox/src/server/delivery/{local,steps}.ts`, `routes.ts`)
13
+
14
+ `POST /sandbox/api/sessions/:id/eject` with `{ apply: false }` returns the plan: for every module of the session the files that land in `modules/<dir>`, the files that would be overwritten, the files the session deleted and the eject will remove, packages a module declares that the workspace cannot resolve yet, the gates, and whether the connected application has to restart. With `{ apply: true }` the sandbox streams the steps:
15
+
16
+ 1. Gates: `spec-schema` and `module-schema` once, then `dependencies`, `typecheck`, `tests`, `format` per draft module. Any failure stops the eject before a file is written (`EJECT_GATES_FAILED`).
17
+ 2. Copy of each session module into `modules/<dir>`, then removal of the files an edit deleted (`removeModuleFiles`, empty directories included).
18
+ 3. `pnpm install` at the workspace root.
19
+ 4. `pnpm flowdular module enable <id> --apply --json` for each new module (writes `flowdular.json`, `platform/package.json`, `platform/src/generated/*`, and grants the module's scopes itself).
20
+ 5. `pnpm flowdular auth sync-scopes --module <id> --apply --json` for each module (idempotent re-grant, needed for edited modules that added a permission).
21
+ 6. `pnpm --filter @flowdular/platform typecheck`.
22
+ 7. Optionally `pnpm build`.
23
+ 8. A restart note: the connected application loads the new composition and runs new schema constants only at start, so a local `pnpm dev` restarts and a remote deployment redeploys.
24
+
25
+ A failing step stops the delivery there with the step's output; the session is marked delivered only when every step passed. Eject requires the connected grant to hold `sandbox.modules.eject`. Delivery targets sit behind one interface (`delivery/types.ts`); the request names one with `target: 'workspace' | 'git-pr'` (default from `flowdular.json`), `workspace` is the one above, `git-pr` is section 2.
26
+
27
+ ## 2. Git delivery from a sandbox (`target: 'git-pr'`, `packages/sandbox/src/server/delivery/git-pr.ts`)
28
+
29
+ The pull request is the unit of a delivery: one session, one branch, one PR, every module the session touched. Nothing in the operator's working tree or index changes; the work happens in a detached worktree under `.flowdular/sandbox/worktrees/<session>` that is removed afterwards, whatever the outcome.
30
+
31
+ - Available when the workspace is a git work tree with at least one commit, `.flowdular/` is ignored, and the configured remote exists (`git rev-parse --verify HEAD`, `git remote get-url <remote>`); a repository without commits answers "make the first commit before delivering as a pull request". A PR is opened when `gh auth status` succeeds (a provider token sealed in the sandbox configuration is handed to gh as `GH_TOKEN`); otherwise the branch is pushed and the compare link shown.
32
+ - Branch `<branchPrefix>/<module-dir>-<session id first 8>` from `<remote>/<baseBranch>`: `git fetch`, `git worktree add --detach`, `git switch -C`.
33
+ - In the worktree: the copy and the removals, `pnpm install --offline` (fallback `--prefer-offline`), `pnpm flowdular module enable <id> --apply` for each new module with the worktree as `--dir`, the platform typecheck.
34
+ - Guardrails before the commit: `git status --porcelain` in the worktree may list only `modules/<dir>/**` of the session's modules and `pnpm-lock.yaml`. A delivery with a new module may also change `flowdular.json`, `platform/package.json` and `platform/src/generated/**`. The count stays within `sandbox.delivery.maxChangedFiles` or, unset, the `.ai/policies/task-budgets.yaml` figure for the session kind (`new-module` 30, `edit-module` 12, default 18); new packages within `maxNewDependencies` (0). Owners come from `.ai/policies/path-ownership.yaml`; with `crossOwnerChanges.requireReviewer` a cross-owner change asks for a reviewer from each owner in the body. A violation lists the offending paths and stops before anything is committed; the branch is deleted.
35
+ - Commit `sandbox: add|update <module id>` (author from git config) with the session id and the gate summary, `git push -u --force-with-lease <remote> <branch>`, `gh pr create --base <baseBranch> --head <branch> --title "Add|Update <module id>" --body-file <tmp>` (`--reviewer` from `git.reviewers`). A second delivery of the same session updates the branch and keeps the open PR.
36
+ - PR body, plain: two or three sentences from the brief and the last review handoff, `Session <id>.`, the gate table (gate, module, result), the file list grouped as added, modified, removed, `Post-merge: pnpm flowdular auth sync-scopes --module <id> --apply` per module, the reviewer note. No attribution footers, no dashes.
37
+ - `sync-scopes` does not run in the worktree: it is a runtime action against the deployment database, so it stays the post-merge step. Deploy, run it with `FD_AUTH_DATABASE` pointing at that database, verify the navigation entry appears for an owner.
38
+ - Configuration in `flowdular.json`, all optional and validated by `packages/contracts/schemas/project.schema.json`: `sandbox.delivery { default: 'workspace' | 'git-pr', targets: ['workspace', 'git-pr'], git: { remote: 'origin', baseBranch: 'main', branchPrefix: 'sandbox', provider: 'github' | 'none', mode: 'auto' | 'direct' | 'fork', forkOwner: null, reviewers: [] }, maxChangedFiles }`. Read at request time. `auto` never creates a fork: it uses direct delivery only after GitHub confirms push access and otherwise asks the operator to choose `direct` or `fork`. Only an explicit `fork` choice authorizes fork creation.
39
+ - The screen: "Into this workspace" / "As a pull request", offered only when both are usable here; an unusable target says why. The git plan shows branch, base, changed files against the budget, new packages, owners touched and the guardrail verdict; done shows the PR or compare link. `.flowdular/sandbox/sessions/<id>/delivery.json` keeps the branch and the URL.
40
+
41
+ ## 3. Direct path from a working tree
42
+
43
+ ```bash
44
+ pnpm flowdular module enable <id> --apply # new module only; also grants its scopes (result: scopes)
45
+ pnpm flowdular auth sync-scopes --module <id> --apply # re-grant after a new permission, or against another database
46
+ pnpm verify # typecheck, test, validate, format:check
47
+ pnpm build # cli build and smoke, module sync --apply, platform build
48
+ pnpm audit --prod --audit-level high # what CI runs (.github/workflows/ci.yml)
49
+ ```
50
+
51
+ `pnpm validate` runs `spec validate --all`, `blueprint validate --all` (every `.ai/blueprints/*/blueprint.json` plus its companion files) and `module validate`. It checks manifests and schemas, not behaviour; typecheck and tests are the evidence.
52
+
53
+ Branch names: `feat/<module>-<topic>`, `fix/<module>-<topic>`, `core/<package>-<topic>`. Commit one logical change per commit; generated files travel with the command that produced them.
54
+
55
+ ## 4. PR conventions (repository rules)
56
+
57
+ - Short body: what changed and why in a few sentences, gotchas, one line on verification (`pnpm verify passes; pnpm build passes`). No file tables, no design essays, no restating the diff.
58
+ - No AI attribution: no AI `Co-Authored-By` line and no `Generated with` footer.
59
+ - No em or en dashes anywhere in commits, PR titles or bodies.
60
+ - Generated files and `modules.enabled` change only through the CLI, and the PR says which command produced them.
61
+ - Changes to `packages/**` name the consumers that were migrated (`core-extend`).
62
+
63
+ ## 4b. Pull request body template
64
+
65
+ ```text
66
+ Adds inventory.core: tenant-scoped stock locations with read and manage scopes,
67
+ a list and create endpoint, a Locations screen with a drawer form, and a
68
+ dashboard KPI. Covers INVENTORY-LIST, INVENTORY-CREATE, INVENTORY-DENY,
69
+ INVENTORY-ISOLATION.
70
+
71
+ Generated by the CLI in this PR: flowdular.json and platform/package.json
72
+ (pnpm flowdular module enable inventory.core --apply), platform/src/generated/*
73
+ (module sync), pnpm-lock.yaml (pnpm install).
74
+
75
+ Gates: spec-schema, module-schema, dependencies, typecheck, tests (7), format
76
+ all passed in the sandbox eject; pnpm verify and pnpm build pass locally.
77
+
78
+ Post-merge: pnpm flowdular auth sync-scopes --module inventory.core --apply against
79
+ the deployment database.
80
+ ```
81
+
82
+ ## 4c. Pre-flight checklist
83
+
84
+ - `git status` shows only `modules/<dir>/**` plus the CLI-generated files named above.
85
+ - `module.json` `version`, `spec/module.yaml` `specVersion` and `package.json` `version` are equal.
86
+ - `spec/module.yaml` is `approved`; the PR does not change its status.
87
+ - No `console.log` left in module code; no secrets or tokens in tests.
88
+ - The PR title is under 70 characters and names the module (`inventory.core: stock locations`).
89
+
90
+ ## 5. Container and tags
91
+
92
+ CI builds the image from `infra/docker/Dockerfile` on every PR (no push). A release tag `v*.*.*` is the trigger for publishing (workflow owned by the platform team). The image runs `node platform/dist/server/entry.js` with `/data` as the database volume. Compose and Kubernetes provide `FD_AUTH_DATABASE`, `FD_AGENTS_DATABASE`, and `FD_WORKFLOWS_DATABASE`. Production also requires `FD_AGENT_CREDENTIAL_KEY`, `FD_AGENT_RUN_GRANT_KEY`, `FD_WORKFLOWS_PAYLOAD_KEY`, and `FD_WORKFLOWS_CURSOR_KEY`; generate every key independently with `openssl rand -base64 32` and supply it through the deployment secret.
93
+
94
+ ## Pitfalls
95
+
96
+ - An eject removes the files a session deleted; a rename shows up as one removal and one addition in the plan.
97
+ - `module enable` runs `pnpm install` when the package is not linked; a failing install is reported as `pnpm install failed while linking the module package`. A failed scope grant after a successful enable is `MODULE_SCOPES_SYNC_FAILED`; rerun `auth sync-scopes`.
98
+ - `platform/.generated/` is a stale ignore entry; the live generated directory is `platform/src/generated/`.
99
+ - `pnpm flowdular module sync --apply` is also run by `pnpm dev` and `pnpm build`; a dirty generated file after a checkout means the enabled list and the files disagree.
100
+
101
+ ## Required auto-review
102
+
103
+ Before delivery, complete the separate `auto-review` phase. Sandbox eject requires
104
+ a current per-module review record and passing schema, dependency, typecheck,
105
+ test and format gates. Missing, skipped and empty-suite results block delivery.
106
+ Any module edit invalidates its review. Host changes also need the auto-review
107
+ report and full verification described by that skill before completion.
@@ -0,0 +1,106 @@
1
+ ---
2
+ name: spec-approval
3
+ description: >-
4
+ Apply an explicit user approval to the exact current Flowdular module
5
+ specification. Use only when the user directly asks to approve one or more
6
+ named current specs, never to infer or initiate approval.
7
+ ---
8
+ # Approve a module specification
9
+
10
+ Approval is a user decision that an agent may record only as a mechanical
11
+ delegate. Never decide that a specification is good enough, treat a review
12
+ verdict as approval, or infer approval from requests such as "continue", "looks
13
+ good", or "build it".
14
+
15
+ ## 1. Required authority
16
+
17
+ Proceed only when the current user message explicitly approves:
18
+
19
+ - one named module;
20
+ - the clearly active module referred to as "this module"; or
21
+ - every current module in one named sandbox session.
22
+
23
+ The instruction must refer to the current specification. An approval copied from
24
+ an earlier conversation, a different hash, or an earlier session is not
25
+ authority for changed content.
26
+
27
+ If the target is ambiguous, ask which module. If the user approves several
28
+ modules, process and report each separately.
29
+
30
+ ## 2. Review the exact input
31
+
32
+ Before recording approval:
33
+
34
+ 1. Read the entire spec/module.yaml.
35
+ 2. Confirm its module id and current specVersion.
36
+ 3. Run pnpm flowdular spec validate --all --json.
37
+ 4. Check the current diff or sandbox review for the requirements, permissions,
38
+ data ownership and acceptance scenarios being approved.
39
+ 5. Stop if validation fails, the module cannot be resolved, or the spec changed
40
+ while it was being reviewed.
41
+
42
+ Do not rewrite requirements while applying approval. A requested content change
43
+ is a new spec-authoring step and needs approval after that edit.
44
+
45
+ ## 3. Sandbox path
46
+
47
+ In the sandbox, use the operator approval action for the selected session module.
48
+ The live route is POST /sandbox/api/sessions/:id/approve, exposed by
49
+ approveSpecification in packages/sandbox/src/client/api.ts.
50
+
51
+ The route changes the status presentation and records the SHA-256 hash of the
52
+ exact approved text in the session. Do not patch the session workspace file to
53
+ bypass that route. Do not forge browser cookies or sandbox request headers. If
54
+ the operator route is unavailable, report the blocker and leave the spec
55
+ unapproved.
56
+
57
+ A multi-module session requires an approval record for every affected module.
58
+ Approving one module does not unblock another.
59
+
60
+ ## 4. Repository checkout path
61
+
62
+ Outside the sandbox, after the explicit current user instruction:
63
+
64
+ 1. Change only the top-level status value to approved.
65
+ 2. Format the file without changing its requirements.
66
+ 3. Run pnpm flowdular spec validate --all --json again.
67
+ 4. Compute shasum -a 256 modules/<dir>/spec/module.yaml.
68
+ 5. Report the module id, version and exact approved hash.
69
+
70
+ Do not combine approval with implementation changes in the same edit. Once the
71
+ approved state and hash are reported, implementation follows module-new or
72
+ module-update.
73
+
74
+ ## 5. Staleness
75
+
76
+ Approval applies only to the exact content that was approved.
77
+
78
+ - In a sandbox session, the recorded hash is authoritative. Any later edit,
79
+ request for changes, or added module reopens the approval gate.
80
+ - In a checkout, any later requirement change must return the status to draft
81
+ or in-review before authoring continues, then receive a new explicit user
82
+ approval.
83
+ - A version bump alone is still a content change and needs fresh approval.
84
+ - Never copy an approved status line into another module or session.
85
+
86
+ ## 6. Refuse
87
+
88
+ Refuse to approve when:
89
+
90
+ - no current user instruction explicitly grants approval;
91
+ - the user asked only for review, implementation or continuation;
92
+ - validation fails;
93
+ - unresolved business questions remain in the spec;
94
+ - the target module or session is ambiguous;
95
+ - the content changed after the user's decision;
96
+ - a sandbox role attempts to approve its own output.
97
+
98
+ A sandbox business manager may request approval in its handoff. That request is
99
+ not approval and cannot satisfy this skill's authority requirement.
100
+
101
+ ## 7. Handoff
102
+
103
+ After approval, state exactly what was approved and which hash now represents
104
+ it. Do not claim that implementation or delivery also passed. Continue to
105
+ implementation only when the user's request includes it and the matching skill
106
+ allows it.
@@ -0,0 +1,79 @@
1
+ ---
2
+ name: test-hardening
3
+ description: >-
4
+ Make a module test suite prove behaviour: where tests live and run, the route
5
+ recipe, the embedded PostgreSQL provider, the denial and isolation cases every
6
+ endpoint needs, and the break-the-implementation check.
7
+ ---
8
+ # Harden a test suite
9
+
10
+ ## 1. Where tests live and run
11
+
12
+ - `tests/module.test.ts` (one file per module today; more files are fine). `tsconfig.json` includes `src/**/*` and `tests/**/*.ts`, so a test file is `.ts`; `.tsrx` components are not compiled by vitest here. Testable client logic (formatting, filtering, mapping, state transitions) goes into a `.ts` helper next to the view and is imported by the test.
13
+ - Runner: `vitest run` (`pnpm --filter @flowdular/module-<dir> test`). In the sandbox the `tests` gate runs `vitest run --passWithNoTests` inside the module directory, so a module with no tests passes the gate. Treat an empty or trivial suite as a defect, not a pass.
14
+ - New modules use vitest 4.1.11, typescript 5.9.3 and `@types/node` 24.13.3. The catalog reference is an immutable older release; use the current scaffold dependency versions for new code.
15
+
16
+ ## 2. Repositories on an embedded PostgreSQL
17
+
18
+ `createPgliteTestProvider()` from `@flowdular/sdk/database-testing` runs a real PostgreSQL inside the test process, with the same `coreloom_runtime` and `coreloom_background` roles and the same forced row-level security a deployment enforces. Booting it costs about two seconds, so a suite opens one provider per test file, migrates it once, and truncates the module's tables between cases; `.ai/references/catalog/tests/support/database.ts` is the shape (`createCatalogTestDatabase` hands out a lease per fixture, `closeCatalogTestDatabases` runs in `afterAll`). Build the service on top: `new CatalogService((await createCatalogTestDatabase()).repository)`. A database module also keeps `tests/migrations.test.ts` for SQL byte parity, fresh apply, safe pre-ledger adoption, and a clean second start. A module whose spec has no `database` capability gets a `MemoryXRepository` from the scaffold instead; a module with a database tests the database repository, never a hand-written fake, because the SQL, the ledger and the row-level security are what need testing.
19
+
20
+ ## 3. Route recipe (from `modules/auth/tests/endpoints.test.ts`)
21
+
22
+ ```ts
23
+ import { createContext } from '@octanejs/app-core';
24
+ import { createAuthenticationMiddleware } from '@flowdular/sdk/modules/auth/server';
25
+ // build an AuthRuntime around a DatabaseAuthRepository on a createPgliteTestProvider() lease and a cheap scrypt cost,
26
+ // sign up through the auth sign-up route to obtain a cookie and csrfToken, then:
27
+ const routes = createCatalogRoutes(auth, runtime);
28
+ const create = routes.find(
29
+ (route) =>
30
+ route.path === '/api/catalog/items' && route.methods.includes('POST'),
31
+ )!;
32
+ const response = await create.handler(
33
+ createContext(
34
+ new Request('https://erp.example/api/catalog/items', {
35
+ method: 'POST',
36
+ headers: {
37
+ 'content-type': 'application/json',
38
+ origin: 'https://erp.example',
39
+ cookie,
40
+ 'x-csrf-token': csrfToken,
41
+ },
42
+ body: JSON.stringify(input),
43
+ }),
44
+ {},
45
+ ),
46
+ );
47
+ ```
48
+
49
+ The auth middleware must have set the principal for `endpointIdentityFromContext` to find it: either run `auth.middleware(context, next)` before the handler or resolve the session and set `AUTH_PRINCIPAL_STATE_KEY` on `context.state` (both exported from `@flowdular/sdk/modules/auth/server`). Read `modules/auth/tests/endpoints.test.ts` for the runtime shape (`cookie`, `settings`, `service`, `middleware`).
50
+
51
+ ## 4. Cases every endpoint needs
52
+
53
+ - 401 `UNAUTHENTICATED`: no cookie, no bearer token.
54
+ - 403 `FORBIDDEN`: a principal whose scopes lack the permission.
55
+ - 403 on a mutation without `x-csrf-token` (`CSRF_REJECTED`) and without `origin` (`ORIGIN_REQUIRED`).
56
+ - 400 with the stable code for each validation bound (`INVALID_INPUT`, module codes such as `INVALID_ITEM_KIND`).
57
+ - 409 for the tenant-scoped uniqueness rule, and success for the same key in another tenant.
58
+ - Tenant isolation: rows created for `tenant-a` are invisible to `list('tenant-b')`. The provider hands the suite the non-bypass `coreloom_runtime` role, so this runs against real forced row-level security; also assert that a call without tenant context fails with `TENANT_CONTEXT_REQUIRED`.
59
+ - Identity: `moduleDefinition.manifest.id` equals the module id (keeps `module.json` and `src/index.ts` aligned). The scaffold writes this and the isolation case; everything else in this list is yours.
60
+
61
+ Assert at the observation boundary: status code, `error.code`, returned record fields. Do not assert internal helper names, call order, or SQL text.
62
+
63
+ ## 5. Break the implementation
64
+
65
+ A regression test that never failed proves nothing. For each new test: comment out the guard it protects (`if (denial) return denial;`, the `WHERE tenant_id = $1`, the `UNIQUE` constraint), run the suite, confirm the test fails, restore the code. Record in the handoff which tests were verified this way.
66
+
67
+ ## 6. Flake sources here
68
+
69
+ `Date.now()` in `createdAt` (sort by `sku`, not by time); `randomUUID()` ids (never assert them); scrypt with the default cost is slow, so tests pass `passwordHash: { cost: 2 ** 12, ... }` as `modules/auth/tests/endpoints.test.ts` does; two tests sharing one provider see each other's rows unless the tables are truncated between them, so take a fresh fixture per test from the file's `tests/support/database.ts` helper.
70
+
71
+ ## 7. Landing
72
+
73
+ Sandbox: the `tests` gate output appears in the chat after your turn. Repository root: `pnpm --filter @flowdular/module-<dir> test`, then `pnpm verify` before a PR.
74
+
75
+ ## Pitfalls
76
+
77
+ - `expect(() => service.create(...)).toThrowError(/active tenant/)` pins a message; prefer the error `code` (`DUPLICATE_SKU`) when the class exposes one.
78
+ - A test that imports `@flowdular/sdk/ui` pulls fonts and CSS; keep client tests to `.ts` helpers.
79
+ - `vitest run` picks up `tests/**/*.test.ts`; a `.spec.ts` name also works but keep one convention.
@@ -0,0 +1,78 @@
1
+ ---
2
+ name: translations-i18n
3
+ description: >-
4
+ Add or review Flowdular UI translations through the shared client runtime,
5
+ module bundles, locale-aware formatting, and validation gates.
6
+ ---
7
+ # Translate Flowdular UI
8
+
9
+ Flowdular loads translations at runtime. The shell owns locale selection and the fallback chain; each module owns its copy.
10
+
11
+ ## Runtime contract
12
+
13
+ - `packages/client/src/i18n` registers the shell bundle and every enabled module bundle. Resolution is active locale, then `en`, then the key itself so a missing key stays visible.
14
+ - A module contribution imports `translations/en.json` and every declared locale, then returns `translations: { en, pl }` with its `moduleId`.
15
+ - Use fully qualified keys with `t()`, for example `t('catalog.items.title')`. In `.tsrx`, import from `@flowdular/sdk/client`. In plain `.ts` helpers, import from `@flowdular/sdk/client/i18n` so tests do not pull the TSRX shell entry.
16
+ - Navigation and account-menu labels use getters. Contributions are created before their bundles are registered, so eager `label: t(...)` can paint a raw key.
17
+ - Locale-sensitive dates, numbers and currency use `activeLocale()` with `Intl.DateTimeFormat` or `Intl.NumberFormat`.
18
+ - The personal locale selector lives in Profile and applies immediately. The tenant default remains an Administration setting and is the fallback when the browser has no personal choice.
19
+
20
+ ## Module workflow
21
+
22
+ 1. Keep the same locale list in `spec/module.yaml`, `module.json` and `flowdular.json`. Every module ships `en`.
23
+ 2. Put all user-facing labels, hints, empty states, errors and accessible names in `translations/<locale>.json`. Keep flat, module-local keys such as `items.form.save`; the runtime adds the module namespace.
24
+ 3. Add the same key to every locale in the same change. Write natural copy in each language.
25
+ 4. Import the bundles in `src/client/contribution.tsrx` and expose them through `translations`.
26
+ 5. Replace literals with `t('<module>.<key>')`. Dynamic families such as `t('expenses.status.' + status)` require every possible suffix in every bundle.
27
+ 6. For a new locale-sensitive helper, add a test that changes the active locale and proves both the text and formatting.
28
+
29
+ Navigation pattern:
30
+
31
+ ```ts
32
+ import { t, type ModuleClientContribution } from '@flowdular/sdk/client';
33
+ import translationsEn from '../../translations/en.json';
34
+ import translationsPl from '../../translations/pl.json';
35
+
36
+ return {
37
+ moduleId: 'inventory.core',
38
+ translations: { en: translationsEn, pl: translationsPl },
39
+ navigation: [
40
+ {
41
+ get label() {
42
+ return t('inventory.navigation.label');
43
+ },
44
+ get description() {
45
+ return t('inventory.navigation.description');
46
+ },
47
+ // remaining contribution fields
48
+ },
49
+ ],
50
+ };
51
+ ```
52
+
53
+ ## Validation
54
+
55
+ Run:
56
+
57
+ ```bash
58
+ pnpm flowdular module validate --module <module-id>
59
+ pnpm --filter @flowdular/module-<dir> typecheck
60
+ pnpm --filter @flowdular/module-<dir> test
61
+ pnpm format:check
62
+ ```
63
+
64
+ `module validate` rejects a missing locale file, mismatched locale key sets, and a static `t('module.key')` whose module bundle does not contain the key. A dynamic key cannot be proven statically, so test its complete value set.
65
+
66
+ When a raw key appears in the UI, check in this order:
67
+
68
+ 1. The key exists in `translations/en.json` and the active locale.
69
+ 2. The contribution exposes the bundle under the correct `moduleId` namespace.
70
+ 3. Navigation copy is lazy through getters.
71
+ 4. The running dev server has rebuilt after the contribution changed.
72
+
73
+ ## Do not
74
+
75
+ - Add a module-local translation runtime or import JSON directly in each view.
76
+ - Leave English fallbacks in client API helpers. Use a translated fallback and preserve server messages when present.
77
+ - Translate identifiers, provider names, currency codes, shortcuts or stable error codes.
78
+ - Hide a missing key with an empty string.
@@ -0,0 +1,92 @@
1
+ ---
2
+ name: ux-design
3
+ description: >-
4
+ Design a module screen on the shared design system, with the record-screen
5
+ recipe, the five states, the component and class inventory, and the icon keys.
6
+ ---
7
+ # Design a screen
8
+
9
+ `docs/design-system.md` (in a session: `reference/design-system.md`) is the only source of visual decisions. Primitives live in `packages/ui` (`reference/packages/ui/components/*.tsrx` and `components.css`). The reference screen is `.ai/references/catalog/src/client/CatalogView.tsrx` with `CatalogItemForm.tsrx`.
10
+
11
+ ## 1. Rules (design-system.md, section Rules)
12
+
13
+ 1. Primitives first: a `ui-*` class or an exported component before any new visual code.
14
+ 2. Colors, fonts, sizes, radii and shadows only from tokens (`var(--...)`); no hex in module CSS.
15
+ 3. Never restyle or override a `ui-*` class outside `packages/ui`.
16
+ 4. A missing primitive becomes a module-local component on tokens, flagged as a promotion candidate for `packages/ui`.
17
+ 5. Blue is action and selection; green, amber and red are state; copper is the brand only.
18
+ 6. Minimum text size 12 px; labels `--text-xs` uppercase; numbers tabular (`.num`, `ui-kpi__value`).
19
+ 7. Containment is owned by the primitives: children of `ui-view`, `ui-two-col`, `ui-grid-2`, `ui-kpi-grid` shrink, long words wrap, wide content scrolls inside `ui-table-wrap`.
20
+ 8. `Kpi` is a stat tile: the value is a number or a short state word; identifiers, addresses and paths go into `note` or a `ui-mono` line.
21
+ 9. Records own the page; creating and editing happens in a `Drawer`. Never split the width between a table and a form.
22
+
23
+ ## 2. Record screen recipe
24
+
25
+ ```text
26
+ div.ui-view
27
+ PageHeader eyebrow title description actions: Button sm [Icon refresh 14] Refresh, Button sm primary [Icon plus 14] New ...
28
+ Alert only when error && !formOpen
29
+ TableCard title count head is one line: title with its count left, SearchField and Filters right
30
+ search SearchField value placeholder label onInput
31
+ filters Filters open onToggle activeCount; the controls live inside the dropdown
32
+ columns rows rowKey columns is a module-level readonly TableColumn<Row>[] outside the component
33
+ status 'loading' | 'idle' 'loading' only while status === 'loading' && rows.length === 0
34
+ empty emptyFiltered filtered filtered picks which of the two the table renders
35
+ actions actionsLabel visible compact buttons; undefined when the scope is missing
36
+ note one constraint worth stating
37
+ Drawer open title subtitle onClose form keyed by 'form-' + formSession
38
+ ```
39
+
40
+ `TableCard` is the record card and `Table` is the only table in the product: never hand-roll `table.ui-table` again, and never rebuild the head, the loading row or the empty state that these already own. `actions(row)` returns `TableAction[]`; the component renders visible compact buttons in its narrow trailing column. Do not build a dropdown or module-owned action markup. Fixed column widths apply through loading, empty and populated states. A cell returns nodes: `span.ui-cell` (`<b>` primary, `<small>` secondary), `ui-mono` for an identifier, `Tag` for state, `numeric: true` on the column for tabular figures.
41
+
42
+ The shared `Table` is backed by the official `@octanejs/tanstack-table` adapter. A module never imports TanStack directly. It supplies the Flowdular columns, rows and actions above, while `@flowdular/sdk/ui` owns the features, row model, header model and cell rendering.
43
+
44
+ Every column declares `width`. Primary identity and descriptions get the largest share, dates and identifiers a medium share, and counts or status the smallest. For a table with actions, data widths normally add up to about 90 percent because the shared action column is 160 px. Without actions they add up to 100 percent. Do not leave all columns unspecified: equal distribution wastes space and weakens the hierarchy.
45
+
46
+ Drawer form: `form.ui-drawer__form > div.ui-drawer__body > div.ui-form > div.ui-form__row > FormField label required help` wrapping a native `input.ui-input`, `select.ui-select` or `textarea.ui-textarea`; `Alert` inside the body for the submit error; `div.ui-drawer__foot` with `<small>` for the constraint and `div.ui-form__actions` (Cancel, primary submit with `disabled={busy}` and a progressive label `Creating…`). `Drawer width="lg"` when rows have two columns or an editor.
47
+
48
+ Read-only master-detail (runs, playground) keeps `ui-two-col` (+ `--wide-aside`). Admin overviews use `ui-kpi-grid` with several `Kpi`; a module dashboard widget is one `Kpi` with `href` and `linkLabel`, rendered by the shell in `dashboard.metrics`.
49
+
50
+ ## 3. Five states
51
+
52
+ - Loading: `Table status="loading"` while `status === 'loading' && rows.length === 0`, so a refresh never blanks rows the user is reading.
53
+ - Empty: `empty` with an icon and a sentence that names the first action; `emptyFiltered` says no match and is chosen by `filtered`.
54
+ - Error: `Alert` (tone `danger` default) under the header, or inside the drawer while the form is open.
55
+ - Populated: the `Table` rows, or a list where records are not tabular.
56
+ - Denied: the shell already hides navigation and widgets whose `scope` the principal lacks. Inside a view, pass booleans derived from `ModuleClientContext.scopes` (`canManage={options.scopes.includes(X_PERMISSIONS.manage)}` in `contribution.tsrx`, as `modules/agents` does) and do not render the action. A 403 from the server still becomes an `Alert`; it is never a crash.
57
+
58
+ ## 4. Component and prop inventory (`packages/ui/src/components`)
59
+
60
+ - `Button`: `variant` primary, secondary (default), ghost, danger; `size` sm, md, lg; `type` button, submit; `block`; `disabled`; `onClick`.
61
+ - `FormField`: `label`, `required`, `help`, `error`; one control child with `ui-input`, `ui-select` or `ui-textarea`.
62
+ - `SearchField`: `value`, `placeholder`, `label` (accessible name), `onInput(value)`.
63
+ - `Table`: `columns: TableColumn<Row>[]` (`key`, `header`, required `width`, `cell(row)`, `numeric`), `rows`, `rowKey(row)`, `status`, `loadingLabel`, `empty`, `emptyFiltered`, `filtered`, `actions(row): TableAction[]`, `actionsLabel`, optional stable `actionsWidth` (160 px default, 280 px for two actions), `onSelect(row)`, `selectedKey`, `caption`.
64
+ - `TableCard`: every `Table` prop plus `title`, `count`, `head`, `search`, `filters`, `before`, `after`, `note`, `noteIcon`.
65
+ - `Filters`: `open`, `onToggle`, `activeCount`, `label`; children are the filter controls, which belong in the dropdown and nowhere else.
66
+ - `CheckGrid`: `groups: { label, options: { value, label, hint? }[] }[]`, `value: string[]`, `mono`, `disabled`, `onChange(next)`.
67
+ - `Drawer`: `open`, `title`, `subtitle`, `width` md or lg, `onClose`; child is `ui-drawer__form` or `ui-drawer__body`. Escape and the scrim close it.
68
+ - `Tag`: `tone` neutral, success, warning, danger, info, ink; `dot`; `mono`.
69
+ - `Kpi`: `label`, `value` (string), `unit`, `badge`, `note`, `href`, `linkLabel`.
70
+ - `PageHeader`: `eyebrow`, `title`, `description`; children are the right-side actions.
71
+ - `EmptyState`: `icon`, `title`, `code`, children as the sentence.
72
+ - `Alert`: `tone` danger (default), warning, info.
73
+ - `Avatar`: `name`, `square` (organizations), `large`.
74
+ - `Icon`: `name`, `size` (18 default, 16 in controls, 14 in `Button size="sm"`), `strokeWidth`.
75
+ - `BrandMark`: `size`, `signature`, `tone`; brand moments only.
76
+
77
+ Icon keys (`ICON_PATHS`, `packages/ui/src/icons/Icon.tsrx`): `dashboard`, `parties`, `catalog`, `user`, `users`, `shield`, `code`, `modules`, `file-text`, `play`, `bot`, `flask`, `activity`, `plug`, `search`, `chevron-down`, `chevrons-up-down`, `plus`, `panel-left`, `check`, `filter`, `download`, `more`, `external`, `alert`, `x`, `sign-out`, `refresh`, `help`, `key`, `settings`, `braces`. An unknown name renders `modules` silently, so check the list.
78
+
79
+ ## 5. Classes a module writes by hand (`packages/ui/src/styles/components.css`)
80
+
81
+ Layout `ui-view`, `ui-two-col` (+`--wide-aside`), `ui-grid-2`, `ui-kpi-grid`, `ui-tag-cloud`, `ui-section-head` (h2 plus actions inside a view), `ui-toolbar` (+`__spacer`). Surfaces `ui-card` (+`__head`, `__title`, `__body`). Data `ui-table` (+`ui-table-wrap`, `ui-table__empty`, `ui-table__state` for a dot plus label, `.num`), `ui-cell` (+`ui-cell__muted`), `ui-mono`, `ui-code`, `ui-dot` (+`--muted`). Row action classes are component-owned and are never written by a module. Forms `ui-form` (+`__row`, `__row--4`, `__foot`, `__actions`), `ui-input` (+`--error`), `ui-select`, `ui-textarea` (+`--error`), `ui-checkbox`, `ui-label`, `ui-help` (+`--error`). Drawer `ui-drawer__form`, `ui-drawer__body`, `ui-drawer__foot`. Bits `ui-kbd`, `ui-note`, `ui-menu` (+`__label`, `__item`, `__item--active`, `__item--danger`, `__sep`), `ui-btn ui-btn--icon` for an icon-only button. Classes rendered by components (`ui-drawer__panel`, `ui-search`, `ui-page-head*`, `ui-field`, `ui-empty*`, `ui-alert*`, `ui-tag*`, `ui-kpi__*`, `ui-checks*`, `ui-avatar*`) are not written by hand.
82
+
83
+ ## 6. Copy
84
+
85
+ User-facing copy lives in every declared `translations/*.json` bundle and is read with fully qualified `t()` keys. Eyebrow names the domain, title names the records, and description is one sentence. Table headers say what the value is. Buttons start with a verb. Loading text ends with `…`. Drawer footer states the constraint the user cannot see. Write natural copy in each locale, with no exclamation marks or database jargon.
86
+
87
+ ## Pitfalls
88
+
89
+ - `Kpi value={items.length}` does not typecheck; use `String(items.length)`.
90
+ - A `Tag` for a lifecycle state uses `success` for active and `neutral` for archived, with `dot`.
91
+ - An `Icon` inside `Button size="sm"` is 14, not 18.
92
+ - A new component file per screen, form, table, or stateful region; a page composes them.
@@ -0,0 +1,156 @@
1
+ ---
2
+ name: variables
3
+ description: >-
4
+ Build variable-aware fields and templates on the {{ }} contract, the scope
5
+ mask, and server-side resolution, with agents.core as the worked example.
6
+ ---
7
+ # Variables (templating and linked fields)
8
+
9
+ A variable field lets a stored value embed `{{ key }}` tokens that are filled at
10
+ run time from context or another module's data. The contract is pure and lives
11
+ in `@flowdular/sdk/contracts` (`packages/contracts/src/variables.ts`); the fields are
12
+ presentational primitives in `@flowdular/sdk/ui`; resolution happens on the server
13
+ before the consumer sees the text. `agents.core` is the worked example: an agent
14
+ author writes instructions as a template and the run snapshot carries the
15
+ resolved text.
16
+
17
+ ## The `{{ }}` contract
18
+
19
+ `VariableDefinition { key, label, kind, scope?, sample?, description? }` is one
20
+ offerable variable. `kind` is `text | number | date | money | identifier`.
21
+ `key` is a dot path whose segments start lowercase and may continue in
22
+ camelCase (`context.user.displayName`), matched by `VARIABLE_KEY_PATTERN` /
23
+ `isVariableKey`.
24
+
25
+ - `extractVariables(template)`: the distinct trimmed `{{ key }}` tokens, in
26
+ first-seen order.
27
+ - `validateTemplate(template, available, allowedScopes?)`: `{ unknown, forbidden }`.
28
+ `unknown` are tokens not in `available`; `forbidden` are tokens whose def
29
+ declares a `scope` not present in `allowedScopes` (omit `allowedScopes` to skip
30
+ the scope check).
31
+ - `resolveTemplate(template, values, { onMissing })`: substitutes each
32
+ `{{ key }}` with `values[key]`. `onMissing` is `keep` (default, leave the token
33
+ verbatim) or `blank`.
34
+ - `tokenizeTemplate(template)`: the segments the UI overlay highlights; the
35
+ `text` fields concatenate back to the exact input.
36
+
37
+ Rules the resolver guarantees: tokens are `{{ key }}` with optional inner spaces;
38
+ `\{{` outputs a literal `{{`; substitution is a single pass, so a value that
39
+ itself looks like a token is emitted verbatim (never recursive); only own,
40
+ string keys resolve, so prototype keys (`__proto__`, `toString`) never resolve.
41
+ No eval, no expressions, only key substitution.
42
+
43
+ ## The scope mask
44
+
45
+ `VariableDefinition.scope` is the permission required to read the source. A
46
+ variable is offered and resolved only when the principal holds that scope:
47
+
48
+ - The UI fields never fetch and never check scopes. The caller passes
49
+ `variables` already filtered to what this principal may use, so a variable the
50
+ principal cannot read is simply absent from the menu and highlights as an error
51
+ pill if typed.
52
+ - On the server, filter the definition list by the principal's scopes before
53
+ building `values`, or call `validateTemplate(..., allowedScopes)` and refuse a
54
+ template whose `forbidden` is non-empty. A scope-less variable
55
+ (`context.*`) is always allowed.
56
+
57
+ ## The UI fields (`@flowdular/sdk/ui`)
58
+
59
+ `VariableTextarea` (multiline), `VariableInput` (single line), and
60
+ `VariableSelect` (one literal option or one variable token) are presentational.
61
+ All take `value`, `onInput`, `variables: readonly VariableDefinition[]`, optional
62
+ `sampleValues?: Record<string,string>`, required translated `label` (accessible
63
+ name), `name` (so a `FormData` submit still captures it), `required`, `disabled`,
64
+ and `error`. Input and textarea also require translated `insertLabel`,
65
+ `variablesLabel`, and `emptyLabel`; the shared primitive has no English copy to
66
+ fall back to. Select takes `options: readonly VariableSelectLiteralOption[]` and
67
+ requires translated `literalGroupLabel` and `variablesGroupLabel`. A caller also
68
+ localizes every `VariableDefinition.label` before passing the definitions, since
69
+ that label is visible in the picker.
70
+
71
+ - The `braces` affordance (a `{}` icon in `ICON_PATHS`) opens a menu of the
72
+ available variables with label, key, and current or sample value; picking one
73
+ inserts `{{ key }}` at the caret. Typing `{{` opens the same menu filtered by
74
+ what follows; ArrowUp/Down and Enter pick, Escape closes.
75
+ - Tokens are highlighted by an overlay layer (`tokenizeTemplate`) sitting behind
76
+ a transparent control, so `{{ key }}` reads as a pill while the real value
77
+ stays plain text; an unknown or forbidden token gets the error pill. All
78
+ color comes from tokens; measurement is client-only in an effect, so SSR is
79
+ safe. See `packages/ui/src/components/VariableField.tsrx` and its wrappers.
80
+ - `VariableSelect` stays a native `<select>`. Literal values and allowed
81
+ `{{ key }}` tokens are real `<option>` values, so keyboard navigation,
82
+ validation, disabled state, accessible naming, and `FormData` submission keep
83
+ browser semantics. Samples appear only in option labels. The component never
84
+ resolves the selected token.
85
+
86
+ Keep the fields presentational: the caller supplies `variables` and
87
+ `sampleValues`, the component never fetches.
88
+
89
+ The platform variable registry (`@flowdular/sdk/kernel`) registers definitions and
90
+ their execution-time resolvers. Reach the shared instance with
91
+ `platformVariableRegistry(context.capabilities)`. `list(scopes)` requires an
92
+ explicit permission snapshot and is the only
93
+ definition list a server sends to a field. `resolve(template, request)` takes a
94
+ trusted tenant id, actor, immutable permission snapshot, `AbortSignal`, explicit
95
+ record bindings and optional values owned by the consumer. It validates the
96
+ whole template before invoking a source. An unknown token, missing scope,
97
+ missing binding, aborted request, unavailable record, or source failure is a
98
+ refusal. Source exceptions are replaced with a generic error so SQL, provider,
99
+ and record details do not cross the module boundary.
100
+
101
+ The source declares `requiredBindings` per variable. For example, `party.name`
102
+ requires `partyId`; the resolver receives that id explicitly and asks the
103
+ parties public capability or read tool under `request.tenantId`. It never infers
104
+ a record from browser state and never reads the parties database. Local form
105
+ values go in `request.values`, while cross-module values must come from the
106
+ registered resolver. The resolved text is returned to the server consumer only;
107
+ the raw template remains stored.
108
+
109
+ ## Server-side resolution rule
110
+
111
+ Resolve the template before the consumer sees it, and keep the raw template
112
+ stored. The stored record keeps the `{{ }}` template; the run or send snapshot
113
+ carries the resolved text. Never resolve in the client and never store the
114
+ resolved text back onto the definition.
115
+
116
+ Worked example in `agents.core`:
117
+
118
+ - `modules/agents/src/domain/context-variables.ts` declares
119
+ `AGENT_CONTEXT_VARIABLES` (`context.tenantName`, `context.today`,
120
+ `context.user.displayName`, `context.user.email`, all scope-less) and
121
+ `agentContextValues(input)` that builds their values from the run's
122
+ tenant/principal/date.
123
+ - `AgentService.enqueueRun` (`services/agent-service.ts`) resolves
124
+ `agent.instructions` with `resolveTemplate` against those values before it
125
+ builds the instruction snapshot; the stored definition is untouched. The
126
+ endpoint (`api/endpoints.ts`) supplies the tenant name and principal from the
127
+ request; `today` comes from the queue timestamp.
128
+ - `AgentDefinitionForm.tsrx` feeds `VariableTextarea` the context variable list
129
+ and sample values, so an author gets the menu and highlighting.
130
+
131
+ ## Adding a variable source via a capability or tool
132
+
133
+ Business-data variables (for example `{{ party.name }}`) resolve through a
134
+ public capability or an agent read tool owned by the source module:
135
+
136
+ 1. Declare the `VariableDefinition` with `scope` equal to the source tool's
137
+ `requiredPermissions` (`AgentTool` in `packages/harness/src/runtime.ts`). The
138
+ tool's permission is the mask: one source of truth, no parallel table.
139
+ 2. Register the source on the shared registry during module composition. Declare
140
+ the record id in `requiredBindings`; do not accept a tenant binding.
141
+ 3. Offer it only through `registry.list(principal.scopes)`. The UI fields take
142
+ this already-filtered list and never fetch.
143
+ 4. Call `registry.resolve` on the server with the trusted tenant, actor,
144
+ permission snapshot, signal, and explicit bindings. The source invokes only
145
+ its owning public capability or read tool and maps the bounded result to
146
+ strings. The registry refuses before the source runs if the scope or binding
147
+ is absent.
148
+
149
+ `automations.core` is the live cross-module example. `agent.name` requires the
150
+ schedule's explicit `agentId`, and its resolver calls the `agents.run-queue`
151
+ capability with the active tenant. A binding containing an agent from another
152
+ tenant resolves to no record and the run is refused. The schedule keeps the raw
153
+ template; only the queued run receives the resolved input.
154
+
155
+ Register a new tool with the `agent-tool-design` skill; this skill covers only
156
+ how its output becomes a resolvable variable.