@akanjs/cli 2.3.9-rc.0 → 2.3.9-rc.10

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 (35) hide show
  1. package/incrementalBuilder.proc.js +3945 -532
  2. package/index.js +12712 -7496
  3. package/package.json +2 -2
  4. package/templates/appSample/common/formatters.ts +1 -1
  5. package/templates/appSample/common/validators.ts +1 -1
  6. package/templates/appSample/lib/_noti/noti.dictionary.ts +1 -1
  7. package/templates/appSample/lib/_noti/noti.service.ts +1 -1
  8. package/templates/appSample/lib/_noti/noti.signal.ts +1 -1
  9. package/templates/appSample/lib/_noti/noti.store.ts +1 -1
  10. package/templates/appSample/lib/task/task.abstract.ts +1 -1
  11. package/templates/appSample/lib/task/task.constant.ts +1 -1
  12. package/templates/appSample/lib/task/task.dictionary.ts +1 -1
  13. package/templates/appSample/lib/task/task.document.ts +3 -3
  14. package/templates/appSample/lib/task/task.service.ts +2 -2
  15. package/templates/appSample/lib/task/task.signal.spec.ts +70 -0
  16. package/templates/appSample/lib/task/task.signal.test.ts +20 -0
  17. package/templates/appSample/lib/task/task.signal.ts +2 -2
  18. package/templates/appSample/lib/task/task.store.ts +2 -2
  19. package/templates/appSample/srvkit/AuthGuard.ts +1 -1
  20. package/templates/appSample/srvkit/SessionInternalArg.ts +1 -1
  21. package/templates/appSample/ui/GlobalLoading.tsx +1 -1
  22. package/templates/appSample/ui/QuantityControl.tsx +1 -1
  23. package/templates/appSample/webkit/useDebounce.ts +1 -1
  24. package/templates/lib/useClient.ts +1 -4
  25. package/templates/module/__Model__.Unit.tsx +1 -4
  26. package/templates/module/__model__.constant.ts +0 -1
  27. package/templates/module/__model__.dictionary.ts +10 -4
  28. package/templates/workspaceRoot/.cursor/rules/{akan-scan-conventions.mdc.template → akan-sync-conventions.mdc.template} +3 -3
  29. package/templates/workspaceRoot/.cursor/rules/akan.mdc.template +12 -0
  30. package/templates/workspaceRoot/AGENTS.md.template +91 -58
  31. package/templates/workspaceRoot/biome.json.template +6 -6
  32. package/templates/workspaceRoot/docs/AI-DEVELOPMENT.md.template +51 -0
  33. package/templates/workspaceRoot/docs/GENERATED.md.template +54 -0
  34. package/templates/workspaceRoot/package.json.template +26 -0
  35. 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.0",
3
+ "version": "2.3.9-rc.10",
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.0",
37
+ "akanjs": "2.3.9-rc.10",
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 scan into common/index.ts barrel automatically.
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 scan into common/index.ts barrel automatically.
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 scan into dict.ts barrel.
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 scan into srv.ts barrel.
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 scan into sig.ts barrel.
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 scan into st.ts barrel.
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 scan auto-discovers each file and registers it in the corresponding barrel:
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 scan into cnst.ts barrel.
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 scan into dict.ts barrel.
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 scan).
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 scan into db.ts barrel.
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: "completed", at: dayjs(), note: "" });
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 scan (do not write manually):
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 scan into srv.ts barrel.
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 scan (do not write manually):
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 scan into sig.ts barrel.
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 scan (do not write manually):
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 scan into st.ts barrel.
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 scan into srvkit/index.ts barrel automatically.
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 scan into srvkit/index.ts barrel automatically.
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 scan into ui/index.ts barrel automatically.
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 scan into ui/index.ts barrel automatically.
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 scan into webkit/index.ts barrel automatically.
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: `
@@ -10,7 +10,6 @@ export default function getContent(scanInfo: AppInfo | LibInfo | null, dict: Dic
10
10
  import { via } from "akanjs/constant";
11
11
 
12
12
  export class ${dict.Model}Input extends via((field) => ({
13
- field: field(String).optional(),
14
13
  })) {}
15
14
 
16
15
  export class ${dict.Model}Object extends via(${dict.Model}Input, (field) => ({})) {}
@@ -3,9 +3,17 @@ import type { AppInfo, LibInfo } from "akanjs";
3
3
  interface Dict {
4
4
  Model: string;
5
5
  model: string;
6
+ modelDescEn?: string;
7
+ modelDescKo?: string;
8
+ modelLabelEn?: string;
9
+ modelLabelKo?: string;
6
10
  sysName: string;
7
11
  }
8
12
  export default function getContent(scanInfo: AppInfo | LibInfo | null, dict: Dict) {
13
+ const modelLabelEn = dict.modelLabelEn ?? dict.Model;
14
+ const modelLabelKo = dict.modelLabelKo ?? dict.Model;
15
+ const modelDescEn = dict.modelDescEn ?? `Manage ${modelLabelEn.toLowerCase()}.`;
16
+ const modelDescKo = dict.modelDescKo ?? `${modelLabelKo}을 관리합니다.`;
9
17
  return `
10
18
  import { modelDictionary } from "akanjs/dictionary";
11
19
 
@@ -14,11 +22,9 @@ import type { ${dict.Model}Endpoint, ${dict.Model}Slice } from "./${dict.model}.
14
22
 
15
23
  export const dictionary = modelDictionary(["en", "ko"])
16
24
  .of((t) =>
17
- t(["${dict.Model}", "${dict.Model}"]).desc(["${dict.Model} description", "${dict.Model} 설명"])
25
+ t(["${modelLabelEn}", "${modelLabelKo}"]).desc(["${modelDescEn}", "${modelDescKo}"])
18
26
  )
19
- .model<${dict.Model}>((t) => ({
20
- field: t(["Field", "필드"]).desc(["Field description", "필드 설명"]),
21
- }))
27
+ .model<${dict.Model}>((t) => ({}))
22
28
  .insight<${dict.Model}Insight>((t) => ({}))
23
29
  .slice<${dict.Model}Slice>((fn) => ({
24
30
  inPublic: fn(["${dict.Model} In Public", "${dict.Model} 공개"]).arg((t) => ({})),
@@ -1,10 +1,10 @@
1
1
  ---
2
- description: scanSync-enforced Akan app/lib layout and generated index conventions
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 Scan Conventions
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 scanSync; do not hand-edit or track them.
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,18 @@ 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
+ - If `plan_workflow` returns `planPath` or `next.tool=apply_workflow`, call `apply_workflow({ planPath })` before
21
+ editing source files directly.
22
+ - After `apply_workflow`, run `run_validation` with `validationTarget` when present; otherwise use `applyReportPath`.
23
+ - Direct source edits are denied when an allowlisted Akan workflow or repair tool can perform the change.
24
+ - Direct edits are fallback only after `list_workflows`/`explain_workflow` show no matching workflow, or after
25
+ `apply_workflow` reports unsupported/no-op/failed diagnostics that require manual action.
26
+ - For compound requests, split the request into workflows and apply each `planPath` in order, such as `create-module`
27
+ followed by `add-field`.
28
+ - If generated output is stale or broken, update the owning source file and run `akan repair generated` or
29
+ `akan sync <app-or-lib>` instead of patching generated files.
18
30
  - For new domain behavior, inspect sibling `constant`, `dictionary`, `signal`, `document`, `service`, `store`, and UI
19
31
  module files before changing shape.
20
32
  - Update `*.abstract.md` when business invariants, workflows, or public behavior change. Do not update it for