@akanjs/cli 3.0.0-beta.9 → 3.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 (224) hide show
  1. package/.build-stamp +1 -1
  2. package/CodeAgent-2vmp9qe0.js +9 -0
  3. package/CodeAgentRpcHost-p7h6n2hp.js +7 -0
  4. package/CodeAgentRpcListener-7p7z5dd7.js +97 -0
  5. package/CodeTui-5ed6bg2a.js +1457 -0
  6. package/README.ko.md +83 -28
  7. package/README.md +70 -27
  8. package/TunnelShare-cwh9ezsm.js +9 -0
  9. package/{agent.command-0q17pnrs.js → agent.command-gn73rhe7.js} +8 -9
  10. package/akanCode.js +102 -0
  11. package/application.command-e968t0hw.js +246 -0
  12. package/{applicationBuildRunner-kxwbssgn.js → applicationBuildRunner-64taxseg.js} +121 -77
  13. package/buildBatch.proc.js +120 -32
  14. package/cloud.command-0jm301rr.js +95 -0
  15. package/cloud.script-nemnzjpv.js +20 -0
  16. package/code.command-18kcm7hq.js +154 -0
  17. package/commandManifest.json +1 -1
  18. package/consoleToStderr-h5bryj94.js +7 -0
  19. package/context.command-trd9bn18.js +48 -0
  20. package/context.runner-y5hwh673.js +21 -0
  21. package/{dependencyScanner-shs82ynp.js → dependencyScanner-06z2dmrr.js} +2 -2
  22. package/{devTui-m1atgkc9.js → devTui-q7k7bakd.js} +122 -40
  23. package/fileSys-wzzrntkw.js +9 -0
  24. package/guideline.command-bdmdn2de.js +42 -0
  25. package/guidelines/agentRule/agentRule.instruction.md +154 -26
  26. package/guidelines/conventions/conventions.instruction.md +134 -48
  27. package/guidelines/mcpRule/mcpRule.instruction.md +7 -5
  28. package/guidelines/queryRule/queryRule.instruction.md +21 -7
  29. package/guidelines/runtimeRule/runtimeRule.instruction.md +291 -6
  30. package/guidelines/ssrRule/ssrRule.instruction.md +4 -2
  31. package/guidelines/transportRule/transportRule.instruction.md +10 -8
  32. package/guidelines/workspaceOnboarding/workspaceOnboarding.instruction.md +20 -0
  33. package/guidelines/workspaceRecipes/workspaceRecipes.instruction.md +66 -3
  34. package/incrementalBuilder.proc.js +564 -109
  35. package/index-0wpybxtr.js +2729 -0
  36. package/{index-kcxmv48c.js → index-2ahj896a.js} +45 -79
  37. package/index-2wc41k5w.js +49 -0
  38. package/index-3hx1z96c.js +87 -0
  39. package/{index-2tv3fdx4.js → index-3w94e7n3.js} +1228 -1458
  40. package/{index-hwkygn9s.js → index-48k96p55.js} +325 -36
  41. package/index-4npz05nm.js +187 -0
  42. package/{index-q0y2jq9j.js → index-52w0fvh8.js} +21 -4
  43. package/index-6he6gvfg.js +4300 -0
  44. package/index-7md2g9vw.js +6 -0
  45. package/{index-04bk599g.js → index-93j60em0.js} +10 -17
  46. package/index-94s080qm.js +266 -0
  47. package/index-9dp031by.js +12 -0
  48. package/{index-ktb5njgy.js → index-9z771ptz.js} +49 -99
  49. package/{index-y6n8nmny.js → index-abxpehf8.js} +75 -203
  50. package/{index-3d9y066c.js → index-bjwxs904.js} +9 -8
  51. package/{index-9077wzd2.js → index-by9pxkr0.js} +43 -26
  52. package/index-c6eggbee.js +123 -0
  53. package/index-d2242ksy.js +99 -0
  54. package/index-drehwpdt.js +1119 -0
  55. package/{index-92zztqy1.js → index-dy0jx9ay.js} +2 -17
  56. package/{index-ffvk10zz.js → index-f3c78nd6.js} +107 -241
  57. package/index-gbggbqcp.js +1058 -0
  58. package/{index-by5gpp8x.js → index-hj6w4ka1.js} +2 -1
  59. package/index-hsmtysz4.js +53 -0
  60. package/{index-j9yhg404.js → index-jmrn54tt.js} +4 -7
  61. package/{index-a64esmtb.js → index-kcdj1z5v.js} +523 -1022
  62. package/{index-d43r08tf.js → index-kqabytj7.js} +77 -150
  63. package/{index-vyvre9en.js → index-mbxbhay5.js} +6 -2
  64. package/{index-vxtad5bd.js → index-mgxpx3f2.js} +5 -8
  65. package/index-p6pbz9n5.js +25 -0
  66. package/{index-sj5vxr8f.js → index-p993efkq.js} +981 -897
  67. package/{index-2xp0kfkt.js → index-pmhqttv3.js} +37 -25
  68. package/index-tqm7jjcn.js +700 -0
  69. package/{index-0g7r8xz3.js → index-vpj42xwt.js} +114 -232
  70. package/index-wa5jbxj1.js +760 -0
  71. package/{index-k2j1hc4r.js → index-wprnsjms.js} +12 -12
  72. package/{index-gyd220p2.js → index-y3mphr1m.js} +554 -577
  73. package/index-yg7jhz8x.js +69 -0
  74. package/{index-h7yw2z9n.js → index-zzvvc845.js} +8 -14
  75. package/index.js +23 -22
  76. package/{library.command-qzzbj6d8.js → library.command-9h749a1x.js} +4 -5
  77. package/{localRegistry.command-vyrgg4xw.js → localRegistry.command-2bqqhxyz.js} +23 -43
  78. package/{module.command-rq5hq3ax.js → module.command-nv8nzgft.js} +8 -12
  79. package/{package.command-d9hd6v9n.js → package.command-mq4bggp6.js} +6 -7
  80. package/package.json +15 -7
  81. package/{page.command-126d56te.js → page.command-j639n707.js} +4 -5
  82. package/{primitive.command-taxd1538.js → primitive.command-kz6849th.js} +9 -23
  83. package/prompter-76k56b37.js +8 -0
  84. package/{quality.command-6gsna0a3.js → quality.command-4hyabwkb.js} +205 -377
  85. package/{recipeScanner-fpagtgc1.js → recipeScanner-4b69ybe0.js} +1 -1
  86. package/{repair.command-tq2dgy72.js → repair.command-a9awpbqr.js} +11 -35
  87. package/{routeSourceValidator-apyqw7wk.js → routeSourceValidator-7ycnfz0e.js} +0 -6
  88. package/{scalar.command-3bcmrrkv.js → scalar.command-ma4fpm9m.js} +7 -11
  89. package/skills/akan-code/SKILL.md +130 -0
  90. package/skills/akan-debug/SKILL.md +42 -0
  91. package/skills/akan-deps/SKILL.md +50 -0
  92. package/skills/akan-err/SKILL.md +39 -0
  93. package/skills/akan-module/SKILL.md +45 -0
  94. package/skills/akan-scaffold/SKILL.md +76 -0
  95. package/skills/akan-scalar/SKILL.md +52 -0
  96. package/skills/akan-service/SKILL.md +34 -0
  97. package/skills/akan-store/SKILL.md +67 -0
  98. package/skills/akan-ui/SKILL.md +63 -0
  99. package/skills/akan-validate/SKILL.md +44 -0
  100. package/{subspace.command-eddzqpx3.js → subspace.command-3k2pmrs4.js} +53 -62
  101. package/templates/__scalar/__model__/__model__.abstract.md.template +8 -0
  102. package/templates/app/page/_index.tsx +257 -258
  103. package/templates/app/page/_layout.tsx +5 -1
  104. package/templates/app/page/_overrides.tsx +12 -0
  105. package/templates/app/page/styles.css.template +316 -14
  106. package/templates/app/public/apple-touch-icon.png +0 -0
  107. package/templates/app/public/favicon.ico +0 -0
  108. package/templates/app/public/jelly/cloud.webp +0 -0
  109. package/templates/app/public/jelly/comet.webp +0 -0
  110. package/templates/app/public/jelly/moon.webp +0 -0
  111. package/templates/app/public/jelly/planet.webp +0 -0
  112. package/templates/app/public/jelly/rocket.webp +0 -0
  113. package/templates/app/public/jelly/star.webp +0 -0
  114. package/templates/app/public/jelly/sugar.webp +0 -0
  115. package/templates/app/public/logo.png +0 -0
  116. package/templates/app/ui/Recipe/index.ts +9 -0
  117. package/templates/app/ui/Recipe/jellyButton.ts +63 -0
  118. package/templates/appSample/lib/__scalar/workHistory/workHistory.abstract.ts +8 -39
  119. package/templates/appSample/lib/_noti/noti.abstract.ts +6 -29
  120. package/templates/appSample/lib/task/Task.Unit.tsx +1 -1
  121. package/templates/appSample/lib/task/Task.Util.tsx +10 -13
  122. package/templates/appSample/lib/task/Task.View.tsx +2 -2
  123. package/templates/appSample/lib/task/Task.Zone.tsx +5 -2
  124. package/templates/appSample/lib/task/task.abstract.ts +12 -32
  125. package/templates/appSample/lib/task/task.signal.spec.ts +1 -15
  126. package/templates/appSample/lib/task/task.signal.test.ts +15 -1
  127. package/templates/appSample/lib/task/task.signal.ts +10 -8
  128. package/templates/appSample/page/task/[taskId]/_index.tsx +6 -6
  129. package/templates/appSample/page/task/[taskId]/edit.tsx +5 -4
  130. package/templates/appSample/page/task/_index.tsx +4 -3
  131. package/templates/appSample/page/task/_layout.tsx +1 -1
  132. package/templates/appSample/page/task/new.tsx +5 -4
  133. package/templates/appSample/srvkit/{AuthGuard.ts → guards.ts} +7 -4
  134. package/templates/appSample/srvkit/{SessionInternalArg.ts → internalArgs.ts} +6 -4
  135. package/templates/appSample/ui/GlobalLoading.tsx +2 -1
  136. package/templates/appSample/ui/QuantityControl.tsx +3 -3
  137. package/templates/client.ts +7 -30
  138. package/templates/crudPages/[__model__Id]/edit/page.tsx +2 -1
  139. package/templates/crudPages/[__model__Id]/page.tsx +2 -1
  140. package/templates/crudPages/new/page.tsx +2 -1
  141. package/templates/crudPages/page.tsx +2 -1
  142. package/templates/crudSinglePage/page.tsx +2 -1
  143. package/templates/lib/__lib/lib.dictionary.ts +0 -2
  144. package/templates/lib/__lib/lib.document.ts +0 -2
  145. package/templates/lib/sig.ts +8 -30
  146. package/templates/localDev/docker-compose.yaml.template +3 -14
  147. package/templates/module/__model__.signal.ts +12 -4
  148. package/templates/pkgRoot/tsconfig.json.template +2 -7
  149. package/templates/server.ts +1 -3
  150. package/templates/service/__model__.abstract.md.template +8 -0
  151. package/templates/workspaceRoot/.gitignore.template +0 -1
  152. package/templates/workspaceRoot/AGENTS.md.template +6 -0
  153. package/templates/workspaceRoot/package.json.template +1 -3
  154. package/tunnel.command-xwr46b66.js +129 -0
  155. package/{typeChecker-3xgrzc2v.js → typeChecker-px49mezc.js} +1 -1
  156. package/typecheck.proc.js +1 -1
  157. package/workflow.command-t7wqe4h7.js +79 -0
  158. package/{workspace.command-06wvstgr.js → workspace.command-4derhzrh.js} +79 -109
  159. package/abstractCompactor-gmkc0z8j.js +0 -102
  160. package/application.command-18y0s485.js +0 -204
  161. package/applicationReleasePackager-4yasxwv9.js +0 -244
  162. package/capacitorApp-36rbr858.js +0 -12
  163. package/cloud.command-av97ndh6.js +0 -94
  164. package/cloud.script-cm11xjdz.js +0 -20
  165. package/context.command-5v37ef3n.js +0 -81
  166. package/devTuiView-0hc2emhe.js +0 -9
  167. package/getDirname-dr3jz1xg.js +0 -7
  168. package/guideline.command-4pz9g1rt.js +0 -360
  169. package/guidelines/agentRule/agentRule.generate.json +0 -31
  170. package/guidelines/componentRule/componentRule.generate.json +0 -31
  171. package/guidelines/conventions/conventions.generate.json +0 -25
  172. package/guidelines/cssRule/cssRule.generate.json +0 -31
  173. package/guidelines/docPageRule/docPageRule.generate.json +0 -31
  174. package/guidelines/docPageRule/docPageRule.instruction.md +0 -25
  175. package/guidelines/docSyncRule/docSyncRule.generate.json +0 -31
  176. package/guidelines/docSyncRule/docSyncRule.instruction.md +0 -25
  177. package/guidelines/enumConstant/enumConstant.generate.json +0 -31
  178. package/guidelines/enumConstant/enumConstant.instruction.md +0 -28
  179. package/guidelines/fieldRule/fieldRule.generate.json +0 -31
  180. package/guidelines/framework/framework.generate.json +0 -32
  181. package/guidelines/mcpRule/mcpRule.generate.json +0 -35
  182. package/guidelines/modelConstant/modelConstant.generate.json +0 -32
  183. package/guidelines/modelDictionary/modelDictionary.generate.json +0 -32
  184. package/guidelines/modelDocument/modelDocument.generate.json +0 -31
  185. package/guidelines/modelService/modelService.generate.json +0 -31
  186. package/guidelines/modelSignal/modelSignal.generate.json +0 -31
  187. package/guidelines/modelStore/modelStore.generate.json +0 -31
  188. package/guidelines/modelTemplate/modelTemplate.generate.json +0 -31
  189. package/guidelines/modelUnit/modelUnit.generate.json +0 -31
  190. package/guidelines/modelUtil/modelUtil.generate.json +0 -31
  191. package/guidelines/modelView/modelView.generate.json +0 -31
  192. package/guidelines/modelZone/modelZone.generate.json +0 -31
  193. package/guidelines/moduleCodegen/moduleCodegen.generate.json +0 -31
  194. package/guidelines/moduleCodegen/moduleCodegen.instruction.md +0 -28
  195. package/guidelines/moduleOverview/moduleOverview.generate.json +0 -31
  196. package/guidelines/queryRule/queryRule.generate.json +0 -31
  197. package/guidelines/recipeRule/recipeRule.generate.json +0 -31
  198. package/guidelines/runtimeRule/runtimeRule.generate.json +0 -25
  199. package/guidelines/scalarConstant/scalarConstant.generate.json +0 -32
  200. package/guidelines/scalarDictionary/scalarDictionary.generate.json +0 -32
  201. package/guidelines/scalarModule/scalarModule.generate.json +0 -31
  202. package/guidelines/sharedUiUsage/sharedUiUsage.generate.json +0 -31
  203. package/guidelines/sharedUiUsage/sharedUiUsage.instruction.md +0 -25
  204. package/guidelines/ssrRule/ssrRule.generate.json +0 -31
  205. package/guidelines/transportRule/transportRule.generate.json +0 -31
  206. package/guidelines/utilUiUsage/utilUiUsage.generate.json +0 -31
  207. package/guidelines/utilUiUsage/utilUiUsage.instruction.md +0 -25
  208. package/guidelines/workspaceOnboarding/workspaceOnboarding.generate.json +0 -24
  209. package/guidelines/workspaceRecipes/workspaceRecipes.generate.json +0 -33
  210. package/index-47pj3aee.js +0 -76
  211. package/index-5ystnsd5.js +0 -841
  212. package/index-6wwzs1bc.js +0 -7
  213. package/index-a17x49vg.js +0 -229
  214. package/index-m0xccmmm.js +0 -228
  215. package/index-meh558jy.js +0 -65
  216. package/index-rtxxan88.js +0 -471
  217. package/index-te54x9zm.js +0 -338
  218. package/index-w8kmx1b6.js +0 -2176
  219. package/index-wt7qrtd4.js +0 -1516
  220. package/templates/__scalar/__model__/__model__.abstract.md +0 -28
  221. package/templates/module/__model__.abstract.md +0 -28
  222. package/templates/service/__model__.abstract.md +0 -28
  223. package/templates/workspaceRoot/patches/react-dom@19.2.7.patch.template +0 -62
  224. package/workflow.command-gtdnzvbz.js +0 -108
