@akanjs/cli 3.0.0-beta.2 → 3.0.0-beta.20

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 (187) hide show
  1. package/.build-stamp +1 -1
  2. package/CodeAgent-awv258wb.js +9 -0
  3. package/CodeAgentRpcHost-p7h6n2hp.js +7 -0
  4. package/CodeAgentRpcListener-7p7z5dd7.js +97 -0
  5. package/CodeTui-1nqh35ez.js +1457 -0
  6. package/README.md +3 -0
  7. package/TunnelShare-scf4e4pd.js +9 -0
  8. package/{agent.command-d2gfxfck.js → agent.command-1s9ag7sb.js} +8 -9
  9. package/akanCode.js +102 -0
  10. package/{application.command-pj2m7584.js → application.command-49hwy6va.js} +51 -78
  11. package/{applicationBuildRunner-kxwbssgn.js → applicationBuildRunner-ybyp97pv.js} +63 -64
  12. package/{applicationReleasePackager-4yasxwv9.js → applicationReleasePackager-esnrphw8.js} +40 -60
  13. package/buildBatch.proc.js +19 -24
  14. package/{capacitorApp-36rbr858.js → capacitorApp-1synz4en.js} +3 -4
  15. package/cloud.command-d7rqtzt9.js +94 -0
  16. package/cloud.script-bkbpcr96.js +19 -0
  17. package/code.command-nn48xzej.js +154 -0
  18. package/commandManifest.json +1 -1
  19. package/consoleToStderr-h5bryj94.js +7 -0
  20. package/context.command-s3mcmvpa.js +48 -0
  21. package/context.runner-k99nb5pk.js +21 -0
  22. package/{dependencyScanner-shs82ynp.js → dependencyScanner-ye2e6pdz.js} +2 -2
  23. package/{devTui-40ggmd99.js → devTui-q7k7bakd.js} +122 -40
  24. package/fileSys-5ngz93ga.js +9 -0
  25. package/guideline.command-sct4qnt5.js +42 -0
  26. package/guidelines/agentRule/agentRule.instruction.md +206 -25
  27. package/guidelines/conventions/conventions.instruction.md +79 -24
  28. package/guidelines/fieldRule/fieldRule.instruction.md +1 -0
  29. package/guidelines/mcpRule/mcpRule.instruction.md +7 -5
  30. package/guidelines/queryRule/queryRule.instruction.md +17 -7
  31. package/guidelines/runtimeRule/runtimeRule.instruction.md +74 -0
  32. package/guidelines/ssrRule/ssrRule.instruction.md +4 -2
  33. package/guidelines/transportRule/transportRule.instruction.md +51 -0
  34. package/guidelines/workspaceRecipes/workspaceRecipes.instruction.md +66 -3
  35. package/incrementalBuilder.proc.js +31 -48
  36. package/{index-h7yw2z9n.js → index-0bq14vr1.js} +8 -14
  37. package/index-13ffe9wp.js +69 -0
  38. package/index-22d57v4c.js +266 -0
  39. package/{index-fz5q3gd7.js → index-31yr441d.js} +10 -17
  40. package/index-3hx1z96c.js +87 -0
  41. package/index-3sq321ak.js +53 -0
  42. package/{index-kcxmv48c.js → index-3t7g9e4r.js} +45 -79
  43. package/{index-q0y2jq9j.js → index-5b6tavv3.js} +6 -3
  44. package/{index-47pj3aee.js → index-5e0n4hqx.js} +11 -12
  45. package/{index-259dsnz3.js → index-5pc1qrbv.js} +5 -8
  46. package/index-7md2g9vw.js +6 -0
  47. package/index-8mna0947.js +187 -0
  48. package/{index-zry5nz81.js → index-8sf7wmdr.js} +51 -30
  49. package/{index-2tv3fdx4.js → index-9d9bgcx4.js} +342 -765
  50. package/index-9dp031by.js +12 -0
  51. package/{index-ne2qy0nk.js → index-ahemdr5n.js} +12 -12
  52. package/{index-ws5e0j51.js → index-as7xmyz8.js} +40 -24
  53. package/index-axzjaqve.js +1118 -0
  54. package/{index-3d9y066c.js → index-bjwxs904.js} +9 -8
  55. package/index-c6eggbee.js +123 -0
  56. package/index-d2242ksy.js +99 -0
  57. package/{index-a64esmtb.js → index-ddxvdd1p.js} +169 -305
  58. package/{index-92zztqy1.js → index-dy0jx9ay.js} +2 -17
  59. package/{index-pnqbkf5p.js → index-dzf3r49a.js} +6 -2
  60. package/{index-w8kmx1b6.js → index-fgq75haa.js} +198 -290
  61. package/{index-by5gpp8x.js → index-hj6w4ka1.js} +2 -1
  62. package/{index-hwkygn9s.js → index-khpfpcsd.js} +302 -33
  63. package/{index-wt7qrtd4.js → index-mxav6j3c.js} +93 -232
  64. package/{index-gyd220p2.js → index-myhc3c1j.js} +554 -577
  65. package/{index-600zhnve.js → index-nv5976x3.js} +56 -197
  66. package/index-p6pbz9n5.js +25 -0
  67. package/{index-4edzf1gr.js → index-przybvy5.js} +77 -150
  68. package/index-smme4r47.js +49 -0
  69. package/{index-mak3cd28.js → index-w2zdcw3k.js} +137 -255
  70. package/index-wa5jbxj1.js +760 -0
  71. package/{index-v0qh106m.js → index-ww4z397j.js} +527 -723
  72. package/{index-j9yhg404.js → index-xqjwd384.js} +4 -7
  73. package/{index-7737gxcp.js → index-xqpzbab5.js} +107 -241
  74. package/index-xsjh6nzh.js +2729 -0
  75. package/{index-ktb5njgy.js → index-ygn77a5w.js} +47 -99
  76. package/index-ztp3e73t.js +700 -0
  77. package/index.js +23 -22
  78. package/{library.command-nbsky672.js → library.command-1gx04jgn.js} +4 -5
  79. package/{localRegistry.command-yeq57wjr.js → localRegistry.command-zndxj1q6.js} +22 -42
  80. package/{module.command-29vqzrh8.js → module.command-3waddk95.js} +8 -11
  81. package/{package.command-axv0abmn.js → package.command-zrc7br1y.js} +6 -7
  82. package/package.json +14 -5
  83. package/{page.command-vs9s3zrh.js → page.command-vpjgardh.js} +4 -5
  84. package/{primitive.command-cbxmqbtc.js → primitive.command-sz84tk4t.js} +9 -22
  85. package/prompter-ptaa3nk0.js +8 -0
  86. package/{quality.command-gmbs7nws.js → quality.command-g869hafs.js} +203 -376
  87. package/{recipeScanner-fpagtgc1.js → recipeScanner-4b69ybe0.js} +1 -1
  88. package/{repair.command-bqsjptsj.js → repair.command-chsnjvvv.js} +11 -35
  89. package/{routeSourceValidator-apyqw7wk.js → routeSourceValidator-7ycnfz0e.js} +0 -6
  90. package/{scalar.command-6k1vhe9s.js → scalar.command-2v21newx.js} +7 -10
  91. package/skills/akan-code/SKILL.md +130 -0
  92. package/skills/akan-debug/SKILL.md +42 -0
  93. package/skills/akan-deps/SKILL.md +50 -0
  94. package/skills/akan-err/SKILL.md +39 -0
  95. package/skills/akan-module/SKILL.md +45 -0
  96. package/skills/akan-scaffold/SKILL.md +76 -0
  97. package/skills/akan-scalar/SKILL.md +52 -0
  98. package/skills/akan-service/SKILL.md +34 -0
  99. package/skills/akan-store/SKILL.md +67 -0
  100. package/skills/akan-ui/SKILL.md +63 -0
  101. package/skills/akan-validate/SKILL.md +44 -0
  102. package/{subspace.command-fwjfcts7.js → subspace.command-ckcbwfp1.js} +138 -67
  103. package/templates/__scalar/__model__/__model__.abstract.md.template +8 -0
  104. package/templates/appSample/lib/__scalar/workHistory/workHistory.abstract.ts +8 -39
  105. package/templates/appSample/lib/_noti/noti.abstract.ts +6 -29
  106. package/templates/appSample/lib/task/task.abstract.ts +12 -32
  107. package/templates/appSample/lib/task/task.signal.spec.ts +15 -18
  108. package/templates/appSample/lib/task/task.signal.test.ts +15 -1
  109. package/templates/appSample/lib/task/task.signal.ts +1 -1
  110. package/templates/appSample/srvkit/{AuthGuard.ts → guards.ts} +7 -4
  111. package/templates/appSample/srvkit/{SessionInternalArg.ts → internalArgs.ts} +6 -4
  112. package/templates/client.ts +7 -30
  113. package/templates/crudPages/[__model__Id]/edit/page.tsx +2 -1
  114. package/templates/crudPages/[__model__Id]/page.tsx +2 -1
  115. package/templates/crudPages/new/page.tsx +2 -1
  116. package/templates/crudPages/page.tsx +2 -1
  117. package/templates/crudSinglePage/page.tsx +2 -1
  118. package/templates/lib/__lib/lib.dictionary.ts +0 -2
  119. package/templates/lib/__lib/lib.document.ts +0 -2
  120. package/templates/lib/sig.ts +8 -30
  121. package/templates/localDev/docker-compose.yaml.template +3 -14
  122. package/templates/module/__model__.signal.ts +12 -4
  123. package/templates/pkgRoot/tsconfig.json.template +2 -7
  124. package/templates/server.ts +0 -2
  125. package/templates/service/__model__.abstract.md.template +8 -0
  126. package/tunnel.command-kd2yr4fx.js +129 -0
  127. package/{typeChecker-3xgrzc2v.js → typeChecker-px49mezc.js} +1 -1
  128. package/typecheck.proc.js +1 -1
  129. package/workflow.command-44252cqv.js +79 -0
  130. package/{workspace.command-8s050m0h.js → workspace.command-fqcztt5p.js} +77 -109
  131. package/abstractCompactor-mjh5ppsq.js +0 -101
  132. package/cloud.command-jshwdq22.js +0 -93
  133. package/context.command-ez8a0zyz.js +0 -80
  134. package/devTuiView-c08ccbat.js +0 -9
  135. package/getDirname-dr3jz1xg.js +0 -7
  136. package/guideline.command-xmqwx8zm.js +0 -359
  137. package/guidelines/agentRule/agentRule.generate.json +0 -31
  138. package/guidelines/componentRule/componentRule.generate.json +0 -31
  139. package/guidelines/conventions/conventions.generate.json +0 -25
  140. package/guidelines/cssRule/cssRule.generate.json +0 -31
  141. package/guidelines/docPageRule/docPageRule.generate.json +0 -31
  142. package/guidelines/docPageRule/docPageRule.instruction.md +0 -25
  143. package/guidelines/docSyncRule/docSyncRule.generate.json +0 -31
  144. package/guidelines/docSyncRule/docSyncRule.instruction.md +0 -25
  145. package/guidelines/enumConstant/enumConstant.generate.json +0 -31
  146. package/guidelines/enumConstant/enumConstant.instruction.md +0 -28
  147. package/guidelines/fieldRule/fieldRule.generate.json +0 -31
  148. package/guidelines/framework/framework.generate.json +0 -32
  149. package/guidelines/mcpRule/mcpRule.generate.json +0 -35
  150. package/guidelines/modelConstant/modelConstant.generate.json +0 -32
  151. package/guidelines/modelDictionary/modelDictionary.generate.json +0 -32
  152. package/guidelines/modelDocument/modelDocument.generate.json +0 -31
  153. package/guidelines/modelService/modelService.generate.json +0 -31
  154. package/guidelines/modelSignal/modelSignal.generate.json +0 -31
  155. package/guidelines/modelStore/modelStore.generate.json +0 -31
  156. package/guidelines/modelTemplate/modelTemplate.generate.json +0 -31
  157. package/guidelines/modelUnit/modelUnit.generate.json +0 -31
  158. package/guidelines/modelUtil/modelUtil.generate.json +0 -31
  159. package/guidelines/modelView/modelView.generate.json +0 -31
  160. package/guidelines/modelZone/modelZone.generate.json +0 -31
  161. package/guidelines/moduleCodegen/moduleCodegen.generate.json +0 -31
  162. package/guidelines/moduleCodegen/moduleCodegen.instruction.md +0 -28
  163. package/guidelines/moduleOverview/moduleOverview.generate.json +0 -31
  164. package/guidelines/queryRule/queryRule.generate.json +0 -31
  165. package/guidelines/recipeRule/recipeRule.generate.json +0 -31
  166. package/guidelines/runtimeRule/runtimeRule.generate.json +0 -25
  167. package/guidelines/scalarConstant/scalarConstant.generate.json +0 -32
  168. package/guidelines/scalarDictionary/scalarDictionary.generate.json +0 -32
  169. package/guidelines/scalarModule/scalarModule.generate.json +0 -31
  170. package/guidelines/sharedUiUsage/sharedUiUsage.generate.json +0 -31
  171. package/guidelines/sharedUiUsage/sharedUiUsage.instruction.md +0 -25
  172. package/guidelines/ssrRule/ssrRule.generate.json +0 -31
  173. package/guidelines/transportRule/transportRule.generate.json +0 -31
  174. package/guidelines/utilUiUsage/utilUiUsage.generate.json +0 -31
  175. package/guidelines/utilUiUsage/utilUiUsage.instruction.md +0 -25
  176. package/guidelines/workspaceOnboarding/workspaceOnboarding.generate.json +0 -24
  177. package/guidelines/workspaceRecipes/workspaceRecipes.generate.json +0 -33
  178. package/index-2hq52jq3.js +0 -465
  179. package/index-6wwzs1bc.js +0 -7
  180. package/index-8pm30m73.js +0 -841
  181. package/index-kn1h7p1x.js +0 -560
  182. package/index-meh558jy.js +0 -65
  183. package/index-rwg2aa4f.js +0 -229
  184. package/templates/__scalar/__model__/__model__.abstract.md +0 -28
  185. package/templates/module/__model__.abstract.md +0 -28
  186. package/templates/service/__model__.abstract.md +0 -28
  187. package/workflow.command-nwpcdc8p.js +0 -107
