@akanjs/cli 3.0.0-beta.8 → 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.
- package/.build-stamp +1 -1
- package/CodeAgent-2vmp9qe0.js +9 -0
- package/CodeAgentRpcHost-p7h6n2hp.js +7 -0
- package/CodeAgentRpcListener-7p7z5dd7.js +97 -0
- package/CodeTui-5ed6bg2a.js +1457 -0
- package/README.ko.md +83 -28
- package/README.md +70 -27
- package/TunnelShare-cwh9ezsm.js +9 -0
- package/{agent.command-0q17pnrs.js → agent.command-gn73rhe7.js} +8 -9
- package/akanCode.js +102 -0
- package/application.command-e968t0hw.js +246 -0
- package/{applicationBuildRunner-kxwbssgn.js → applicationBuildRunner-64taxseg.js} +121 -77
- package/buildBatch.proc.js +120 -32
- package/cloud.command-0jm301rr.js +95 -0
- package/cloud.script-nemnzjpv.js +20 -0
- package/code.command-18kcm7hq.js +154 -0
- package/commandManifest.json +1 -1
- package/consoleToStderr-h5bryj94.js +7 -0
- package/context.command-trd9bn18.js +48 -0
- package/context.runner-y5hwh673.js +21 -0
- package/{dependencyScanner-shs82ynp.js → dependencyScanner-06z2dmrr.js} +2 -2
- package/{devTui-m1atgkc9.js → devTui-q7k7bakd.js} +122 -40
- package/fileSys-wzzrntkw.js +9 -0
- package/guideline.command-bdmdn2de.js +42 -0
- package/guidelines/agentRule/agentRule.instruction.md +154 -26
- package/guidelines/conventions/conventions.instruction.md +134 -48
- package/guidelines/mcpRule/mcpRule.instruction.md +7 -5
- package/guidelines/queryRule/queryRule.instruction.md +21 -7
- package/guidelines/runtimeRule/runtimeRule.instruction.md +291 -6
- package/guidelines/ssrRule/ssrRule.instruction.md +4 -2
- package/guidelines/transportRule/transportRule.instruction.md +10 -8
- package/guidelines/workspaceOnboarding/workspaceOnboarding.instruction.md +20 -0
- package/guidelines/workspaceRecipes/workspaceRecipes.instruction.md +66 -3
- package/incrementalBuilder.proc.js +564 -109
- package/index-0wpybxtr.js +2729 -0
- package/{index-kcxmv48c.js → index-2ahj896a.js} +45 -79
- package/index-2wc41k5w.js +49 -0
- package/index-3hx1z96c.js +87 -0
- package/{index-2tv3fdx4.js → index-3w94e7n3.js} +1228 -1458
- package/{index-hwkygn9s.js → index-48k96p55.js} +325 -36
- package/index-4npz05nm.js +187 -0
- package/{index-q0y2jq9j.js → index-52w0fvh8.js} +21 -4
- package/index-6he6gvfg.js +4300 -0
- package/index-7md2g9vw.js +6 -0
- package/{index-04bk599g.js → index-93j60em0.js} +10 -17
- package/index-94s080qm.js +266 -0
- package/index-9dp031by.js +12 -0
- package/{index-ktb5njgy.js → index-9z771ptz.js} +49 -99
- package/{index-y6n8nmny.js → index-abxpehf8.js} +75 -203
- package/{index-3d9y066c.js → index-bjwxs904.js} +9 -8
- package/{index-9077wzd2.js → index-by9pxkr0.js} +43 -26
- package/index-c6eggbee.js +123 -0
- package/index-d2242ksy.js +99 -0
- package/index-drehwpdt.js +1119 -0
- package/{index-92zztqy1.js → index-dy0jx9ay.js} +2 -17
- package/{index-ffvk10zz.js → index-f3c78nd6.js} +107 -241
- package/index-gbggbqcp.js +1058 -0
- package/{index-by5gpp8x.js → index-hj6w4ka1.js} +2 -1
- package/index-hsmtysz4.js +53 -0
- package/{index-j9yhg404.js → index-jmrn54tt.js} +4 -7
- package/{index-a64esmtb.js → index-kcdj1z5v.js} +523 -1022
- package/{index-d43r08tf.js → index-kqabytj7.js} +77 -150
- package/{index-vyvre9en.js → index-mbxbhay5.js} +6 -2
- package/{index-vxtad5bd.js → index-mgxpx3f2.js} +5 -8
- package/index-p6pbz9n5.js +25 -0
- package/{index-sj5vxr8f.js → index-p993efkq.js} +981 -897
- package/{index-2xp0kfkt.js → index-pmhqttv3.js} +37 -25
- package/index-tqm7jjcn.js +700 -0
- package/{index-0g7r8xz3.js → index-vpj42xwt.js} +114 -232
- package/index-wa5jbxj1.js +760 -0
- package/{index-k2j1hc4r.js → index-wprnsjms.js} +12 -12
- package/{index-gyd220p2.js → index-y3mphr1m.js} +554 -577
- package/index-yg7jhz8x.js +69 -0
- package/{index-h7yw2z9n.js → index-zzvvc845.js} +8 -14
- package/index.js +23 -22
- package/{library.command-qzzbj6d8.js → library.command-9h749a1x.js} +4 -5
- package/{localRegistry.command-vyrgg4xw.js → localRegistry.command-2bqqhxyz.js} +23 -43
- package/{module.command-rq5hq3ax.js → module.command-nv8nzgft.js} +8 -12
- package/{package.command-d9hd6v9n.js → package.command-mq4bggp6.js} +6 -7
- package/package.json +15 -7
- package/{page.command-126d56te.js → page.command-j639n707.js} +4 -5
- package/{primitive.command-taxd1538.js → primitive.command-kz6849th.js} +9 -23
- package/prompter-76k56b37.js +8 -0
- package/{quality.command-6gsna0a3.js → quality.command-4hyabwkb.js} +205 -377
- package/{recipeScanner-fpagtgc1.js → recipeScanner-4b69ybe0.js} +1 -1
- package/{repair.command-tq2dgy72.js → repair.command-a9awpbqr.js} +11 -35
- package/{routeSourceValidator-apyqw7wk.js → routeSourceValidator-7ycnfz0e.js} +0 -6
- package/{scalar.command-3bcmrrkv.js → scalar.command-ma4fpm9m.js} +7 -11
- package/skills/akan-code/SKILL.md +130 -0
- package/skills/akan-debug/SKILL.md +42 -0
- package/skills/akan-deps/SKILL.md +50 -0
- package/skills/akan-err/SKILL.md +39 -0
- package/skills/akan-module/SKILL.md +45 -0
- package/skills/akan-scaffold/SKILL.md +76 -0
- package/skills/akan-scalar/SKILL.md +52 -0
- package/skills/akan-service/SKILL.md +34 -0
- package/skills/akan-store/SKILL.md +67 -0
- package/skills/akan-ui/SKILL.md +63 -0
- package/skills/akan-validate/SKILL.md +44 -0
- package/{subspace.command-eddzqpx3.js → subspace.command-3k2pmrs4.js} +53 -62
- package/templates/__scalar/__model__/__model__.abstract.md.template +8 -0
- package/templates/app/page/_index.tsx +257 -258
- package/templates/app/page/_layout.tsx +5 -1
- package/templates/app/page/_overrides.tsx +12 -0
- package/templates/app/page/styles.css.template +316 -14
- package/templates/app/public/apple-touch-icon.png +0 -0
- package/templates/app/public/favicon.ico +0 -0
- package/templates/app/public/jelly/cloud.webp +0 -0
- package/templates/app/public/jelly/comet.webp +0 -0
- package/templates/app/public/jelly/moon.webp +0 -0
- package/templates/app/public/jelly/planet.webp +0 -0
- package/templates/app/public/jelly/rocket.webp +0 -0
- package/templates/app/public/jelly/star.webp +0 -0
- package/templates/app/public/jelly/sugar.webp +0 -0
- package/templates/app/public/logo.png +0 -0
- package/templates/app/ui/Recipe/index.ts +9 -0
- package/templates/app/ui/Recipe/jellyButton.ts +63 -0
- package/templates/appSample/lib/__scalar/workHistory/workHistory.abstract.ts +8 -39
- package/templates/appSample/lib/_noti/noti.abstract.ts +6 -29
- package/templates/appSample/lib/task/Task.Unit.tsx +1 -1
- package/templates/appSample/lib/task/Task.Util.tsx +10 -13
- package/templates/appSample/lib/task/Task.View.tsx +2 -2
- package/templates/appSample/lib/task/Task.Zone.tsx +5 -2
- package/templates/appSample/lib/task/task.abstract.ts +12 -32
- package/templates/appSample/lib/task/task.signal.spec.ts +1 -15
- package/templates/appSample/lib/task/task.signal.test.ts +15 -1
- package/templates/appSample/lib/task/task.signal.ts +10 -8
- package/templates/appSample/page/task/[taskId]/_index.tsx +6 -6
- package/templates/appSample/page/task/[taskId]/edit.tsx +5 -4
- package/templates/appSample/page/task/_index.tsx +4 -3
- package/templates/appSample/page/task/_layout.tsx +1 -1
- package/templates/appSample/page/task/new.tsx +5 -4
- package/templates/appSample/srvkit/{AuthGuard.ts → guards.ts} +7 -4
- package/templates/appSample/srvkit/{SessionInternalArg.ts → internalArgs.ts} +6 -4
- package/templates/appSample/ui/GlobalLoading.tsx +2 -1
- package/templates/appSample/ui/QuantityControl.tsx +3 -3
- package/templates/client.ts +7 -30
- package/templates/crudPages/[__model__Id]/edit/page.tsx +2 -1
- package/templates/crudPages/[__model__Id]/page.tsx +2 -1
- package/templates/crudPages/new/page.tsx +2 -1
- package/templates/crudPages/page.tsx +2 -1
- package/templates/crudSinglePage/page.tsx +2 -1
- package/templates/lib/__lib/lib.dictionary.ts +0 -2
- package/templates/lib/__lib/lib.document.ts +0 -2
- package/templates/lib/sig.ts +8 -30
- package/templates/localDev/docker-compose.yaml.template +3 -14
- package/templates/module/__model__.signal.ts +12 -4
- package/templates/pkgRoot/tsconfig.json.template +2 -7
- package/templates/server.ts +1 -3
- package/templates/service/__model__.abstract.md.template +8 -0
- package/templates/workspaceRoot/.gitignore.template +0 -1
- package/templates/workspaceRoot/AGENTS.md.template +6 -0
- package/templates/workspaceRoot/package.json.template +1 -3
- package/tunnel.command-xwr46b66.js +129 -0
- package/{typeChecker-3xgrzc2v.js → typeChecker-px49mezc.js} +1 -1
- package/typecheck.proc.js +1 -1
- package/workflow.command-t7wqe4h7.js +79 -0
- package/{workspace.command-06wvstgr.js → workspace.command-4derhzrh.js} +79 -109
- package/abstractCompactor-gmkc0z8j.js +0 -102
- package/application.command-18y0s485.js +0 -204
- package/applicationReleasePackager-4yasxwv9.js +0 -244
- package/capacitorApp-36rbr858.js +0 -12
- package/cloud.command-av97ndh6.js +0 -94
- package/cloud.script-cm11xjdz.js +0 -20
- package/context.command-5v37ef3n.js +0 -81
- package/devTuiView-0hc2emhe.js +0 -9
- package/getDirname-dr3jz1xg.js +0 -7
- package/guideline.command-4pz9g1rt.js +0 -360
- package/guidelines/agentRule/agentRule.generate.json +0 -31
- package/guidelines/componentRule/componentRule.generate.json +0 -31
- package/guidelines/conventions/conventions.generate.json +0 -25
- package/guidelines/cssRule/cssRule.generate.json +0 -31
- package/guidelines/docPageRule/docPageRule.generate.json +0 -31
- package/guidelines/docPageRule/docPageRule.instruction.md +0 -25
- package/guidelines/docSyncRule/docSyncRule.generate.json +0 -31
- package/guidelines/docSyncRule/docSyncRule.instruction.md +0 -25
- package/guidelines/enumConstant/enumConstant.generate.json +0 -31
- package/guidelines/enumConstant/enumConstant.instruction.md +0 -28
- package/guidelines/fieldRule/fieldRule.generate.json +0 -31
- package/guidelines/framework/framework.generate.json +0 -32
- package/guidelines/mcpRule/mcpRule.generate.json +0 -35
- package/guidelines/modelConstant/modelConstant.generate.json +0 -32
- package/guidelines/modelDictionary/modelDictionary.generate.json +0 -32
- package/guidelines/modelDocument/modelDocument.generate.json +0 -31
- package/guidelines/modelService/modelService.generate.json +0 -31
- package/guidelines/modelSignal/modelSignal.generate.json +0 -31
- package/guidelines/modelStore/modelStore.generate.json +0 -31
- package/guidelines/modelTemplate/modelTemplate.generate.json +0 -31
- package/guidelines/modelUnit/modelUnit.generate.json +0 -31
- package/guidelines/modelUtil/modelUtil.generate.json +0 -31
- package/guidelines/modelView/modelView.generate.json +0 -31
- package/guidelines/modelZone/modelZone.generate.json +0 -31
- package/guidelines/moduleCodegen/moduleCodegen.generate.json +0 -31
- package/guidelines/moduleCodegen/moduleCodegen.instruction.md +0 -28
- package/guidelines/moduleOverview/moduleOverview.generate.json +0 -31
- package/guidelines/queryRule/queryRule.generate.json +0 -31
- package/guidelines/recipeRule/recipeRule.generate.json +0 -31
- package/guidelines/runtimeRule/runtimeRule.generate.json +0 -25
- package/guidelines/scalarConstant/scalarConstant.generate.json +0 -32
- package/guidelines/scalarDictionary/scalarDictionary.generate.json +0 -32
- package/guidelines/scalarModule/scalarModule.generate.json +0 -31
- package/guidelines/sharedUiUsage/sharedUiUsage.generate.json +0 -31
- package/guidelines/sharedUiUsage/sharedUiUsage.instruction.md +0 -25
- package/guidelines/ssrRule/ssrRule.generate.json +0 -31
- package/guidelines/transportRule/transportRule.generate.json +0 -31
- package/guidelines/utilUiUsage/utilUiUsage.generate.json +0 -31
- package/guidelines/utilUiUsage/utilUiUsage.instruction.md +0 -25
- package/guidelines/workspaceOnboarding/workspaceOnboarding.generate.json +0 -24
- package/guidelines/workspaceRecipes/workspaceRecipes.generate.json +0 -33
- package/index-47pj3aee.js +0 -76
- package/index-5ystnsd5.js +0 -841
- package/index-6wwzs1bc.js +0 -7
- package/index-a17x49vg.js +0 -229
- package/index-m0xccmmm.js +0 -228
- package/index-meh558jy.js +0 -65
- package/index-rtxxan88.js +0 -471
- package/index-te54x9zm.js +0 -338
- package/index-w8kmx1b6.js +0 -2176
- package/index-wt7qrtd4.js +0 -1516
- package/templates/__scalar/__model__/__model__.abstract.md +0 -28
- package/templates/module/__model__.abstract.md +0 -28
- package/templates/service/__model__.abstract.md +0 -28
- package/templates/workspaceRoot/patches/react-dom@19.2.7.patch.template +0 -62
- 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
|
-
|
|
4
|
-
} from "./index-
|
|
3
|
+
FileSys2
|
|
4
|
+
} from "./index-52w0fvh8.js";
|
|
5
5
|
import {
|
|
6
6
|
GlobalConfig
|
|
7
|
-
} from "./index-
|
|
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-
|
|
14
|
+
} from "./index-vpj42xwt.js";
|
|
15
15
|
import {
|
|
16
16
|
Executor,
|
|
17
17
|
WorkspaceExecutor,
|
|
18
18
|
AppExecutor
|
|
19
|
-
} from "./index-
|
|
20
|
-
import"./index-6wwzs1bc.js";
|
|
19
|
+
} from "./index-3w94e7n3.js";
|
|
21
20
|
import {
|
|
22
21
|
SlicePlanner
|
|
23
|
-
} from "./index-
|
|
22
|
+
} from "./index-jmrn54tt.js";
|
|
24
23
|
import {
|
|
25
24
|
LibSource
|
|
26
|
-
} from "./index-
|
|
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
|
-
|
|
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#
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
201
|
-
|
|
202
|
-
|
|
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
|
|
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
|
|
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
|
|
235
|
+
if (await FileSys2.fileExists(envPath))
|
|
228
236
|
return;
|
|
229
237
|
const lines = Object.entries(this.#localEnv()).map(([key, value]) => `${key}=${value}`);
|
|
230
|
-
await
|
|
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
|
|
244
|
+
if (!await FileSys2.fileExists(manifestPath))
|
|
237
245
|
return [];
|
|
238
|
-
const rootPackageJson = await
|
|
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
|
|
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
|
|
277
|
+
if (!await FileSys2.fileExists(gitignorePath))
|
|
270
278
|
return;
|
|
271
279
|
const [begin = "", end = ""] = Subspace.secretsMarkers;
|
|
272
|
-
const lines = (await
|
|
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
|
|
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
|
|
299
|
+
if (!await FileSys2.fileExists(filePath))
|
|
293
300
|
continue;
|
|
294
|
-
const content = await
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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-
|
|
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.>
|