@@ -0,0 +1,67 @@
1
+ ---
2
+ name: akan-store
3
+ description: "Fill in a module store, or call module data and actions from UI — what store() already generates (CRUD, form setters, slice actions) and what belongs in your subclass."
4
+ ---
5
+
6
+ # Akan store: what is already generated
7
+
8
+ `class CommentStore extends store(sig.comment, () => ({ /* extra state */ })) { /* extra actions */ }`
9
+ gives you the whole CRUD and form surface for free. **Redeclaring any of it is a typecheck error
10
+ ("already exists"), and the fix is to delete your version and call the generated one** — never rename around
11
+ it, never cast, never reimplement.
12
+
13
+ For a model `comment` (`<Slice>` is a signal slice suffix, e.g. `InPublic`):
14
+
15
+ ## Generated actions — `st.do.*`
16
+
17
+ - write: `createComment(input)`, `updateComment(id, input)`, `removeComment(id)`
18
+ - form: `newComment()`, `editComment(model)`, `writeOnComment(path, value)`, `set<Field>OnComment(value)`,
19
+ `add<Field>OnComment(value)` / `sub<Field>OnComment(idx)` for arrays, `upload<Field>OnComment(fileList)`
20
+ for file fields, `checkCommentSubmitable()`, `submitComment()`, `createCommentInForm()`,
21
+ `updateCommentInForm()`
22
+ - single: `viewComment(model)`, `mergeComment(model, data)`, `setComment(...models)`, `resetComment()`
23
+ - per slice: `initComment<Slice>(...args)`, `refreshComment<Slice>()`, `selectComment<Slice>(model)`,
24
+ `setPageOfComment<Slice>(page)`, `loadMoreOfComment<Slice>()`, `setLimitOfComment<Slice>(limit)`,
25
+ `setQueryArgsOfComment<Slice>(...)`, `setSortOfComment<Slice>(sort)`
26
+
27
+ ## Generated state — `st.use.*`
28
+
29
+ `comment`, `commentLoading`, `commentForm`, `commentFormLoading`, `commentSubmit`, `commentModal`,
30
+ `commentViewAt`, `commentOperation`; per slice `commentList<Slice>`, `commentListLoading<Slice>`,
31
+ `commentInitList<Slice>`, `commentSelection<Slice>`, `commentInsight<Slice>`, `pageOfComment<Slice>`,
32
+ `lastPageOfComment<Slice>`, `hasMoreOfComment<Slice>`, `limitOfComment<Slice>`, `sortOfComment<Slice>`.
33
+
34
+ ## What your subclass is for
35
+
36
+ Only what the generated set does not cover: extra client state, a multi-step or cross-model workflow, and
37
+ calls to the non-CRUD endpoints you added in the signal. Each is an `async` method using `this.set(...)` /
38
+ `this.get()` / `this.pick(...)`.
39
+
40
+ To reach another store, cast `this`: `import type { RootStore } from "../st"`, then
41
+ `(this as unknown as RootStore).logout()` to call its action or `(this as unknown as RootStore).set({ … })` /
42
+ `.get()` to write or read its state. Every store is mixed into one root at runtime, so the cast only tells the
43
+ type what `this` already is. Keep it `import type` — `st.ts` imports every store, so a value import is a cycle.
44
+
45
+ A store that needs none of this stays empty. `store(sig.comment, () => ({}))` with an empty body is the
46
+ correct, complete result — an empty store is not an unfinished one.
47
+
48
+ ## The rule the linter enforces
49
+
50
+ - **An action returns nothing.** Every method dispatches through `st.do.<action>()` and is typed `void`, so a
51
+ returned value is unreachable. Write it into state with `this.set({ … })`.
52
+
53
+ ## From UI
54
+
55
+ Read with `st.use.commentListInPublic()`, act with `st.do.createComment(input)` / `st.do.submitComment()`.
56
+ Do not wrap a generated action in a store method just to rename it.
57
+
58
+ **Pass a form setter by reference**: `onChange={st.do.setTypeOnComment}`, never
59
+ `onChange={(v) => st.do.setTypeOnComment(v)}`. The two run identically, but the arrow is an anonymous
60
+ closure, so the control emits no `data-akan-action` and publishes no agent tool for the field —
61
+ `no-unpublished-form-setter.grit` fails the build on it.
62
+
63
+ ## Signal side
64
+
65
+ The scaffolded `slice(…, { guards: { root, get, cru } })` already exposes get / list / create / update /
66
+ remove. Add a query or mutation only for behaviour beyond CRUD, and give it its own `guards` array — a
67
+ custom endpoint never inherits the slice's.
@@ -0,0 +1,63 @@
1
+ ---
2
+ name: akan-ui
3
+ description: "Build Akan pages and components: the server-by-default rule, the Unit/View vs Template/Zone/Util split, forms, and what never to hand-roll."
4
+ ---
5
+
6
+ # Akan UI
7
+
8
+ A new View / Unit / Template / Zone / Util for an existing module comes from the `create-ui` workflow (the
9
+ `akan-scaffold` skill). Edit the scaffolded component instead of authoring one from scratch.
10
+
11
+ ## The boundary is mechanical, not a judgement call
12
+
13
+ | role | side | directive |
14
+ | --- | --- | --- |
15
+ | `<Model>.Unit.tsx`, `<Model>.View.tsx` | server | never `"use client"` |
16
+ | `<Model>.Template.tsx`, `<Model>.Zone.tsx`, `<Model>.Util.tsx` | client | `"use client"` on line 1 |
17
+ | `page/**` | server | never |
18
+
19
+ **Server is the default and `"use client"` is a cost you justify per component.** A component earns the
20
+ directive only by using a hook, a JSX event handler, the store, a browser global, or a client-only package.
21
+ Rendering markup, reading a param, calling `l()`, and mapping over data are all server work. Measure with
22
+ `akan quality ssr`; treat 50% server share as the floor and a falling share as a regression.
23
+
24
+ Four moves that keep markup on the server: wrap the interaction and render `children` untouched; split a
25
+ compound component so only the provider holds state; fetch in the route and pass the result into a `Zone` as
26
+ an `init` prop; push the boundary down to the leaf that needs it.
27
+
28
+ ## Never hand-roll these
29
+
30
+ - loading / empty / list states → `Load.Units` / `Load.View` / `Load.Edit` with `renderItem`, `renderList`,
31
+ `renderView`, `renderEmpty`; `<Empty />` for a bare placeholder; `Model.New` / `Model.Edit` /
32
+ `Model.SureToRemove` for CRUD modals.
33
+ - a mount-time fetch → the route calls `fetch.init<Model><Slice>(…)` before the first byte. A
34
+ `useEffect(…, [])` that loads data is flagged by `akan quality ssr`, and `fetch.init*` from a client file
35
+ fails lint outright.
36
+ - a model field input → `Field.*`, never a bare `<input>`.
37
+ - a mode switch → `Tab`, not a `useState`.
38
+
39
+ ## Component shape
40
+
41
+ `export const X = ({ … }: XProps) => { return (…); };` — arrow const, block body. Never `React.FC`, never
42
+ `defaultProps`, never `PropsWithChildren`; children are `children: ReactNode`. Declare
43
+ `interface XProps` directly above the component with `className?: string` first.
44
+
45
+ Conditional render is `cond ? <X/> : null`. **Never `{cond && <X/>}`** — in a `className` context it renders
46
+ the literal string `"false"`.
47
+
48
+ ## Classes
49
+
50
+ Never hand-order Tailwind classes; the formatter sorts them and its output is correct. Stay inside the
51
+ semantic token vocabulary — `bg-primary`, `text-foreground/70` — because a raw palette class (`bg-blue-500`),
52
+ an arbitrary colour (`bg-[#3b82f6]`), a daisyUI legacy class, and a colour literal in `style={{…}}` all
53
+ render no CSS and fail lint. Reach for `cn` only for a conditional or to merge an incoming `className`, and
54
+ merge the caller last.
55
+
56
+ ## Forms
57
+
58
+ Store-driven end to end: `value={xForm.field}` with `onChange={st.do.setFieldOnX}`, **the setter passed by
59
+ reference**. An inline arrow silently drops the control's agent annotation and fails
60
+ `no-unpublished-form-setter.grit`. Nested rows use `st.do.writeOnX("payments.3.name", v)`.
61
+
62
+ Full detail: `get_guideline` with `ssrRule`, `componentRule`, `cssRule`, `recipeRule`, or the per-role
63
+ `modelUnit` / `modelView` / `modelZone` / `modelTemplate` / `modelUtil`.
@@ -0,0 +1,44 @@
1
+ ---
2
+ name: akan-validate
3
+ description: "Run the akan check loop after editing and fix each failure class the right way — no silencing, no lint-disable, no gaming the checker."
4
+ ---
5
+
6
+ # Akan validation loop
7
+
8
+ ## One call does the whole chain
9
+
10
+ `akan_verify` runs sync → lint → typecheck → `quality ssr` over everything the working tree has changed, in
11
+ the right order, and reports only what failed. **Call it after editing, before you say you are done.** It
12
+ also runs itself at the end of a turn that changed source, so a failure comes back to you either way.
13
+
14
+ The same chain by hand, for the app you are editing:
15
+
16
+ - `akan sync <app>` — after adding, renaming, or deleting any file. Regenerates the barrels.
17
+ - `akan lint <app>` — fast; run after every edit. Prints up to 200 diagnostics.
18
+ - `akan typecheck <app>` — the client/server boundary violations land here, not in lint.
19
+ - `akan quality ssr` — only when you wrote or changed a `.tsx`. Did the server share hold?
20
+ - `akan build <app>` — the full production build; slower, and only when the build itself is in question.
21
+
22
+ Raw `tsc` misses the generated barrels and reports false errors. Do not reach for it.
23
+
24
+ ## Fixing rules
25
+
26
+ **Apply the real fix the rule intends. Do not silence the checker.** No blanket lint-disable, no throwaway
27
+ re-export, no export-in-place trick, no `as any`, no non-null assertion. Gaming a check is a defect, not a
28
+ fix. When a suppression is genuinely right it carries a reason:
29
+ `// biome-ignore lint/<rule>: <why>` — and a grit plugin diagnostic is suppressed as `lint/plugin`, not the
30
+ bare `plugin` category Biome's own message suggests.
31
+
32
+ Common classes and their real fix:
33
+
34
+ | symptom | fix |
35
+ | --- | --- |
36
+ | a member "already exists" or conflicts with the base class | you redeclared something Akan generates, usually store CRUD. **Delete your version** and call the generated one — do not rename, cast, or suppress. See `akan-store`. |
37
+ | `throw new Error` | a typed `Err` with a dictionary key. See `akan-err`. |
38
+ | an edit to a generated file | regenerate instead: `akan sync <app>` or `repair_generated`. |
39
+ | a file in the wrong place | relocate to `common/` / `webkit/` / `srvkit/` / `ui/`. Never a sibling helper `.ts` inside `lib/<model>/`. |
40
+ | a missing dependency | declare it in the owning app or lib `package.json`. See `akan-deps`. |
41
+ | a Tailwind class order or colour complaint | let the formatter sort; use semantic tokens. See `akan-ui`. |
42
+
43
+ Re-run until clean. If a failure is genuinely outside the scope of what you changed, say so in your reply
44
+ instead of forcing it.
@@ -1,29 +1,28 @@
1
1
  // @bun