@@ -3,45 +3,14 @@ import type { AppInfo, LibInfo } from "akanjs";
3
3
  export default function getContent(scanInfo: AppInfo | LibInfo | null, dict: { appName: string }) {
4
4
  return {
5
5
  filename: "workHistory.abstract.md",
6
- content: `# WorkHistory Scalar Abstract
7
-
8
- ## Akan.js Module Pattern
9
-
10
- WorkHistory is a **scalar module** — a reusable value type under lib/__scalar/.
11
- Scalar modules are embedded into models via field([ScalarType]), not stored independently.
12
- They live under the double-underscore prefix to distinguish them from database models.
13
-
14
- Standard scalar layers:
15
- - workHistory.constant.ts — enum (WorkHistoryAction) + entry class (WorkHistory) defined via via() + enumOf()
16
- - workHistory.dictionary.ts — i18n via scalarDictionary() (fewer layers than modelDictionary)
17
- - workHistory.abstract.md — embedding rules and intent
18
-
19
- ## Convention: lib/__scalar/<type>/ directory naming
20
-
21
- - Scalar modules use double-underscore prefix: lib/__scalar/workHistory/
22
- - Files drop the underscores: workHistory.constant.ts, not __workHistory.constant.ts
23
- - Models embed scalars via field([WorkHistory]) — list-of-scalar embedding
24
-
25
- ## Embedding Example (in task.constant.ts)
26
-
27
- TaskObject embeds a list of WorkHistory:
28
- import { WorkHistory } from "../../__scalar/workHistory/workHistory.constant";
29
- export class TaskObject extends via(TaskInput, (field) => ({
30
- workHistory: field([WorkHistory]),
31
- }))
32
-
33
- Each status change (create, start, complete) pushes a new entry.
34
-
35
- ## Agent Notes
36
-
37
- - Read this abstract before modifying the WorkHistory scalar.
38
- - Update when new action types or fields are added.
39
- - Do not duplicate workHistory fields in individual models; always reuse this scalar.
40
-
41
- ## Related Modules
42
-
43
- - lib/task/ — database module embedding workHistory as a list field
44
- - lib/_noti/ — pure service module (different module type, no scalar usage)
6
+ content: `# workHistory Abstract
7
+ One step in a task's history: which move happened, when, and an optional note.
8
+
9
+ ## Rules
10
+ - Entries are only appended — by \`Task\`'s chain methods and by the task service on create — and never edited or
11
+ removed, so the list reads as the task's moves in order.
12
+ - \`at\` is the moment the entry was built when the writer gives none.
13
+ - \`note\` is empty on every entry the app writes itself.
45
14
  `,
46
15
  };
47
16
  }
