@akanjs/cli 2.3.9-rc.0 → 2.3.9-rc.2
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/incrementalBuilder.proc.js +907 -18
- package/index.js +3928 -1486
- package/package.json +2 -2
- package/templates/appSample/common/formatters.ts +1 -1
- package/templates/appSample/common/validators.ts +1 -1
- package/templates/appSample/lib/_noti/noti.dictionary.ts +1 -1
- package/templates/appSample/lib/_noti/noti.service.ts +1 -1
- package/templates/appSample/lib/_noti/noti.signal.ts +1 -1
- package/templates/appSample/lib/_noti/noti.store.ts +1 -1
- package/templates/appSample/lib/task/task.abstract.ts +1 -1
- package/templates/appSample/lib/task/task.constant.ts +1 -1
- package/templates/appSample/lib/task/task.dictionary.ts +1 -1
- package/templates/appSample/lib/task/task.document.ts +3 -3
- package/templates/appSample/lib/task/task.service.ts +2 -2
- package/templates/appSample/lib/task/task.signal.spec.ts +70 -0
- package/templates/appSample/lib/task/task.signal.test.ts +20 -0
- package/templates/appSample/lib/task/task.signal.ts +2 -2
- package/templates/appSample/lib/task/task.store.ts +2 -2
- package/templates/appSample/srvkit/AuthGuard.ts +1 -1
- package/templates/appSample/srvkit/SessionInternalArg.ts +1 -1
- package/templates/appSample/ui/GlobalLoading.tsx +1 -1
- package/templates/appSample/ui/QuantityControl.tsx +1 -1
- package/templates/appSample/webkit/useDebounce.ts +1 -1
- package/templates/lib/useClient.ts +1 -4
- package/templates/module/__Model__.Unit.tsx +1 -4
- package/templates/workspaceRoot/.cursor/rules/{akan-scan-conventions.mdc.template → akan-sync-conventions.mdc.template} +3 -3
- package/templates/workspaceRoot/.cursor/rules/akan.mdc.template +5 -0
- package/templates/workspaceRoot/AGENTS.md.template +77 -58
- package/templates/workspaceRoot/docs/AI-DEVELOPMENT.md.template +51 -0
- package/templates/workspaceRoot/docs/GENERATED.md.template +54 -0
- package/templates/workspaceRoot/package.json.template +26 -0
- package/templates/workspaceRoot/Untitled +0 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@akanjs/cli",
|
|
3
|
-
"version": "2.3.9-rc.
|
|
3
|
+
"version": "2.3.9-rc.2",
|
|
4
4
|
"sourceType": "module",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"publishConfig": {
|
|
@@ -34,7 +34,7 @@
|
|
|
34
34
|
"@langchain/openai": "^1.4.6",
|
|
35
35
|
"@tailwindcss/node": "^4.3.0",
|
|
36
36
|
"@trapezedev/project": "^7.1.4",
|
|
37
|
-
"akanjs": "2.3.9-rc.
|
|
37
|
+
"akanjs": "2.3.9-rc.2",
|
|
38
38
|
"chalk": "^5.6.2",
|
|
39
39
|
"commander": "^14.0.3",
|
|
40
40
|
"daisyui": "5.5.23",
|
|
@@ -7,7 +7,7 @@ export default function getContent(scanInfo: AppInfo | LibInfo | null, dict: { a
|
|
|
7
7
|
// Convention: common/ folder — only pure functions that run on both server and client.
|
|
8
8
|
// Cannot import window, Bun, process.env, or any runtime-specific API.
|
|
9
9
|
// Naming: camelCase .ts, file name = primary export name.
|
|
10
|
-
// Scanned by akan
|
|
10
|
+
// Scanned by akan sync into common/index.ts barrel automatically.
|
|
11
11
|
|
|
12
12
|
export function trimString(str: string, maxLength = 50, suffix = "...") {
|
|
13
13
|
if (str.length <= maxLength) return str;
|
|
@@ -7,7 +7,7 @@ export default function getContent(scanInfo: AppInfo | LibInfo | null, dict: { a
|
|
|
7
7
|
// Convention: common/ folder — only pure functions that run on both server and client.
|
|
8
8
|
// Cannot import window, Bun, process.env, or any runtime-specific API.
|
|
9
9
|
// Naming: camelCase .ts, file name = primary export name.
|
|
10
|
-
// Scanned by akan
|
|
10
|
+
// Scanned by akan sync into common/index.ts barrel automatically.
|
|
11
11
|
|
|
12
12
|
export function isValidEmail(email: string): boolean {
|
|
13
13
|
const emailRegex = /^[^s@]+@[^s@]+.[^s@]+$/;
|
|
@@ -9,7 +9,7 @@ import type { NotiEndpoint } from "./noti.signal";
|
|
|
9
9
|
// Uses serviceDictionary(["en", "ko"]) from akanjs/dictionary — the framework convention for service dictionaries.
|
|
10
10
|
// Unlike modelDictionary, no model/lightModel/query/sort/enum sections (service modules have no DB model).
|
|
11
11
|
// Sections: .endpoint() for signal endpoint names, .translate() for UI messages.
|
|
12
|
-
// Registered by akan
|
|
12
|
+
// Registered by akan sync into dict.ts barrel.
|
|
13
13
|
|
|
14
14
|
export const dictionary = serviceDictionary(["en", "ko"])
|
|
15
15
|
.endpoint<NotiEndpoint>((fn) => ({
|
|
@@ -8,7 +8,7 @@ export default function getContent(scanInfo: AppInfo | LibInfo | null, dict: { a
|
|
|
8
8
|
// Extends serve("noti" as const, ...) — named service (string literal), no DB model binding.
|
|
9
9
|
// DB modules use serve(db.<module>, ...); service modules use serve("<name>" as const, ...).
|
|
10
10
|
// DI deps available: { service, use, signal, plug, env, memory }.
|
|
11
|
-
// Registered by akan
|
|
11
|
+
// Registered by akan sync into srv.ts barrel.
|
|
12
12
|
|
|
13
13
|
export class NotiService extends serve("noti" as const, () => ({})) {}
|
|
14
14
|
|
|
@@ -10,7 +10,7 @@ import * as srv from "../srv";
|
|
|
10
10
|
// Extends endpoint(srv.<module>, ...) — pubsub endpoint for real-time server→client communication.
|
|
11
11
|
// pubsub() is the Akan.js convention for publish-subscribe: server publishes, all connected clients receive.
|
|
12
12
|
// Client subscribes via fetch.subscribeSend((data) => { ... }).
|
|
13
|
-
// Registered by akan
|
|
13
|
+
// Registered by akan sync into sig.ts barrel.
|
|
14
14
|
|
|
15
15
|
export class NotiInternal extends internal(srv.noti, () => ({})) {}
|
|
16
16
|
|
|
@@ -9,7 +9,7 @@ import { store } from "akanjs/store";
|
|
|
9
9
|
// Extends store("noti" as const, ...) — named store (string literal), no signal binding.
|
|
10
10
|
// DB modules use store(sig.<module>, ...); service modules use store("<name>" as const, ...).
|
|
11
11
|
// State definitions use direct property assignment. Actions use this.set() / this.get().
|
|
12
|
-
// Registered by akan
|
|
12
|
+
// Registered by akan sync into st.ts barrel.
|
|
13
13
|
|
|
14
14
|
export class NotiStore extends store("noti" as const, () => ({
|
|
15
15
|
notiList: [] as { id: string; type: string; message: string; sentAt: Dayjs }[],
|
|
@@ -28,7 +28,7 @@ Every database module in Akan.js follows this layered architecture:
|
|
|
28
28
|
|
|
29
29
|
## Convention: scan-registered barrels
|
|
30
30
|
|
|
31
|
-
akan
|
|
31
|
+
akan sync auto-discovers each file and registers it in the corresponding barrel:
|
|
32
32
|
cnst.ts, db.ts, dict.ts, srv.ts, sig.ts, st.ts
|
|
33
33
|
|
|
34
34
|
## Related Modules
|
|
@@ -12,7 +12,7 @@ import { WorkHistory } from "../__scalar/workHistory/workHistory.constant";
|
|
|
12
12
|
// Scalars are embedded via field([ScalarType], ...) — see WorkHistory embedding below.
|
|
13
13
|
// Layer order: enum → Input → Object → Light → Full.
|
|
14
14
|
// Input = user-provided fields; Object = Input + system fields + embedded scalars; Light = subset for list views; Full = Object + Light.
|
|
15
|
-
// Registered by akan
|
|
15
|
+
// Registered by akan sync into cnst.ts barrel.
|
|
16
16
|
|
|
17
17
|
export class TaskStatus extends enumOf("taskStatus", [
|
|
18
18
|
"todo",
|
|
@@ -11,7 +11,7 @@ import type { TaskFilter } from "./task.document";
|
|
|
11
11
|
// Uses modelDictionary(["en", "ko"]) from akanjs/dictionary — the framework convention for bilingual module dictionaries.
|
|
12
12
|
// Sections: .of() module name, .model() field labels, .lightModel() list-view labels, .query() filter labels,
|
|
13
13
|
// .sort() labels, .enum() value labels, .error() messages (thrown via Err()), .translate() UI messages (used in store via msg.xxx).
|
|
14
|
-
// Registered by akan
|
|
14
|
+
// Registered by akan sync into dict.ts barrel.
|
|
15
15
|
|
|
16
16
|
export const dictionary = modelDictionary(["en", "ko"])
|
|
17
17
|
.of((t) =>
|
|
@@ -10,10 +10,10 @@ import * as cnst from "../cnst";
|
|
|
10
10
|
// ===== task.document.ts =====
|
|
11
11
|
// Convention: <module>.document.ts — the database query and persistence layer.
|
|
12
12
|
// Import from/into/by from akanjs/document — the framework convention for Filter, Document, and Model classes.
|
|
13
|
-
// Filter defines query/sort conditions (auto-generates List/Find/Pick/Exists/Count methods via akan
|
|
13
|
+
// Filter defines query/sort conditions (auto-generates List/Find/Pick/Exists/Count methods via akan sync).
|
|
14
14
|
// Document defines chainable per-document methods (e.g., task.start().save()).
|
|
15
15
|
// Model = into(Document, Filter, cnst.module, ...) — collection-level operations, schema hooks.
|
|
16
|
-
// Registered by akan
|
|
16
|
+
// Registered by akan sync into db.ts barrel.
|
|
17
17
|
|
|
18
18
|
export class TaskFilter extends from(cnst.Task, (filter) => ({
|
|
19
19
|
query: {
|
|
@@ -34,7 +34,7 @@ export class Task extends by(cnst.Task) {
|
|
|
34
34
|
start() {
|
|
35
35
|
if (this.status !== "todo") throw new Err("task.error.cannotStartFromNonTodo");
|
|
36
36
|
this.status = "inProgress";
|
|
37
|
-
this.workHistory.push({ action: "
|
|
37
|
+
this.workHistory.push({ action: "started", at: dayjs(), note: "" });
|
|
38
38
|
return this;
|
|
39
39
|
}
|
|
40
40
|
|
|
@@ -9,11 +9,11 @@ import * as db from "../db";
|
|
|
9
9
|
// ===== task.service.ts =====
|
|
10
10
|
// Convention: <module>.service.ts — business logic orchestration for a database module.
|
|
11
11
|
// Extends serve(db.<module>, depsCallback) from akanjs/service — binds to the DB model, receives DI deps.
|
|
12
|
-
// Auto-generated by akan
|
|
12
|
+
// Auto-generated by akan sync (do not write manually):
|
|
13
13
|
// getTask(id), createModel(data), updateModel(id, data), removeModel(id),
|
|
14
14
|
// listByStatus(status), searchDocs(text), and all filter+query methods from document.ts.
|
|
15
15
|
// Manual below: lifecycle hooks, custom business logic methods.
|
|
16
|
-
// Registered by akan
|
|
16
|
+
// Registered by akan sync into srv.ts barrel.
|
|
17
17
|
|
|
18
18
|
export class TaskService extends serve(db.task, () => ({})) {
|
|
19
19
|
// Lifecycle hook: runs before every document creation.
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import type { AppInfo, LibInfo } from "akanjs";
|
|
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";
|
|
7
|
+
|
|
8
|
+
import * as cnst from "../cnst";
|
|
9
|
+
import type { fetch as appFetch } from "../useServer";
|
|
10
|
+
|
|
11
|
+
type AppFetch = typeof appFetch;
|
|
12
|
+
|
|
13
|
+
const getFetch = async () => await getOrSetupSignalTestFetch<AppFetch>();
|
|
14
|
+
|
|
15
|
+
export interface TaskAgent {
|
|
16
|
+
task: cnst.Task;
|
|
17
|
+
fetch: AppFetch;
|
|
18
|
+
taskInput: DocumentModel<cnst.TaskInput>;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export const createTask = async (overrides: Partial<DocumentModel<cnst.TaskInput>> = {}): Promise<TaskAgent> => {
|
|
22
|
+
const fetch = await getFetch();
|
|
23
|
+
const taskInput = {
|
|
24
|
+
...sampleOf(cnst.TaskInput),
|
|
25
|
+
...overrides,
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
const task = await fetch.createTask(taskInput);
|
|
29
|
+
|
|
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
|
+
return {
|
|
38
|
+
task,
|
|
39
|
+
fetch,
|
|
40
|
+
taskInput,
|
|
41
|
+
};
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
export const getStartedTask = async (overrides: Partial<DocumentModel<cnst.TaskInput>> = {}): Promise<TaskAgent> => {
|
|
45
|
+
const agent = await createTask(overrides);
|
|
46
|
+
const task = await agent.fetch.startTask(agent.task.id);
|
|
47
|
+
|
|
48
|
+
expect(task.status).toBe("inProgress");
|
|
49
|
+
expect(task.workHistory.map((entry) => entry.action)).toEqual(["created", "started"]);
|
|
50
|
+
|
|
51
|
+
return {
|
|
52
|
+
...agent,
|
|
53
|
+
task,
|
|
54
|
+
};
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
export const getCompletedTask = async (overrides: Partial<DocumentModel<cnst.TaskInput>> = {}): Promise<TaskAgent> => {
|
|
58
|
+
const agent = await getStartedTask(overrides);
|
|
59
|
+
const task = await agent.fetch.completeTask(agent.task.id);
|
|
60
|
+
|
|
61
|
+
expect(task.status).toBe("completed");
|
|
62
|
+
expect(task.workHistory.map((entry) => entry.action)).toEqual(["created", "started", "completed"]);
|
|
63
|
+
|
|
64
|
+
return {
|
|
65
|
+
...agent,
|
|
66
|
+
task,
|
|
67
|
+
};
|
|
68
|
+
};
|
|
69
|
+
`;
|
|
70
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { AppInfo, LibInfo } from "akanjs";
|
|
2
|
+
|
|
3
|
+
export default function getContent(scanInfo: AppInfo | LibInfo | null, dict: { appName: string }) {
|
|
4
|
+
return `import { describe, expect, test } from "bun:test";
|
|
5
|
+
import { configureSignalTest } from "akanjs/test";
|
|
6
|
+
|
|
7
|
+
import * as taskSpec from "./task.signal.spec";
|
|
8
|
+
|
|
9
|
+
configureSignalTest({ databaseMode: "memory" });
|
|
10
|
+
|
|
11
|
+
describe("Task signal smoke", () => {
|
|
12
|
+
test("exposes custom task mutations over fetch", async () => {
|
|
13
|
+
const { task } = await taskSpec.getCompletedTask({ title: "Signal smoke task" });
|
|
14
|
+
|
|
15
|
+
expect(task.status).toBe("completed");
|
|
16
|
+
expect(task.workHistory.map((entry) => entry.action)).toEqual(["created", "started", "completed"]);
|
|
17
|
+
});
|
|
18
|
+
});
|
|
19
|
+
`;
|
|
20
|
+
}
|
|
@@ -12,12 +12,12 @@ import * as srv from "../srv";
|
|
|
12
12
|
// endpoint() receives: { query, mutation, pubsub, message } — each typed with the model's return type.
|
|
13
13
|
// .param() = URL path parameter, .body() = request body, .search() = query string.
|
|
14
14
|
//
|
|
15
|
-
// Auto-generated by akan
|
|
15
|
+
// Auto-generated by akan sync (do not write manually):
|
|
16
16
|
// viewTask(id) — fetch single task for detail view
|
|
17
17
|
// editTask(id) — fetch task for edit view
|
|
18
18
|
// mergeTask(id, data) — create/update task
|
|
19
19
|
// Manual endpoints below: only define endpoints that need custom business logic.
|
|
20
|
-
// Registered by akan
|
|
20
|
+
// Registered by akan sync into sig.ts barrel.
|
|
21
21
|
|
|
22
22
|
export class TaskInternal extends internal(srv.task, ({ interval }) => ({})) {}
|
|
23
23
|
|
|
@@ -8,13 +8,13 @@ import { fetch, msg, sig } from "../useClient";
|
|
|
8
8
|
// ===== task.store.ts =====
|
|
9
9
|
// Convention: <module>.store.ts — client-side state management for a database module.
|
|
10
10
|
// Extends store(sig.<module>, ...) from akanjs/store — binds to the signal to auto-generate model states/actions.
|
|
11
|
-
// Auto-generated by akan
|
|
11
|
+
// Auto-generated by akan sync (do not write manually):
|
|
12
12
|
// taskForm — form state bound to model fields (create + edit)
|
|
13
13
|
// setTitleOnTask, setContentOnTask, setDueOnTask — auto-setters for form fields
|
|
14
14
|
// createTask(data), updateTask(id, data), removeTask(id) — CRUD actions
|
|
15
15
|
// task (cached model), taskLoading, taskModal — model display states
|
|
16
16
|
// Manual below: custom actions wrapping auto-generated fetch with toast feedback.
|
|
17
|
-
// Registered by akan
|
|
17
|
+
// Registered by akan sync into st.ts barrel.
|
|
18
18
|
|
|
19
19
|
export class TaskStore extends store(sig.task, () => ({})) {
|
|
20
20
|
async startTask(taskId: string) {
|
|
@@ -10,7 +10,7 @@ export default function getContent(scanInfo: AppInfo | LibInfo | null, dict: { a
|
|
|
10
10
|
// Implements the Guard interface from akanjs/signal.
|
|
11
11
|
// Guards are applied at endpoint/slice declaration: { guards: { root: SignedIn } }.
|
|
12
12
|
// Naming: PascalCase .ts, static name property matches the guard identifier.
|
|
13
|
-
// Scanned by akan
|
|
13
|
+
// Scanned by akan sync into srvkit/index.ts barrel automatically.
|
|
14
14
|
|
|
15
15
|
export class SignedIn implements Guard {
|
|
16
16
|
static name = "SignedIn";
|
|
@@ -11,7 +11,7 @@ export default function getContent(scanInfo: AppInfo | LibInfo | null, dict: { a
|
|
|
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
13
|
// Naming: PascalCase .ts, class name = arg identifier.
|
|
14
|
-
// Scanned by akan
|
|
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 {
|
|
@@ -10,7 +10,7 @@ import { clsx } from "akanjs/client";
|
|
|
10
10
|
// ===== GlobalLoading.tsx =====
|
|
11
11
|
// Convention: ui/ folder — reusable visual components. PascalCase .tsx, "use client" directive.
|
|
12
12
|
// File name = exported component name.
|
|
13
|
-
// Scanned by akan
|
|
13
|
+
// Scanned by akan sync into ui/index.ts barrel automatically.
|
|
14
14
|
|
|
15
15
|
interface GlobalLoadingProps {
|
|
16
16
|
className?: string;
|
|
@@ -10,7 +10,7 @@ import { clsx } from "akanjs/client";
|
|
|
10
10
|
// ===== QuantityControl.tsx =====
|
|
11
11
|
// Convention: ui/ folder — reusable visual components. PascalCase .tsx, "use client" directive.
|
|
12
12
|
// File name = exported component name.
|
|
13
|
-
// Scanned by akan
|
|
13
|
+
// Scanned by akan sync into ui/index.ts barrel automatically.
|
|
14
14
|
|
|
15
15
|
interface QuantityControlProps {
|
|
16
16
|
className?: string;
|
|
@@ -11,7 +11,7 @@ import { useEffect, useState } from "react";
|
|
|
11
11
|
// Convention: webkit/ folder — browser-only hooks; "use client" directive required.
|
|
12
12
|
// useEffect/useState are React client-side primitives that only work in the browser.
|
|
13
13
|
// Naming: camelCase .ts, file name = primary export name.
|
|
14
|
-
// Scanned by akan
|
|
14
|
+
// Scanned by akan sync into webkit/index.ts barrel automatically.
|
|
15
15
|
|
|
16
16
|
export function useDebounce<T>(value: T, delay = 300): T {
|
|
17
17
|
const [debouncedValue, setDebouncedValue] = useState<T>(value);
|
|
@@ -1,9 +1,6 @@
|
|
|
1
1
|
import type { AppInfo, LibInfo } from "akanjs";
|
|
2
2
|
|
|
3
|
-
export default function getContent(
|
|
4
|
-
scanInfo: AppInfo | LibInfo | null,
|
|
5
|
-
dict: { [key: string]: string } = {},
|
|
6
|
-
) {
|
|
3
|
+
export default function getContent(scanInfo: AppInfo | LibInfo | null, dict: { [key: string]: string } = {}) {
|
|
7
4
|
return `
|
|
8
5
|
import { makePageProto, registerClientRuntime } from "akanjs/client";
|
|
9
6
|
import { FetchClient } from "akanjs/fetch";
|
|
@@ -5,10 +5,7 @@ interface Dict {
|
|
|
5
5
|
model: string;
|
|
6
6
|
sysName: string;
|
|
7
7
|
}
|
|
8
|
-
export default function getContent(
|
|
9
|
-
scanInfo: AppInfo | LibInfo | null,
|
|
10
|
-
dict: Dict,
|
|
11
|
-
) {
|
|
8
|
+
export default function getContent(scanInfo: AppInfo | LibInfo | null, dict: Dict) {
|
|
12
9
|
return {
|
|
13
10
|
filename: `${dict.Model}.Unit.tsx`,
|
|
14
11
|
content: `
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
---
|
|
2
|
-
description:
|
|
2
|
+
description: sync-enforced Akan app/lib layout and generated index conventions
|
|
3
3
|
globs: apps/**/*,libs/**/*
|
|
4
4
|
alwaysApply: false
|
|
5
5
|
---
|
|
6
6
|
|
|
7
|
-
# Akan
|
|
7
|
+
# Akan Sync Conventions
|
|
8
8
|
|
|
9
9
|
- `apps/<appName>` root may only contain these files: `akan.app.json`, `akan.config.ts`, `capacitor.config.ts`, `client.ts`, `main.ts`, `package.json`, `server.ts`, `tsconfig.json`.
|
|
10
10
|
- `apps/<appName>` root may only contain these folders: `.akan`, `android`, `common`, `env`, `ios`, `lib`, `page`, `private`, `public`, `script`, `srvkit`, `ui`, `webkit`.
|
|
@@ -15,5 +15,5 @@ alwaysApply: false
|
|
|
15
15
|
- Service module UI files are limited to `<Service>.Util.tsx` and `<Service>.Zone.tsx`.
|
|
16
16
|
- Scalar module UI files are limited to `<Scalar>.Template.tsx` and `<Scalar>.Unit.tsx`.
|
|
17
17
|
- Module `*.test.ts`, `*.test.tsx`, `*.spec.ts`, and `*.spec.tsx` files are allowed.
|
|
18
|
-
- `ui/index.ts`, `webkit/index.ts`, `srvkit/index.ts`, `common/index.ts`, and module `lib/**/index.ts` files are generated by
|
|
18
|
+
- `ui/index.ts`, `webkit/index.ts`, `srvkit/index.ts`, `common/index.ts`, and module `lib/**/index.ts` files are generated by `akan sync`; do not hand-edit or track them.
|
|
19
19
|
- Generated facet indexes export only 1-depth files/folders with `export * from "./name";`.
|
|
@@ -15,6 +15,11 @@ alwaysApply: true
|
|
|
15
15
|
- Do not hand-edit generated Akan files such as `akan.app.json`, `client.ts`, `server.ts`, generated facet indexes,
|
|
16
16
|
`lib/cnst.ts`, `lib/dict.ts`, `lib/db.ts`, `lib/srv.ts`, `lib/st.ts`, `lib/sig.ts`, `lib/useClient.ts`, or
|
|
17
17
|
`lib/useServer.ts`.
|
|
18
|
+
- Prefer Akan MCP workflows before direct source edits: use `akan mcp --mode plan` for workflow discovery and
|
|
19
|
+
planning, then `akan mcp --mode apply` only for allowlisted apply, validation, and repair tools.
|
|
20
|
+
- Direct source edits are denied when an allowlisted Akan workflow or repair tool can perform the change.
|
|
21
|
+
- If generated output is stale or broken, update the owning source file and run `akan repair generated` or
|
|
22
|
+
`akan sync <app-or-lib>` instead of patching generated files.
|
|
18
23
|
- For new domain behavior, inspect sibling `constant`, `dictionary`, `signal`, `document`, `service`, `store`, and UI
|
|
19
24
|
module files before changing shape.
|
|
20
25
|
- Update `*.abstract.md` when business invariants, workflows, or public behavior change. Do not update it for
|
|
@@ -3,6 +3,9 @@
|
|
|
3
3
|
This is an Akan.js workspace. Akan is convention-driven: keep business intent in the expected place and let
|
|
4
4
|
the framework generate the repeated surfaces.
|
|
5
5
|
|
|
6
|
+
For agent-neutral documentation that Claude Code, Codex, Devin, GitHub Copilot Coding Agent, and other tools can
|
|
7
|
+
also read, see `docs/AI-DEVELOPMENT.md` and `docs/GENERATED.md`.
|
|
8
|
+
|
|
6
9
|
## Workspace Layout
|
|
7
10
|
|
|
8
11
|
- `apps/<app>` contains application pages, app UI, app domain modules, env files, and `akan.config.ts`.
|
|
@@ -16,25 +19,26 @@ the framework generate the repeated surfaces.
|
|
|
16
19
|
|
|
17
20
|
## Generated Files
|
|
18
21
|
|
|
19
|
-
Do not hand-edit generated Akan files. Regenerate them through Akan
|
|
22
|
+
Do not hand-edit generated Akan files. Regenerate them through Akan sync, lint, start, build, or the matching
|
|
20
23
|
CLI command instead.
|
|
21
24
|
|
|
22
25
|
Common generated files include:
|
|
23
26
|
- `apps/*/client.ts`
|
|
24
27
|
- `apps/*/server.ts`
|
|
25
|
-
-
|
|
26
|
-
-
|
|
27
|
-
-
|
|
28
|
-
-
|
|
29
|
-
-
|
|
30
|
-
-
|
|
31
|
-
-
|
|
32
|
-
-
|
|
33
|
-
-
|
|
34
|
-
-
|
|
35
|
-
-
|
|
36
|
-
-
|
|
37
|
-
-
|
|
28
|
+
- `*/lib/cnst.ts`
|
|
29
|
+
- `*/lib/db.ts`
|
|
30
|
+
- `*/lib/dict.ts`
|
|
31
|
+
- `*/lib/option.ts`
|
|
32
|
+
- `*/lib/sig.ts`
|
|
33
|
+
- `*/lib/srv.ts`
|
|
34
|
+
- `*/lib/st.ts`
|
|
35
|
+
- `*/lib/useClient.ts`
|
|
36
|
+
- `*/lib/useServer.ts`
|
|
37
|
+
- `*/lib/**/index.ts`
|
|
38
|
+
- `*/ui/index.ts`
|
|
39
|
+
- `*/webkit/index.ts`
|
|
40
|
+
- `*/srvkit/index.ts`
|
|
41
|
+
- `*/common/index.ts`
|
|
38
42
|
|
|
39
43
|
## Domain Module Responsibilities
|
|
40
44
|
|
|
@@ -58,15 +62,18 @@ Use the local module shape before adding a new abstraction.
|
|
|
58
62
|
|
|
59
63
|
1. Read the nearby module and convention before creating files. If `*.abstract.md` exists, read it first.
|
|
60
64
|
2. Put new files in the established Akan location instead of adding parallel architecture.
|
|
61
|
-
3. Prefer
|
|
62
|
-
4.
|
|
63
|
-
5.
|
|
64
|
-
6.
|
|
65
|
-
7. Keep server-
|
|
66
|
-
8.
|
|
67
|
-
9.
|
|
68
|
-
10.
|
|
69
|
-
11.
|
|
65
|
+
3. Prefer Akan MCP workflows before direct source edits. Start with `akan mcp --mode plan` for `list_workflows`, `explain_workflow`, and `plan_workflow`.
|
|
66
|
+
4. Use `akan mcp --mode apply` only for allowlisted `apply_workflow`, `run_validation`, and repair tools.
|
|
67
|
+
5. Direct source edits are denied when an allowlisted Akan workflow or repair tool can perform the change.
|
|
68
|
+
6. If no workflow exists, edit only the owning source files and never patch generated files directly.
|
|
69
|
+
7. Keep page and layout files server-oriented: route-level `usePage`, headers, static layout, and `akanjs/ui` `Tab` composition belong there.
|
|
70
|
+
8. Put interactive loading, submit/import actions, local form state, and `router.push` in client `Util`, `Template`, or `Zone` components.
|
|
71
|
+
9. Prefer `Tab` for static mode selection instead of extra `useState`; render one focused client component inside each `Tab.Panel`.
|
|
72
|
+
10. Keep server-only logic out of client surfaces and client-only code out of server imports.
|
|
73
|
+
11. Treat `AKAN_PUBLIC_*` env vars as public. Never put secrets in them.
|
|
74
|
+
12. Add or update tests when behavior, contracts, or CLI output changes.
|
|
75
|
+
13. Update `*.abstract.md` when business invariants, workflows, or public behavior change.
|
|
76
|
+
14. Run the smallest relevant verification command after changes.
|
|
70
77
|
|
|
71
78
|
## Common Commands
|
|
72
79
|
|
|
@@ -117,77 +124,89 @@ For the default generated app, start with:
|
|
|
117
124
|
akan start <%= appName %>
|
|
118
125
|
```
|
|
119
126
|
|
|
120
|
-
### The Essential Loop:
|
|
127
|
+
### The Essential Loop: Workflow -> Sync -> Check
|
|
128
|
+
|
|
129
|
+
Almost every Akan.js change follows this pattern. **Missing sync or repair is the #1 cause of agent confusion.**
|
|
121
130
|
|
|
122
|
-
|
|
131
|
+
1. **Plan** — Ask the Akan MCP server for the workflow first.
|
|
132
|
+
```
|
|
133
|
+
akan mcp --mode plan
|
|
134
|
+
# use list_workflows, explain_workflow, and plan_workflow
|
|
135
|
+
```
|
|
123
136
|
|
|
124
|
-
|
|
137
|
+
2. **Apply or edit source** — Use allowlisted apply/repair tools when available. If no workflow exists, edit only
|
|
138
|
+
owning source files such as `task.constant.ts`, `task.dictionary.ts`, `Task.Template.tsx`, or `Task.Unit.tsx`.
|
|
139
|
+
Never edit generated files.
|
|
125
140
|
```
|
|
126
|
-
|
|
141
|
+
akan mcp --mode apply
|
|
142
|
+
# use apply_workflow, run_validation, repair_generated, repair_imports, or repair_module_shape
|
|
127
143
|
```
|
|
128
144
|
|
|
129
|
-
|
|
145
|
+
3. **Sync or repair** — Regenerate barrel files so Akan discovers your change. This regenerates:
|
|
130
146
|
`cnst.ts`, `db.ts`, `srv.ts`, `sig.ts`, `st.ts`, `dict.ts`, `useClient.ts`, `useServer.ts`,
|
|
131
147
|
`ui/index.ts`, `webkit/index.ts`, `srvkit/index.ts`, `common/index.ts`, and all module `index.ts` files.
|
|
132
148
|
```
|
|
133
|
-
akan
|
|
149
|
+
akan sync <%= appName %>
|
|
150
|
+
# or: akan repair generated --app <%= appName %>
|
|
134
151
|
```
|
|
135
|
-
**CRITICAL**:
|
|
152
|
+
**CRITICAL**: Sync after EVERY file add, delete, or rename. Without sync, other modules cannot
|
|
136
153
|
`import * as cnst from "../cnst"` and find your new model.
|
|
137
154
|
|
|
138
|
-
|
|
155
|
+
4. **Check** — Verify your change compiles and lints.
|
|
139
156
|
```
|
|
140
157
|
akan start <%= appName %> # dev server with live feedback (preferred)
|
|
141
158
|
akan lint <%= appName %> # quick lint-only check
|
|
159
|
+
akan doctor --strict # structured workspace diagnostics
|
|
142
160
|
```
|
|
143
161
|
|
|
144
|
-
If `akan
|
|
145
|
-
- `akan build <%= appName %>` — full rebuild catches type errors
|
|
146
|
-
- Re-run `akan create-
|
|
162
|
+
If `akan sync` gives errors, try:
|
|
163
|
+
- `akan build <%= appName %>` — full rebuild catches type errors sync may miss
|
|
164
|
+
- Re-run `akan create-module <name> --app <%= appName %>` if the scaffold is corrupted
|
|
147
165
|
|
|
148
166
|
## Quick Decision Matrix — "Where do I put this code?"
|
|
149
167
|
|
|
150
168
|
| You want to... | Create in... | Run after... |
|
|
151
169
|
|----------------|-------------|--------------|
|
|
152
|
-
| Define a new database-backed noun (e.g., User, Product) | `lib/<model>/` → constant, document, service, signal, store, dictionary, abstract | `akan
|
|
153
|
-
| Add a pure workflow / integration (e.g., Payment, Email) | `lib/_<service>/` → service, signal, store, dictionary, abstract | `akan
|
|
154
|
-
| Add a reusable value type (e.g., Address, WorkHistory) | `lib/__scalar/<type>/` → constant, dictionary, abstract | `akan
|
|
170
|
+
| Define a new database-backed noun (e.g., User, Product) | `lib/<model>/` → constant, document, service, signal, store, dictionary, abstract | `akan sync <name>` |
|
|
171
|
+
| Add a pure workflow / integration (e.g., Payment, Email) | `lib/_<service>/` → service, signal, store, dictionary, abstract | `akan sync <name>` |
|
|
172
|
+
| Add a reusable value type (e.g., Address, WorkHistory) | `lib/__scalar/<type>/` → constant, dictionary, abstract | `akan sync <name>` |
|
|
155
173
|
| Create a new URL-visitable page | `page/` → `_index.tsx`, `_layout.tsx`, `[param]/_index.tsx` | Rebuild (akan start auto-detects) |
|
|
156
|
-
| Add a form or reusable UI component | `ui/` → PascalCase `.tsx` with `"use client"` if needed | `akan
|
|
157
|
-
| Add a React hook or browser helper | `webkit/` → camelCase `.ts` with `"use client"` | `akan
|
|
158
|
-
| Add a server-only guard, middleware, or adaptor | `srvkit/` → PascalCase `.ts` | `akan
|
|
159
|
-
| Add a pure helper (no DOM, no server API) | `common/` → camelCase `.ts` | `akan
|
|
174
|
+
| Add a form or reusable UI component | `ui/` → PascalCase `.tsx` with `"use client"` if needed | `akan sync <name>` |
|
|
175
|
+
| Add a React hook or browser helper | `webkit/` → camelCase `.ts` with `"use client"` | `akan sync <name>` |
|
|
176
|
+
| Add a server-only guard, middleware, or adaptor | `srvkit/` → PascalCase `.ts` | `akan sync <name>` |
|
|
177
|
+
| Add a pure helper (no DOM, no server API) | `common/` → camelCase `.ts` | `akan sync <name>` |
|
|
160
178
|
|
|
161
179
|
## Anti-patterns: Never Do These
|
|
162
180
|
|
|
163
181
|
| Don't | Why | Do Instead |
|
|
164
182
|
|-------|-----|------------|
|
|
165
|
-
| Edit `cnst.ts`, `db.ts`, `srv.ts`, `sig.ts`, `st.ts`, `dict.ts`, `useClient.ts`, `useServer.ts`, or any `index.ts` | These are **generated by `akan
|
|
166
|
-
| Create a file without running
|
|
183
|
+
| Edit `cnst.ts`, `db.ts`, `srv.ts`, `sig.ts`, `st.ts`, `dict.ts`, `useClient.ts`, `useServer.ts`, or any `index.ts` | These are **generated by `akan sync`**. Your changes will be overwritten. | Edit the source files in `lib/<model>/` directories and run `akan sync <name>` |
|
|
184
|
+
| Create a file without running sync | New files won't appear in barrel exports. Imports like `import * as cnst from "../cnst"` will fail. | Always run `akan sync <name>` after creating, renaming, or deleting any module file |
|
|
167
185
|
| Use JS `#private` methods in service classes | Akan's build system rejects `#private`. Use TypeScript `private` keyword instead. | `private _methodName()` — never `#_methodName()` |
|
|
168
186
|
| Use `console.log()` | Biome lint forbids `console.log`. Only `console.error`, `console.info`, `console.warn` are allowed. | Use one of the three allowed console methods |
|
|
169
187
|
| Import server APIs (`fs`, `Bun`, `process.env`) in `ui/`, `webkit/`, or `common/` | Server-only imports in client code cause build failures. | Keep server dependencies in `lib/`, `srvkit/`, or `private/` only |
|
|
170
|
-
| Skip running `akan
|
|
188
|
+
| Skip running `akan sync` after deleting a file | Deleted files remain referenced in barrel exports, causing import errors everywhere. | Run `akan sync <name>` after every file add, remove, or rename |
|
|
171
189
|
| Use "use client" or `useState`/`useEffect` in pages/*.tsx, *.Unit.tsx, and *.View.tsx files | Server code cannot use React hooks. Wrap in a separate `"use client"` component. | Move hook logic to `webkit/` or a `"use client"` UI component |
|
|
172
190
|
| Use `<a>` tag for internal navigation between pages | Akan.js uses `<Link>` from `akanjs/ui` for client-side navigation — avoids full page reloads. | `import { Link } from "akanjs/ui"` and use `<Link href="/task">...</Link>` |
|
|
173
191
|
|
|
174
192
|
## Generated File Tracker (Quick Reference)
|
|
175
193
|
|
|
176
|
-
These files are regenerated by `akan
|
|
194
|
+
These files are regenerated by `akan sync` and overwritten on every sync. **Do not hand-edit them.**
|
|
177
195
|
|
|
178
196
|
| File | Generated From | Purpose |
|
|
179
197
|
|------|---------------|---------|
|
|
180
|
-
|
|
|
181
|
-
|
|
|
182
|
-
|
|
|
183
|
-
|
|
|
184
|
-
|
|
|
185
|
-
|
|
|
186
|
-
|
|
|
187
|
-
|
|
|
198
|
+
| `*/lib/cnst.ts` | All `*/lib/*/**.constant.ts` | Barrel for all constants |
|
|
199
|
+
| `*/lib/db.ts` | All `*/lib/<model>/*.document.ts` | Barrel for all document models |
|
|
200
|
+
| `*/lib/dict.ts` | All `*/lib/*/**.dictionary.ts` | Barrel for all dictionaries |
|
|
201
|
+
| `*/lib/option.ts` | Generated option helpers | Option helper entry |
|
|
202
|
+
| `*/lib/sig.ts` | All `*/lib/**/**.signal.ts` | Barrel for all signals |
|
|
203
|
+
| `*/lib/srv.ts` | All `*/lib/**/**.service.ts` | Barrel for all services |
|
|
204
|
+
| `*/lib/st.ts` | All `*/lib/**/**.store.ts` | Barrel for all stores |
|
|
205
|
+
| `*/lib/useClient.ts` | Client-safe module re-exports | Client-side import entry |
|
|
206
|
+
| `*/lib/useServer.ts` | Server-only module re-exports | Server-side import entry |
|
|
188
207
|
| `apps/*/client.ts` | App-wide client barrel | The `fetch` and `st` instances |
|
|
189
208
|
| `apps/*/server.ts` | App-wide server barrel | Server-side service resolution |
|
|
190
|
-
| `*/lib
|
|
209
|
+
| `*/lib/**/index.ts` | Per-module barrel | Module-level re-exports |
|
|
191
210
|
| `*/ui/index.ts` | All UI component files | UI barrel |
|
|
192
211
|
| `*/webkit/index.ts` | All webkit files | Webkit barrel |
|
|
193
212
|
| `*/srvkit/index.ts` | All srvkit files | Srvkit barrel |
|
|
@@ -206,7 +225,7 @@ rewrite the entire file.
|
|
|
206
225
|
|
|
207
226
|
### Recipe 1: Adding a New Field to a Model
|
|
208
227
|
|
|
209
|
-
**Files to edit (in order):** `constant.ts` → `dictionary.ts` → `Template.tsx` → `Unit.tsx` → `akan
|
|
228
|
+
**Files to edit (in order):** `constant.ts` → `dictionary.ts` → `Template.tsx` → `Unit.tsx` → `akan sync`
|
|
210
229
|
|
|
211
230
|
```typescript
|
|
212
231
|
// 1. apps/<app>/lib/<model>/<model>.constant.ts
|
|
@@ -249,7 +268,7 @@ const form = st.use.taskForm();
|
|
|
249
268
|
})}>{task.priority}</span>
|
|
250
269
|
|
|
251
270
|
// 5. Regenerate barrels
|
|
252
|
-
// akan
|
|
271
|
+
// akan sync <name>
|
|
253
272
|
```
|
|
254
273
|
|
|
255
274
|
---
|
|
@@ -491,7 +510,7 @@ For each business question, follow this chain:
|
|
|
491
510
|
|
|
492
511
|
## Auto-Generated API Reference
|
|
493
512
|
|
|
494
|
-
akan
|
|
513
|
+
akan sync automatically generates APIs across all layers. Only write custom logic — never hand-write what the framework generates.
|
|
495
514
|
|
|
496
515
|
### Signal — Endpoint Auto-Generation
|
|
497
516
|
|
|
@@ -542,4 +561,4 @@ akan scan automatically generates APIs across all layers. Only write custom logi
|
|
|
542
561
|
| `exists[Query](args)`, `count[Query](args)` | Existence check and count |
|
|
543
562
|
| `insight[Query](args)`, `query[Query](args)` | Insight and raw query |
|
|
544
563
|
|
|
545
|
-
**Rule**: Define `Filter` with `.query()` conditions in `document.ts`. akan
|
|
564
|
+
**Rule**: Define `Filter` with `.query()` conditions in `document.ts`. akan sync auto-generates all 10 query helper methods per filter. Write `Document` chain methods only for state transitions with validation.
|