2
2
  import {
3
- FileSys
4
- } from "./index-q0y2jq9j.js";
3
+ FileSys2
4
+ } from "./index-52w0fvh8.js";
5
5
  import {
6
6
  GlobalConfig
7
- } from "./index-m0xccmmm.js";
7
+ } from "./index-94s080qm.js";
8
8
  import {
9
9
  Workspace,
10
10
  CommandContainer,
11
11
  runner,
12
12
  script,
13
13
  command
14
- } from "./index-0g7r8xz3.js";
14
+ } from "./index-vpj42xwt.js";
15
15
  import {
16
16
  Executor,
17
17
  WorkspaceExecutor,
18
18
  AppExecutor
19
- } from "./index-2tv3fdx4.js";
20
- import"./index-6wwzs1bc.js";
19
+ } from "./index-3w94e7n3.js";
21
20
  import {
22
21
  SlicePlanner
23
- } from "./index-j9yhg404.js";
22
+ } from "./index-jmrn54tt.js";
24
23
  import {
25
24
  LibSource
26
- } from "./index-3d9y066c.js";
25
+ } from "./index-bjwxs904.js";
27
26
 
28
27
  // pkgs/@akanjs/devkit/subspace.ts
29
28
  import { mkdir, readdir, rename, rm } from "fs/promises";