@@ -3,36 +3,13 @@ import type { AppInfo, LibInfo } from "akanjs";
3
3
  export default function getContent(scanInfo: AppInfo | LibInfo | null, dict: { appName: string }) {
4
4
  return {
5
5
  filename: "noti.abstract.md",
6
- content: `# Noti Abstract
6
+ content: `# noti Service Abstract
7
+ Noti holds the in-app notification list on the client: the messages the app shows, an unread count, and dismissal.
7
8
 
8
- ## Akan.js Module Pattern
9
-
10
- Noti is a **pure service module** — not a database module. It demonstrates the Akan.js convention
11
- for modules that orchestrate behavior without owning a persistent document model.
12
-
13
- Both module types follow the same layer pattern, but service modules have fewer layers:
14
- no constant.ts (no data shape), no document.ts (no DB queries), no UI components.
15
-
16
- - noti.service.ts — business logic via serve("noti" as const, ...) (named service, no DB binding)
17
- - noti.signal.ts — real-time pubsub endpoint bound via endpoint(srv.noti, ...)
18
- - noti.store.ts — client state via store("noti" as const, ...) (named store, no signal binding)
19
- - noti.dictionary.ts — i18n via serviceDictionary() (no model field labels)
20
-
21
- ## Convention: lib/_<service>/ directory naming
22
-
23
- - Service modules live under lib/_<service>/ — the underscore prefix signals "this is a service, not a database model."
24
- - Files drop the underscore: lib/_noti/noti.service.ts, not lib/_noti/_noti.service.ts.
25
- - Scan registers into the same barrel files: srv.ts, sig.ts, st.ts, dict.ts.
26
-
27
- ## Convention: named serve/store (no DB binding)
28
-
29
- - serve("noti" as const, ...) — service name passed as string, no db.<module> binding.
30
- - store("noti" as const, ...) — store name passed as string, no sig.<module> binding.
31
-
32
- ## Related Modules
33
-
34
- - lib/task/ — database module reference (compare structure differences)
35
- - lib/__scalar/workHistory/ — reusable scalar field modules (double-underscore prefix, embedded in models)
9
+ ## Rules
10
+ - Notifications live only in the client store; the server side declares nothing yet, so a reload starts empty.
11
+ - \`unreadCount\` counts additions since the last \`markAllRead()\`, not the list's length — removing a notification
12
+ does not lower it.
36
13
  `,
37
14
  };
38
15
  }
@@ -3,38 +3,18 @@ import type { AppInfo, LibInfo } from "akanjs";
3
3
  export default function getContent(scanInfo: AppInfo | LibInfo | null, dict: { appName: string }) {
4
4
  return {
5
5
  filename: "task.abstract.md",
6
- content: `# Task Abstract
7
-
8
- ## Akan.js Module Pattern
9
-
10
- Task is a **database-backed domain module** — the standard Akan.js pattern for persisted entities.
11
- This module demonstrates the full Akan.js module lifecycle:
12
- constant -> document -> service -> signal -> store -> UI.
13
-
14
- Every database module in Akan.js follows this layered architecture:
15
- - task.constant.ts — data shape (enum, Input, Object, Light, Full layers via via())
16
- - task.document.ts — DB queries, filters, sorts, indexes, Document chain methods
17
- - task.dictionary.ts — i18n labels, error messages, UI translations
18
- - task.service.ts — business logic orchestration bound to DB via serve(db.task, ...)
19
- - task.signal.ts — API surface (query, mutation, pubsub endpoints bound via endpoint(srv.task, ...))
20
- - task.store.ts — client state management bound to signal via store(sig.task, ...)
21
- - Task.*.tsx — UI components (Zone -> Load -> Unit / View / Template / Util)
22
-
23
- ## Convention: lib/<module>/ directory naming
24
-
25
- - Database modules live under lib/<module>/ (no underscore prefix)
26
- - Files follow <module>.<layer>.ts naming — e.g., task.constant.ts, task.service.ts
27
- - UI files use PascalCase with explicit suffix: Task.Zone.tsx, Task.Unit.tsx, Task.View.tsx
28
-
29
- ## Convention: scan-registered barrels
30
-
31
- akan sync auto-discovers each file and registers it in the corresponding barrel:
32
- cnst.ts, db.ts, dict.ts, srv.ts, sig.ts, st.ts
33
-
34
- ## Related Modules
35
-
36
- - lib/__scalar/workHistory/ — standard scalar modules with double-underscore prefix (reusable field sets)
37
- - lib/_noti/ — pure service module (compare structure differences: no constant/document, serve without DB binding)
6
+ content: `# task Abstract
7
+ A task is one unit of work that moves from todo through in progress to completed, and keeps a record of each move.
8
+
9
+ ## Rules
10
+ - A task only moves forward: \`start()\` needs \`todo\` and \`complete()\` needs \`inProgress\`; any other state throws.
11
+ - Every move appends one \`workHistory\` entry, and creation writes the first (\`created\`), so the history is never
12
+ empty and its last entry always matches \`status\`.
13
+ - Anyone may read a task; creating, changing, starting and completing one needs a signed-in caller. There is no
14
+ generated admin API (\`root: None\`).
15
+ - Removal is soft: a removed task keeps its row with \`removedAt\` set.
16
+
17
+ todo -> inProgress -> completed
38
18
  `,
39
19
  };
40
20
  }
@@ -1,16 +1,26 @@
1
1
  import type { AppInfo, LibInfo } from "akanjs";
2
2
 