@@ -118,16 +117,16 @@ class Subspace {
118
117
  return true;
119
118
  const normalize = (content) => {
120
119
  try {
121
- const manifest = JSON.parse(content);
122
- delete manifest[LibSource.manifestKey];
123
- return JSON.stringify(manifest);
120
+ return LibSource.unstamped(content);
124
121
  } catch {
125
122
  return content;
126
123
  }
127
124
  };
128
125
  return normalize(theirs) !== normalize(ours);
129
126
  }
130
- async#meaningfulFiles(ref, files) {
127
+ async#changedSliceFiles(ref, diffArgs) {
128
+ const files = (await this.#git(["diff", "--name-only", ...diffArgs])).split(`
129
+ `).filter((file) => !!file.trim() && !this.#isSubspaceOwned(file));
131
130
  const kept = await Promise.all(files.map(async (file) => this.#isStamped(file) ? await this.#differsBeyondStamp(ref, file) : true));
132
131
  return files.filter((_, index) => kept[index]);
133
132
  }
@@ -140,14 +139,12 @@ class Subspace {
140
139
  if (!hasBranch)
141
140
  return { name: this.name, branch, hasBranch, anchor, behindPaths: [], driftedLibs: [] };
142
141
  const ref = `${this.#remote}/${branch}`;
143
- const diff = await this.#git(["diff", "--name-only", "HEAD", ref, "--", ...paths]);
144
- const behindPaths = await this.#meaningfulFiles(ref, diff.split(`
145
- `).filter((file) => !!file.trim()).filter((file) => !this.#isSubspaceOwned(file)));
142
+ const behindPaths = await this.#changedSliceFiles(ref, ["HEAD", ref, "--", ...paths]);
146
143
  const driftedLibs = libs.filter((lib) => behindPaths.some((file) => file.startsWith(`libs/${lib}/`)));
147
144
  return { name: this.name, branch, hasBranch, anchor, behindPaths, driftedLibs };
148
145
  }
149
146
  async#ensureClone(branch) {
150
- if (!await FileSys.dirExists(path.join(this.#clonePath, ".git"))) {
147
+ if (!await FileSys2.dirExists(path.join(this.#clonePath, ".git"))) {
151
148
  await rm(this.#clonePath, { recursive: true, force: true });
152
149
  await mkdir(path.dirname(this.#clonePath), { recursive: true });
153
150
  await this.#workspace.spawn("git", ["clone", "--quiet", this.#declaration.repo, this.#clonePath]);
@@ -164,12 +161,12 @@ class Subspace {
164
161
  await rm(holdRoot, { recursive: true, force: true });
165
162
  for (const member of Subspace.memberDirs) {
166
163
  const memberRoot = path.join(this.#clonePath, member);
167
- if (!await FileSys.dirExists(memberRoot))
164
+ if (!await FileSys2.dirExists(memberRoot))
168
165
  continue;
169
166
  for (const name of await readdir(memberRoot)) {
170
167
  for (const owned of Subspace.subspaceOwnedDirs) {
171
168
  const from = path.join(memberRoot, name, owned);
172
- if (!await FileSys.dirExists(from))
169
+ if (!await FileSys2.dirExists(from))
173
170
  continue;
174
171
  const to = path.join(holdRoot, member, name, owned);
175
172
  await mkdir(path.dirname(to), { recursive: true });
@@ -192,14 +189,25 @@ class Subspace {
192
189
  const target = path.join(to, entry.name);
193
190
  if (entry.isDirectory())
194
191
  await Subspace.#copyMissing(source, target);
195
- else if (!await FileSys.entryExists(target))
192
+ else if (!await FileSys2.entryExists(target))
196
193
  await rename(source, target);
197
194
  }
198
195
  }
199
196
  async#extract(paths, excludes = []) {
200
- const pathspec = paths.map((entry) => `'${entry}'`).join(" ");
201
- const excludeArgs = excludes.map((entry) => `--exclude='${entry}'`).join(" ");
202
- await this.#workspace.exec(`git archive HEAD -- ${pathspec} | tar -x -C '${this.#clonePath}' ${excludeArgs}`.trim());
197
+ const archivePath = path.relative(this.#workspace.workspaceRoot, `${this.#clonePath}.tar`);
198
+ await this.#workspace.spawn("git", ["archive", "-o", archivePath, "HEAD", "--", ...paths]);
199
+ try {
200
+ await this.#workspace.spawn("tar", [
201
+ "-x",
202
+ "-f",
203
+ archivePath,
204
+ "-C",
205
+ this.#clonePath,
206
+ ...excludes.map((entry) => `--exclude=${entry}`)
207
+ ]);
208
+ } finally {
209
+ await rm(path.join(this.#workspace.workspaceRoot, archivePath), { force: true });
210
+ }
203
211
  }
204
212
  #localEnv() {
205
213
  const { serveDomain } = WorkspaceExecutor.getBaseDevEnv();
@@ -214,32 +222,32 @@ class Subspace {
214
222
  }
215
223
  async#writeLocalEnv() {
216
224
  const excludePath = path.join(this.#clonePath, ".git/info/exclude");
217
- const exclude = await FileSys.fileExists(excludePath) ? await FileSys.readText(excludePath) : "";
225
+ const exclude = await FileSys2.fileExists(excludePath) ? await FileSys2.readText(excludePath) : "";
218
226
  if (!exclude.split(`
219
227
  `).includes("/.env")) {
220
228
  await mkdir(path.dirname(excludePath), { recursive: true });
221
- await FileSys.writeText(excludePath, exclude.trim() ? `${exclude.replace(/\n*$/, `
229
+ await FileSys2.writeText(excludePath, exclude.trim() ? `${exclude.replace(/\n*$/, `
222
230
  `)}/.env
223
231
  ` : `/.env
224
232
  `);
225
233
  }
226
234
  const envPath = path.join(this.#clonePath, ".env");
227
- if (await FileSys.fileExists(envPath))
235
+ if (await FileSys2.fileExists(envPath))
228
236
  return;
229
237
  const lines = Object.entries(this.#localEnv()).map(([key, value]) => `${key}=${value}`);
230
- await FileSys.writeText(envPath, `${lines.join(`
238
+ await FileSys2.writeText(envPath, `${lines.join(`
231
239
  `)}
232
240
  `);
233
241
  }
234
242
  async#rewriteManifest(plans) {
235
243
  const manifestPath = path.join(this.#clonePath, "package.json");
236
- if (!await FileSys.fileExists(manifestPath))
244
+ if (!await FileSys2.fileExists(manifestPath))
237
245
  return [];
238
- const rootPackageJson = await FileSys.readJson(manifestPath);
246
+ const rootPackageJson = await FileSys2.readJson(manifestPath);
239
247
  const { packageJson, pruned, warnings } = await SlicePlanner.pruneDependencies(this.#workspace, rootPackageJson, plans.flatMap((plan) => plan.requiredDependencies));
240
248
  for (const warning of warnings)
241
249
  this.#workspace.logger.warn(warning);
242
- await FileSys.writeJson(manifestPath, {
250
+ await FileSys2.writeJson(manifestPath, {
243
251
  ...packageJson,
244
252
  name: this.name,
245
253
  description: `${this.name} workspace`
@@ -266,11 +274,10 @@ class Subspace {
266
274
  }
267
275
  async#rewriteGitignore() {
268
276
  const gitignorePath = path.join(this.#clonePath, ".gitignore");
269
- if (!await FileSys.fileExists(gitignorePath))
277
+ if (!await FileSys2.fileExists(gitignorePath))
270
278
  return;
271
279
  const [begin = "", end = ""] = Subspace.secretsMarkers;
272
- const lines = (await FileSys.readText(gitignorePath)).split(`
273
- `);
280
+ const lines = (await FileSys2.readText(gitignorePath)).split(/\r?\n/);
274
281
  const beginIdx = lines.indexOf(begin);
275
282
  const endIdx = lines.indexOf(end);
276
283
  const filtered = beginIdx >= 0 && endIdx > beginIdx ? [
@@ -278,7 +285,7 @@ class Subspace {
278
285
  ...lines.slice(beginIdx + 1, endIdx).filter((line) => this.#declaration.apps.some((app) => line.startsWith(`apps/${app}/`))),
279
286
  ...lines.slice(endIdx)
280
287
  ] : lines;
281
- await FileSys.writeText(gitignorePath, filtered.filter((line) => line.trim() !== "**/bun.lock").join(`
288
+ await FileSys2.writeText(gitignorePath, filtered.filter((line) => line.trim() !== "**/bun.lock").join(`
282
289
  `));
283
290
  }
284
291
  async#rewriteWorkspaceSection(libs) {
@@ -289,27 +296,27 @@ class Subspace {
289
296
  ];
290
297
  for (const file of ["AGENTS.md", "CLAUDE.md"]) {
291
298
  const filePath = path.join(this.#clonePath, file);
292
- if (!await FileSys.fileExists(filePath))
299
+ if (!await FileSys2.fileExists(filePath))
293
300
  continue;
294
- const content = await FileSys.readText(filePath);
301
+ const content = await FileSys2.readText(filePath);
295
302
  const rewritten = replacements.reduce((text, [pattern, value]) => text.replace(pattern, value), content);
296
303
  if (rewritten !== content)
297
- await FileSys.writeText(filePath, rewritten);
304
+ await FileSys2.writeText(filePath, rewritten);
298
305
  }
299
306
  }
300
307
  async#stampLib(clone, lib, branch) {
301
308
  const manifestPath = path.join(this.#clonePath, "libs", lib, "package.json");
302
- if (!await FileSys.fileExists(manifestPath))
309
+ if (!await FileSys2.fileExists(manifestPath))
303
310
  return;
304
311
  const origin = `${this.#workspace.repoName}#${branch}`;
305
312
  const sha = await this.headSha();
306
313
  const [hash, previous] = await Promise.all([this.#hashLib(clone, lib), this.#committedStamp(clone, lib)]);
307
- const manifest = await FileSys.readJson(manifestPath);
314
+ const manifest = await FileSys2.readJson(manifestPath);
308
315
  const akan = manifest[LibSource.manifestKey] ?? {};
309
316
  const unchanged = previous?.origin === origin && previous.sha === sha && previous.hash === hash;
310
317
  const syncedAt = unchanged ? previous.syncedAt : new Date().toISOString();
311
318
  manifest[LibSource.manifestKey] = { ...akan, source: { origin, sha, hash, syncedAt } };
312
- await FileSys.writeJson(manifestPath, manifest);
319
+ await FileSys2.writeJson(manifestPath, manifest);
313
320
  }
314
321
  async#committedStamp(clone, lib) {
315
322
  try {
@@ -332,22 +339,9 @@ class Subspace {
332
339
  `libs/${lib}`
333
340
  ]);
334
341
  const candidates = listed.split("\x00").filter((file) => !!file && !this.#isSubspaceOwned(file)).sort();
335
- const present = await Promise.all(candidates.map(async (file) => await FileSys.entryExists(path.join(this.#clonePath, file))));
342
+ const present = await Promise.all(candidates.map(async (file) => await FileSys2.entryExists(path.join(this.#clonePath, file))));
336
343
  const files = candidates.filter((_, index) => present[index]);
337
- const hasher = new Bun.CryptoHasher("sha256");
338
- for (const file of files) {
339
- const content = await FileSys.readText(path.join(this.#clonePath, file));
340
- hasher.update(file);
341
- hasher.update("\x00");
342
- if (file === `libs/${lib}/package.json`) {
343
- const manifest = JSON.parse(content);
344
- delete manifest[LibSource.manifestKey];
345
- hasher.update(JSON.stringify(manifest));
346
- } else
347
- hasher.update(content);
348
- hasher.update("\x00");
349
- }
350
- return hasher.digest("hex").slice(0, 32);
344
+ return await LibSource.hashFiles(files, `libs/${lib}/package.json`, (file) => FileSys2.readText(path.join(this.#clonePath, file)));
351
345
  }
352
346
  async diff(branch, filter) {
353
347
  if (!await this.fetch(branch))
@@ -366,10 +360,7 @@ class Subspace {
366
360
  if (!scoped.length)
367
361
  return { files: [], patch: "" };
368
362
  const from = `${this.#remote}/${branch}`;
369
- const pathspec = filter ? [filter] : scoped;
370
- const names = await this.#git(["diff", "--name-only", from, "HEAD", "--", ...pathspec]);
371
- const files = await this.#meaningfulFiles(from, names.split(`
372
- `).filter((file) => !!file.trim()).filter((file) => !this.#isSubspaceOwned(file)));
363
+ const files = await this.#changedSliceFiles(from, [from, "HEAD", "--", ...filter ? [filter] : scoped]);
373
364
  if (!files.length)
374
365
  return { files: [], patch: "" };
375
366
  return { files, patch: await this.#git(["diff", from, "HEAD", "--", ...files]) };
@@ -423,7 +414,7 @@ class Subspace {
423
414
  };
424
415
  }
425
416
  async#writeAnchorFile(branch) {
426
- await FileSys.writeJson(path.join(this.#clonePath, Subspace.anchorFile), {
417
+ await FileSys2.writeJson(path.join(this.#clonePath, Subspace.anchorFile), {
427
418
  workspace: this.#workspace.repoName,
428
419
  hubSha: await this.headSha(),
429
420
  branch,
@@ -460,7 +451,7 @@ class Subspace {
460
451
  async#savePatch(range, files, label) {
461
452
  const patchPath = path.join(this.#workspace.workspaceRoot, ".akan/subspace", `${this.name}-${label}.patch`);
462
453
  await mkdir(path.dirname(patchPath), { recursive: true });
463
- await FileSys.writeText(patchPath, await this.#git(["diff", range, "--", ...files]));
454
+ await FileSys2.writeText(patchPath, await this.#git(["diff", range, "--", ...files]));
464
455
  return { path: path.relative(this.#workspace.workspaceRoot, patchPath), absolute: patchPath, files };
465
456
  }
466
457
  }
@@ -582,7 +573,7 @@ class SubspaceConfig {
582
573
  static defaultPushableBranches = ["main", "develop", "debug"];
583
574
  static async from(workspace) {
584
575
  const configPath = path2.join(workspace.workspaceRoot, SubspaceConfig.fileName);
585
- if (!await FileSys.fileExists(configPath))
576
+ if (!await FileSys2.fileExists(configPath))
586
577
  return null;
587
578
  const input = await import(configPath).then((mod) => mod.default);
588
579
  return new SubspaceConfig(input);
@@ -725,7 +716,7 @@ class SubspaceRunner extends runner("subspace") {
725
716
  if (!asked)
726
717
  return { name: subspace.name, workspaceId, host, apps, libs: [], outcome: "cancelled", files: [] };
727
718
  const { libs } = await subspace.slice();
728
- const { CloudScript } = await import("./cloud.script-cm11xjdz.js");
719
+ const { CloudScript } = await import("./cloud.script-nemnzjpv.js");
729
720
  const { files } = await CommandContainer.get(CloudScript).uploadEnv(workspace, {
730
721
  host,
731
722
  workspaceId,
@@ -0,0 +1,8 @@
1
+ # <%= model %> Abstract
2
+
3
+ <One sentence: the value this scalar holds, and what embeds it.>
4
+
5
+ ## Rules
6
+
7
+ - <What a caller may assume about the value that the constant file cannot say: an order, a unit, a fixed value.>
8
+ - <Two to five of them. A single field's meaning goes in a trailing comment beside it in the constant file.>