3
- export default function getContent(scanInfo: AppInfo | LibInfo | null, dict: { appName: string }) {
4
- return `import { expect } from "bun:test";
5
- import type { DocumentModel } from "akanjs/constant";
6
- import { getOrSetupSignalTestFetch, sampleOf } from "akanjs/test";
3
+ export default function getContent(
4
+ scanInfo: AppInfo | LibInfo | null,
5
+ dict: { appName: string },
6
+ options: { libs?: string[] } = {},
7
+ ) {
8
+ // Task writes are guarded by SignedIn, so the fixtures sign a user in through libs/shared when it is installed.
9
+ const signsIn = options.libs?.includes("shared") ?? false;
10
+ return `${
11
+ signsIn
12
+ ? `import * as userSpec from "@libs/shared/lib/user/user.signal.spec";
13
+ `
14
+ : ""
15
+ }import type { DocumentModel } from "akanjs/constant";
16
+ import { ${signsIn ? "" : "getOrSetupSignalTestFetch, "}sampleOf } from "akanjs/test";
7
17
 
8
18
  import * as cnst from "../cnst";
9
19
  import type { fetch as appFetch } from "../useServer";
10
20
 
11
21
  type AppFetch = typeof appFetch;
12
22
 
13
- const getFetch = async () => await getOrSetupSignalTestFetch<AppFetch>();
23
+ const getFetch = async () => ${signsIn ? "(await userSpec.getUserAgentWithPassword<AppFetch>()).fetch" : "await getOrSetupSignalTestFetch<AppFetch>()"};
14
24
 
15
25
  export interface TaskAgent {
16
26
  task: cnst.Task;
@@ -27,13 +37,6 @@ export const createTask = async (overrides: Partial<DocumentModel<cnst.TaskInput
27
37
 
28
38
  const task = await fetch.createTask(taskInput);
29
39
 
30
- expect(task).toMatchObject({
31
- title: taskInput.title,
32
- content: taskInput.content,
33
- status: "todo",
34
- });
35
- expect(task.workHistory.map((entry) => entry.action)).toEqual(["created"]);
36
-
37
40
  return {
38
41
  task,
39
42
  fetch,
@@ -45,9 +48,6 @@ export const getStartedTask = async (overrides: Partial<DocumentModel<cnst.TaskI
45
48
  const agent = await createTask(overrides);
46
49
  const task = await agent.fetch.startTask(agent.task.id);
47
50
 
48
- expect(task.status).toBe("inProgress");
49
- expect(task.workHistory.map((entry) => entry.action)).toEqual(["created", "started"]);
50
-
51
51
  return {
52
52
  ...agent,
53
53
  task,
@@ -58,9 +58,6 @@ export const getCompletedTask = async (overrides: Partial<DocumentModel<cnst.Tas
58
58
  const agent = await getStartedTask(overrides);
59
59
  const task = await agent.fetch.completeTask(agent.task.id);
60
60
 
61
- expect(task.status).toBe("completed");
62
- expect(task.workHistory.map((entry) => entry.action)).toEqual(["created", "started", "completed"]);
63
-
64
61
  return {
65
62
  ...agent,
66
63
  task,
@@ -6,9 +6,23 @@ import { configureSignalTest } from "akanjs/test";
6
6
 
7
7
  import * as taskSpec from "./task.signal.spec";
8
8
 
9
- configureSignalTest({ databaseMode: "memory" });
9
+ configureSignalTest({ storage: "memory" });
10
10
 
11
11
  describe("Task signal smoke", () => {
12
+ test("creates a task in todo", async () => {
13
+ const { task, taskInput } = await taskSpec.createTask({ title: "Signal smoke task" });
14
+
15
+ expect(task).toMatchObject({ title: taskInput.title, content: taskInput.content, status: "todo" });
16
+ expect(task.workHistory.map((entry) => entry.action)).toEqual(["created"]);
17
+ });
18
+
19
+ test("starts a task", async () => {
20
+ const { task } = await taskSpec.getStartedTask({ title: "Signal smoke task" });
21
+
22
+ expect(task.status).toBe("inProgress");
23
+ expect(task.workHistory.map((entry) => entry.action)).toEqual(["created", "started"]);
24
+ });
25
+
12
26
  test("exposes custom task mutations over fetch", async () => {
13
27
  const { task } = await taskSpec.getCompletedTask({ title: "Signal smoke task" });
14
28
 
@@ -25,7 +25,7 @@ export class TaskInternal extends internal(srv.task, ({ interval }) => ({})) {}
25
25
  // Guards, per key: root = the generated admin API (runs any filter with caller-supplied args), get = reads,
26
26
  // cru = create/update/remove. root and cru are never Public; None here means "nobody until you name a guard".
27
27
  export class TaskSlice extends slice(srv.task, { guards: { root: None, get: Public, cru: SignedIn } }, (init) => ({
28
- inPublic: init().exec(function () {
28
+ inPublic: init({ guards: [Public] }).exec(function () {
29
29
  return this.taskService.queryAny();
30
30
  }),
31
31
  })) {}
@@ -2,14 +2,14 @@ import type { AppInfo, LibInfo } from "akanjs";
2
2
 
3
3
  export default function getContent(scanInfo: AppInfo | LibInfo | null, dict: { appName: string }) {
4
4
  return {
5
- filename: "AuthGuard.ts",
5
+ filename: "guards.ts",
6
6
  content: `import type { Guard, GuardScope, SignalContext } from "akanjs/signal";
7
7
 
8
- // ===== AuthGuard.ts =====
8
+ // ===== guards.ts =====
9
9
  // Convention: srvkit/ folder — server-only helpers, cannot import from client code.
10
10
  // Implements the Guard interface from akanjs/signal.
11
11
  // Guards are applied at endpoint/slice declaration: { guards: { root: SignedIn } }.
12
- // Naming: PascalCase .ts, static name property matches the guard identifier.
12
+ // Naming: camelCase file, PascalCase classes; each static name matches its guard identifier.
13
13
  // Scanned by akan sync into srvkit/index.ts barrel automatically.
14
14
 
15
15
  export class SignedIn implements Guard {
@@ -21,7 +21,10 @@ export class SignedIn implements Guard {
21
21
 
22
22
  canPass(context: SignalContext): boolean {
23
23
  // Reads the account the middleware resolved, so the same guard answers over HTTP, a websocket and MCP alike.
24
- return !!context.get<{ id?: string }>("account");
24
+ // A guest still has an account — libs/shared resolves one to \`{ appName, environment }\` — so only an
25
+ // identity on it proves a sign-in: your own middleware's \`id\`, or libs/shared's \`self\` (user) / \`me\` (admin).
26
+ const account = context.get<{ id?: string; self?: { id?: string }; me?: { id?: string } }>("account");
27
+ return !!(account?.id ?? account?.self?.id ?? account?.me?.id);
25
28
  }
26
29
  }
27
30
 
@@ -2,22 +2,24 @@ import type { AppInfo, LibInfo } from "akanjs";
2
2
 
3
3
  export default function getContent(scanInfo: AppInfo | LibInfo | null, dict: { appName: string }) {
4
4
  return {
5
- filename: "SessionInternalArg.ts",
5
+ filename: "internalArgs.ts",
6
6
  content: `import type { InternalArg, SignalContext } from "akanjs/signal";
7
7
 
8
- // ===== SessionInternalArg.ts =====
8
+ // ===== internalArgs.ts =====
9
9
  // Convention: srvkit/ folder — server-only helpers.
10
10
  // Implements the InternalArg interface from akanjs/signal.
11
11
  // InternalArg is an auto-injected argument for resolveField/endpoint .with() chains.
12
12
  // Appended to a query/mutation via: .with(CurrentUserId, { nullable: true }).exec(...)
13
- // Naming: PascalCase .ts, class name = arg identifier.
13
+ // Naming: camelCase file, PascalCase classes; each class name is its arg identifier.
14
14
  // Scanned by akan sync into srvkit/index.ts barrel automatically.
15
15
 
16
16
  export class CurrentUserId implements InternalArg<string | null> {
17
17
  getArg(context: SignalContext): string | null {
18
18
  // \`context.get\` reads what the account middleware resolved, on every transport. Branching on
19
19
  // \`getHttpContext()\` would return null for the same caller arriving over a websocket or MCP.
20
- return context.get<{ id?: string }>("account")?.id ?? null;
20
+ // An app's own middleware may put the id on the account itself; libs/shared puts it on \`self\` or \`me\`.
21
+ const account = context.get<{ id?: string; self?: { id?: string }; me?: { id?: string } }>("account");
22
+ return account?.id ?? account?.self?.id ?? account?.me?.id ?? null;
21
23
  }
22
24
  }
23
25
 
@@ -4,36 +4,13 @@ const capitalize = (str: string) => str.charAt(0).toUpperCase() + str.slice(1);
4
4
 
5
5
  export default function getContent(scanInfo: AppInfo | LibInfo | null, dict: { [key: string]: string } = {}) {
6
6
  if (!scanInfo) return null;
7
- const databaseModules = [...scanInfo.database.entries()]
8
- .filter(
9
- ([_, fileTypes]) =>
10
- fileTypes.has("template") ||
11
- fileTypes.has("unit") ||
12
- fileTypes.has("util") ||
13
- fileTypes.has("view") ||
14
- fileTypes.has("zone"),
15
- )
16
- .map(([key]) => key);
17
- const scalarModules = [...scanInfo.scalar.entries()]
18
- .filter(
19
- ([_, fileTypes]) =>
20
- fileTypes.has("template") ||
21
- fileTypes.has("unit") ||
22
- fileTypes.has("util") ||
23
- fileTypes.has("view") ||
24
- fileTypes.has("zone"),
25
- )
26
- .map(([key]) => key);
27
- const serviceModules = [...scanInfo.service.entries()]
28
- .filter(
29
- ([_, fileTypes]) =>
30
- fileTypes.has("template") ||
31
- fileTypes.has("unit") ||
32
- fileTypes.has("util") ||
33
- fileTypes.has("view") ||
34
- fileTypes.has("zone"),
35
- )
36
- .map(([key]) => key);
7
+ const uiModules = (modules: Map<string, Set<string>>) =>
8
+ [...modules.entries()]
9
+ .filter(([_, fileTypes]) => ["template", "unit", "util", "view", "zone"].some((type) => fileTypes.has(type)))
10
+ .map(([key]) => key);
11
+ const databaseModules = uiModules(scanInfo.database);
12
+ const scalarModules = uiModules(scanInfo.scalar);
13
+ const serviceModules = uiModules(scanInfo.service);
37
14
  return `
38
15
  export * as cnst from "./lib/cnst";
39
16
  export { msg, Err, usePage, fetch, sig } from "./lib/useClient";
@@ -4,12 +4,13 @@ interface Dict {
4
4
  Model: string;
5
5
  model: string;
6
6
  appName: string;
7
+ clientPath: string;
7
8
  }
8
9
  export default function getContent(scanInfo: AppInfo | LibInfo | null, dict: Dict) {
9
10
  return {
10
11
  filename: "_index.tsx",
11
12
  content: `
12
- import { ${dict.Model}, fetch, usePage } from "@apps/${dict.appName}/client";
13
+ import { ${dict.Model}, fetch, usePage } from "${dict.clientPath}";
13
14
  import { ID } from "akanjs/base";
14
15
  import { page } from "akanjs/client";
15
16
  import { Load } from "akanjs/ui";
@@ -4,12 +4,13 @@ interface Dict {
4
4
  Model: string;
5
5
  model: string;
6
6
  appName: string;
7
+ clientPath: string;
7
8
  }
8
9
  export default function getContent(scanInfo: AppInfo | LibInfo | null, dict: Dict) {
9
10
  return {
10
11
  filename: "_index.tsx",
11
12
  content: `
12
- import { ${dict.Model}, fetch, usePage } from "@apps/${dict.appName}/client";
13
+ import { ${dict.Model}, fetch, usePage } from "${dict.clientPath}";
13
14
  import { ID } from "akanjs/base";
14
15
  import { page } from "akanjs/client";
15
16
  import { buttonRecipe, Link } from "akanjs/ui";
@@ -4,12 +4,13 @@ interface Dict {
4
4
  Model: string;
5
5
  model: string;
6
6
  appName: string;
7
+ clientPath: string;
7
8
  }
8
9
  export default function getContent(scanInfo: AppInfo | LibInfo | null, dict: Dict) {
9
10
  return {
10
11
  filename: "_index.tsx",
11
12
  content: `
12
- import { type cnst, fetch, usePage, ${dict.Model} } from "@apps/${dict.appName}/client";
13
+ import { type cnst, fetch, usePage, ${dict.Model} } from "${dict.clientPath}";
13
14
  import { page } from "akanjs/client";
14
15
  import { Load } from "akanjs/ui";
15
16
 
@@ -4,12 +4,13 @@ interface Dict {
4
4
  Model: string;
5
5
  model: string;
6
6
  appName: string;
7
+ clientPath: string;
7
8
  }
8
9
  export default function getContent(scanInfo: AppInfo | LibInfo | null, dict: Dict) {
9
10
  return {
10
11
  filename: "_index.tsx",
11
12
  content: `
12
- import { ${dict.Model}, fetch, usePage } from "@apps/${dict.appName}/client";
13
+ import { ${dict.Model}, fetch, usePage } from "${dict.clientPath}";
13
14
  import { page } from "akanjs/client";
14
15
  import { buttonRecipe, Link } from "akanjs/ui";
15
16
 
@@ -4,12 +4,13 @@ interface Dict {
4
4
  Model: string;
5
5
  model: string;
6
6
  appName: string;
7
+ clientPath: string;
7
8
  }
8
9
  export default function getContent(scanInfo: AppInfo | LibInfo | null, dict: Dict) {
9
10
  return {
10
11
  filename: "_index.tsx",
11
12
  content: `
12
- import { ${dict.Model}, fetch, usePage } from "@apps/${dict.appName}/client";
13
+ import { ${dict.Model}, fetch, usePage } from "${dict.clientPath}";
13
14
  import { page } from "akanjs/client";
14
15
  import { buttonRecipe, Model } from "akanjs/ui";
15
16
 
@@ -1,7 +1,5 @@
1
1
  import type { AppInfo, LibInfo } from "akanjs";
2
2
 
3
- const capitalize = (str: string) => str.charAt(0).toUpperCase() + str.slice(1);
4
-
5
3
  export default function getContent(scanInfo: AppInfo | LibInfo | null, dict: { [key: string]: string } = {}) {
6
4
  if (!scanInfo) return null;
7
5
  const libs = scanInfo.getLibs();
@@ -1,7 +1,5 @@
1
1
  import type { AppInfo, LibInfo } from "akanjs";
2
2
 
3
- const capitalize = (str: string) => str.charAt(0).toUpperCase() + str.slice(1);
4
-
5
3
  export default function getContent(scanInfo: AppInfo | LibInfo | null, dict: { [key: string]: string } = {}) {
6
4
  if (!scanInfo) return null;
7
5
  const libs = scanInfo.getLibs();
@@ -13,6 +13,8 @@ export default function getContent(scanInfo: AppInfo | LibInfo | null, dict: { [
13
13
  const libs = scanInfo.getLibs();
14
14
 
15
15
  const signalNames = [...(libs.length === 0 ? ["base"] : libs), ...databaseModules, ...serviceModules];
16
+ const serverSignalClass = (module: string) =>
17
+ `export class ${capitalize(module)} extends serverSignal(${module}Sig.${capitalize(module)}Endpoint, ${module}Sig.${capitalize(module)}Internal) {}`;
16
18
 
17
19
  return `
18
20
  import { FetchClient, type FetchClientType } from "akanjs/fetch";
@@ -20,38 +22,14 @@ import { SignalRegistry, serverSignal${libs.length === 0 ? ", fetch as base" : "
20
22
  import { Err } from "./dict";
21
23
  ${libs.map((lib) => `import { fetch as ${lib} } from "@libs/${lib}/lib/sig";`).join("\n")}
22
24
 
23
- ${[...scanInfo.database.entries()]
24
- .filter(([_, files]) => files.has("signal"))
25
- .map(([module]) => `import * as ${module}Sig from "./${module}/${module}.signal";`)
26
- .join("\n")}
27
- ${[...scanInfo.service.entries()]
28
- .filter(([_, files]) => files.has("signal"))
29
- .map(([module]) => `import * as ${module}Sig from "./_${module}/${module}.signal";`)
30
- .join("\n")}
25
+ ${databaseModules.map((module) => `import * as ${module}Sig from "./${module}/${module}.signal";`).join("\n")}
26
+ ${serviceModules.map((module) => `import * as ${module}Sig from "./_${module}/${module}.signal";`).join("\n")}
31
27
 
32
- ${[...scanInfo.database.entries()]
33
- .filter(([_, files]) => files.has("signal"))
34
- .map(([module]) => `export * from "./${module}/${module}.signal";`)
35
- .join("\n")}
36
- ${[...scanInfo.service.entries()]
37
- .filter(([_, files]) => files.has("signal"))
38
- .map(([module]) => `export * from "./_${module}/${module}.signal";`)
39
- .join("\n")}
28
+ ${databaseModules.map((module) => `export * from "./${module}/${module}.signal";`).join("\n")}
29
+ ${serviceModules.map((module) => `export * from "./_${module}/${module}.signal";`).join("\n")}
40
30
 
41
- ${[...scanInfo.database.entries()]
42
- .filter(([_, files]) => files.has("signal"))
43
- .map(
44
- ([module]) =>
45
- `export class ${capitalize(module)} extends serverSignal(${module}Sig.${capitalize(module)}Endpoint, ${module}Sig.${capitalize(module)}Internal) {}`,
46
- )
47
- .join("\n")}
48
- ${[...scanInfo.service.entries()]
49
- .filter(([_, files]) => files.has("signal"))
50
- .map(
51
- ([module]) =>
52
- `export class ${capitalize(module)} extends serverSignal(${module}Sig.${capitalize(module)}Endpoint, ${module}Sig.${capitalize(module)}Internal) {}`,
53
- )
54
- .join("\n")}
31
+ ${databaseModules.map(serverSignalClass).join("\n")}
32
+ ${serviceModules.map(serverSignalClass).join("\n")}
55
33
 
56
34
  ${databaseModules.map((module) => `export const ${module} = SignalRegistry.registerDatabase("${module}" as const, ${module}Sig.${capitalize(module)}Internal, ${module}Sig.${capitalize(module)}Endpoint, ${module}Sig.${capitalize(module)}Slice, ${capitalize(module)});`).join("\n")}
57
35
  ${serviceModules.map((module) => `export const ${module} = SignalRegistry.registerService("${module}" as const, ${module}Sig.${capitalize(module)}Internal, ${module}Sig.${capitalize(module)}Endpoint, ${capitalize(module)});`).join("\n")}
@@ -1,29 +1,18 @@
1
- version: "3.8"
2
1
  networks:
3
2
  <%= repoName %>-network:
4
3
  driver: bridge
5
4
  services:
6
5
  redis:
7
6
  container_name: <%= repoName %>-redis
8
- image: redis
7
+ image: redis:8
9
8
  restart: unless-stopped
10
9
  ports:
11
10
  - 6379:6379
12
11
  networks:
13
12
  - <%= repoName %>-network
14
- libsql:
15
- container_name: <%= repoName %>-libsql
16
- image: ghcr.io/tursodatabase/libsql-server:latest
17
- restart: unless-stopped
18
- ports:
19
- - 8080:8080
20
- volumes:
21
- - ./libsql:/var/lib/sqld
22
- networks:
23
- - <%= repoName %>-network
24
13
  postgres:
25
14
  container_name: <%= repoName %>-postgres
26
- image: postgres:17
15
+ image: postgres:18
27
16
  restart: unless-stopped
28
17
  ports:
29
18
  - 5432:5432
@@ -32,6 +21,6 @@ services:
32
21
  - POSTGRES_USER=akan
33
22
  - POSTGRES_PASSWORD=akan
34
23
  volumes:
35
- - ./postgres:/var/lib/postgresql/data
24
+ - ./postgres:/var/lib/postgresql
36
25
  networks:
37
26
  - <%= repoName %>-network
@@ -6,16 +6,24 @@ interface Dict {
6
6
  models: string;
7
7
  sysName: string;
8
8
  }
9
- export default function getContent(scanInfo: AppInfo | LibInfo | null, dict: Dict) {
9
+ interface Options {
10
+ sharedGuards?: boolean;
11
+ }
12
+ export default function getContent(scanInfo: AppInfo | LibInfo | null, dict: Dict, options: Options = {}) {
13
+ // Admin lives in libs/shared; without it root and cru stay None, shut until the app names a guard of its own.
14
+ const guardImports = options.sharedGuards
15
+ ? `import { Admin } from "@libs/shared/srvkit";\nimport { endpoint, internal, Public, slice } from "akanjs/signal";`
16
+ : `import { endpoint, internal, None, Public, slice } from "akanjs/signal";`;
17
+ const writeGuard = options.sharedGuards ? "Admin" : "None";
10
18
  return `
11
- import { endpoint, internal, None, Public, slice } from "akanjs/signal";
19
+ ${guardImports}
12
20
 
13
21
  import * as srv from "../srv";
14
22
 
15
23
  export class ${dict.Model}Internal extends internal(srv.${dict.model}, ({ interval }) => ({})) {}
16
24
 
17
- export class ${dict.Model}Slice extends slice(srv.${dict.model}, { guards: { root: None, get: Public, cru: Public } }, (init) => ({
18
- inPublic: init()
25
+ export class ${dict.Model}Slice extends slice(srv.${dict.model}, { guards: { root: ${writeGuard}, get: Public, cru: ${writeGuard} } }, (init) => ({
26
+ inPublic: init({ guards: [Public] })
19
27
  .exec(function () {
20
28
  return this.${dict.model}Service.queryAny();
21
29
  }),
@@ -1,10 +1,5 @@
1
1
  {
2
- "extends": "../../tsconfig.json",
2
+ "extends": "<%= workspaceRootPath %>/tsconfig.json",
3
3
  "files": [],
4
- "include": ["**/*.ts", "**/*.tsx"],
5
- "references": [
6
- {
7
- "path": "./tsconfig.spec.json"
8
- }
9
- ]
4
+ "include": ["**/*.ts", "**/*.tsx"]
10
5
  }
@@ -1,7 +1,5 @@
1
1
  import type { AppInfo, LibInfo } from "akanjs";
2
2
 
3
- const capitalize = (str: string) => str.charAt(0).toUpperCase() + str.slice(1);
4
-
5
3
  export default function getContent(scanInfo: AppInfo | LibInfo | null, dict: { [key: string]: string } = {}) {
6
4
  if (!scanInfo) return null;
7
5
  const libs = scanInfo.getLibs();
@@ -0,0 +1,8 @@
1
+ # <%= model %> Service Abstract
2
+
3
+ <One sentence: the workflow or integration this service owns, stated as fact.>
4
+
5
+ ## Rules
6
+
7
+ - <An invariant the code obeys but cannot explain: a lifetime, a refusal, an ordering, and its reason.>
8
+ - <Two to five of them. If the service moves something through states, end the file with one arrow line.>