@akanjs/cli 3.0.0-alpha.8 → 3.0.0-alpha.80

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 (152) hide show
  1. package/.build-stamp +1 -1
  2. package/README.ko.md +1 -1
  3. package/README.md +1 -1
  4. package/{abstractCompactor-e5yq60qz.js → abstractCompactor-pwc6d6yk.js} +3 -5
  5. package/{agent.command-pggr92jj.js → agent.command-180hh0ye.js} +11 -12
  6. package/{application.command-692yap1d.js → application.command-gmcgen9y.js} +59 -35
  7. package/{applicationBuildRunner-ha3y8ew8.js → applicationBuildRunner-tavpx3fv.js} +22 -20
  8. package/{applicationReleasePackager-gg8ccbeh.js → applicationReleasePackager-ktjt0erc.js} +8 -9
  9. package/buildBatch.proc.js +12 -13
  10. package/capacitorApp-kwa67kzs.js +12 -0
  11. package/{cloud.command-wqfsyzv2.js → cloud.command-gpq7qxrc.js} +15 -17
  12. package/commandManifest.json +1 -1
  13. package/{context.command-9bxtdn0w.js → context.command-dk2m2sjx.js} +19 -22
  14. package/dependencyScanner-c64m6fxp.js +8 -0
  15. package/fleet.command-3bkyyd3a.js +836 -0
  16. package/getDirname-dr3jz1xg.js +7 -0
  17. package/{guideline.command-nfqahxex.js → guideline.command-w6ddv13r.js} +16 -17
  18. package/guidelines/agentRule/agentRule.generate.json +31 -0
  19. package/guidelines/agentRule/agentRule.instruction.md +525 -0
  20. package/guidelines/conventions/conventions.generate.json +25 -0
  21. package/guidelines/conventions/conventions.instruction.md +847 -0
  22. package/guidelines/cssRule/cssRule.instruction.md +7 -0
  23. package/guidelines/framework/framework.generate.json +3 -2
  24. package/guidelines/framework/framework.instruction.md +1 -9
  25. package/guidelines/mcpRule/mcpRule.generate.json +25 -0
  26. package/guidelines/mcpRule/mcpRule.instruction.md +217 -0
  27. package/guidelines/queryRule/queryRule.generate.json +31 -0
  28. package/guidelines/queryRule/queryRule.instruction.md +143 -0
  29. package/guidelines/runtimeRule/runtimeRule.generate.json +25 -0
  30. package/guidelines/runtimeRule/runtimeRule.instruction.md +176 -0
  31. package/guidelines/ssrRule/ssrRule.instruction.md +49 -5
  32. package/guidelines/transportRule/transportRule.generate.json +31 -0
  33. package/guidelines/transportRule/transportRule.instruction.md +60 -0
  34. package/guidelines/workspaceOnboarding/workspaceOnboarding.generate.json +24 -0
  35. package/guidelines/workspaceOnboarding/workspaceOnboarding.instruction.md +263 -0
  36. package/guidelines/workspaceRecipes/workspaceRecipes.generate.json +33 -0
  37. package/guidelines/workspaceRecipes/workspaceRecipes.instruction.md +402 -0
  38. package/incrementalBuilder.proc.js +21 -22
  39. package/{index-4hek879p.js → index-1kzsn94d.js} +1 -1
  40. package/{index-p3f4q532.js → index-3c9n3nt8.js} +31 -30
  41. package/index-3d9y066c.js +76 -0
  42. package/{index-76rn3g2c.js → index-47pj3aee.js} +4 -4
  43. package/{index-gex7c8kd.js → index-5n0hf8r9.js} +65 -38
  44. package/{index-mssqn2sq.js → index-5y7qba1c.js} +79 -33
  45. package/index-6wwzs1bc.js +7 -0
  46. package/{index-wnp7hwq7.js → index-92zztqy1.js} +7 -7
  47. package/{index-fgc8r6dj.js → index-a5vmm6x4.js} +3 -1
  48. package/{index-azv0xjb5.js → index-a8h184wr.js} +15 -11
  49. package/{index-67546d0j.js → index-ayy5ecax.js} +1 -1
  50. package/{index-xhgtkekh.js → index-b2nt0w0k.js} +27 -27
  51. package/{index-xys926f2.js → index-by5gpp8x.js} +4 -4
  52. package/{index-bz2sfzzt.js → index-cd5x18qh.js} +32 -32
  53. package/{index-bjpxzr6s.js → index-ctphds8v.js} +1 -1
  54. package/{index-w6wbb612.js → index-dq6wq1tv.js} +94 -61
  55. package/{index-js368gk9.js → index-ee8z8adx.js} +13 -13
  56. package/{index-5dw57f0d.js → index-f7wewj2v.js} +155 -76
  57. package/{index-6rhabeev.js → index-ft1sfjz0.js} +36 -25
  58. package/{index-7myvn0f0.js → index-h7yw2z9n.js} +7 -7
  59. package/{index-5pmx7bkg.js → index-j9vnq8tr.js} +18 -18
  60. package/{index-vg0sj9nc.js → index-k1n0h7dk.js} +840 -201
  61. package/{index-vtj3zps1.js → index-k86zc21w.js} +57 -57
  62. package/{index-46tjzh6s.js → index-mfm35dhh.js} +29 -29
  63. package/{index-xjd3rqzj.js → index-n68jx79w.js} +6 -15
  64. package/{index-ztpw544k.js → index-qcracvc9.js} +8 -8
  65. package/{index-2c3e2xh8.js → index-saheq2bs.js} +4 -4
  66. package/{index-gmy45vz2.js → index-v0s24t8j.js} +14 -14
  67. package/{index-0cj2zxbm.js → index-wet2ytnr.js} +12 -11
  68. package/{index-j9cxndc0.js → index-x23688ar.js} +12 -7
  69. package/index-xnhf8tr9.js +109 -0
  70. package/{index-a1e0j63x.js → index-xxe90kqy.js} +58 -25
  71. package/{index-qb61h0k7.js → index-z3c9mmw4.js} +1098 -1360
  72. package/index.js +22 -24
  73. package/{library.command-md9715jz.js → library.command-wpedct6c.js} +11 -9
  74. package/{localRegistry.command-exkbamyj.js → localRegistry.command-pjdxa01g.js} +20 -22
  75. package/{module.command-w5aaddxk.js → module.command-8n4kct3e.js} +13 -15
  76. package/{package.command-yfkrcy0z.js → package.command-fcbd0b1d.js} +8 -9
  77. package/package.json +5 -7
  78. package/{page.command-echbavp4.js → page.command-x4379grq.js} +7 -8
  79. package/{primitive.command-9fbhspw2.js → primitive.command-8etmnqhq.js} +13 -15
  80. package/{quality.command-3pnt9yep.js → quality.command-hfx7z8yr.js} +226 -104
  81. package/recipeScanner-fpagtgc1.js +11 -0
  82. package/{repair.command-carj4vax.js → repair.command-j0fs5qkz.js} +12 -13
  83. package/{routeSourceValidator-e4jmbfcx.js → routeSourceValidator-ze6x894c.js} +0 -2
  84. package/{scalar.command-51ryzypc.js → scalar.command-2emk89eh.js} +11 -13
  85. package/templates/app/env/env.client.debug.ts.template +1 -5
  86. package/templates/app/env/env.client.develop.ts.template +1 -5
  87. package/templates/app/env/env.client.local.ts.template +1 -5
  88. package/templates/app/env/env.client.main.ts.template +1 -5
  89. package/templates/app/env/env.client.testing.ts.template +1 -5
  90. package/templates/app/env/env.client.type.ts +2 -6
  91. package/templates/app/env/env.server.debug.ts.template +1 -5
  92. package/templates/app/env/env.server.develop.ts.template +1 -5
  93. package/templates/app/env/env.server.local.ts.template +1 -5
  94. package/templates/app/env/env.server.main.ts.template +1 -5
  95. package/templates/app/main.ts +3 -1
  96. package/templates/appSample/lib/task/task.signal.ts +11 -6
  97. package/templates/appSample/srvkit/AuthGuard.ts +9 -5
  98. package/templates/appSample/srvkit/SessionInternalArg.ts +4 -3
  99. package/templates/crudPages/[__model__Id]/edit/page.tsx +6 -6
  100. package/templates/crudPages/[__model__Id]/page.tsx +10 -13
  101. package/templates/crudPages/new/page.tsx +6 -6
  102. package/templates/crudPages/page.tsx +9 -9
  103. package/templates/crudSinglePage/page.tsx +11 -7
  104. package/templates/env/_env.server.type.ts +7 -5
  105. package/templates/lib/db.ts +1 -1
  106. package/templates/lib/dict.ts +1 -1
  107. package/templates/lib/sig.ts +1 -1
  108. package/templates/lib/srv.ts +6 -2
  109. package/templates/module/__Model__.Util.tsx +1 -1
  110. package/templates/module/__Model__.Zone.tsx +3 -5
  111. package/templates/module/__model__.dictionary.ts +4 -1
  112. package/templates/module/__model__.signal.ts +2 -3
  113. package/templates/module/__model__.store.ts +1 -2
  114. package/templates/workspaceRoot/.gitignore.template +0 -1
  115. package/templates/workspaceRoot/AGENTS.md.template +4 -972
  116. package/templates/workspaceRoot/CLAUDE.md.template +19 -0
  117. package/templates/workspaceRoot/biome.json.template +4 -244
  118. package/templates/workspaceRoot/docs/AI-DEVELOPMENT.md.template +10 -14
  119. package/templates/workspaceRoot/docs/GENERATED.md.template +12 -10
  120. package/templates/workspaceRoot/package.json.template +2 -4
  121. package/typeChecker-3xgrzc2v.js +7 -0
  122. package/typecheck.proc.js +4 -5
  123. package/{workflow.command-91wmvm8k.js → workflow.command-cvpmzwjg.js} +21 -23
  124. package/{workspace.command-wa10mfd9.js → workspace.command-mbb5s3wx.js} +108 -91
  125. package/capacitorApp-p0z5yab2.js +0 -60
  126. package/dependencyScanner-f6grrep3.js +0 -9
  127. package/index-mxvakhsm.js +0 -66
  128. package/index-r24hmh0q.js +0 -4
  129. package/recipeScanner-tghkzrrt.js +0 -12
  130. package/templates/workspaceRoot/.cursor/rules/akan-page-routing.mdc.template +0 -43
  131. package/templates/workspaceRoot/.cursor/rules/akan-sync-conventions.mdc.template +0 -34
  132. package/templates/workspaceRoot/.cursor/rules/application-test-commands.mdc.template +0 -16
  133. package/templates/workspaceRoot/.cursor/rules/change-scope.mdc.template +0 -12
  134. package/templates/workspaceRoot/.cursor/rules/client-server-boundaries.mdc.template +0 -18
  135. package/templates/workspaceRoot/.cursor/rules/coding-comments.mdc.template +0 -25
  136. package/templates/workspaceRoot/.cursor/rules/coding-style.mdc.template +0 -44
  137. package/templates/workspaceRoot/.cursor/rules/domain-module-conventions.mdc.template +0 -17
  138. package/templates/workspaceRoot/.cursor/rules/format-lint.mdc.template +0 -12
  139. package/templates/workspaceRoot/.cursor/rules/lint-enforced-rules.mdc.template +0 -19
  140. package/templates/workspaceRoot/.cursor/rules/module-file-playbook.mdc.template +0 -62
  141. package/templates/workspaceRoot/.cursor/rules/naming-conventions.mdc.template +0 -15
  142. package/templates/workspaceRoot/.cursor/rules/react-component-style.mdc.template +0 -21
  143. package/templates/workspaceRoot/.cursor/rules/repo-overview.mdc.template +0 -17
  144. package/templates/workspaceRoot/.cursor/rules/scalar-modeling.mdc.template +0 -99
  145. package/templates/workspaceRoot/.cursor/rules/secrets-and-env.mdc.template +0 -13
  146. package/templates/workspaceRoot/.cursor/rules/service-signal-conventions.mdc.template +0 -83
  147. package/templates/workspaceRoot/.cursor/rules/srvkit-adapters-and-guards.mdc.template +0 -50
  148. package/templates/workspaceRoot/.cursor/rules/ssr-first.mdc.template +0 -59
  149. package/templates/workspaceRoot/.cursor/rules/typescript-imports.mdc.template +0 -16
  150. package/typeChecker-kravn7ns.js +0 -8
  151. /package/{index-0wae5ebk.js → index-meh558jy.js} +0 -0
  152. /package/{index-ss469dec.js → index-nz8prx9g.js} +0 -0
@@ -0,0 +1,76 @@
1
+ // @bun
2
+ // pkgs/@akanjs/devkit/libSource.ts
3
+ class LibSource {
4
+ static manifestKey = "akan";
5
+ static unhashedDirs = ["env"];
6
+ #lib;
7
+ constructor(lib) {
8
+ this.#lib = lib;
9
+ }
10
+ get #prefix() {
11
+ return `libs/${this.#lib.name}/`;
12
+ }
13
+ #isHashed(file) {
14
+ const relative = file.slice(this.#prefix.length);
15
+ return !LibSource.unhashedDirs.includes(relative.split("/")[0] ?? "");
16
+ }
17
+ async#hashableContent(file) {
18
+ const content = await this.#lib.workspace.readFile(file);
19
+ if (file !== `${this.#prefix}package.json`)
20
+ return content;
21
+ const manifest = JSON.parse(content);
22
+ delete manifest[LibSource.manifestKey];
23
+ return JSON.stringify(manifest);
24
+ }
25
+ async computeHash() {
26
+ const files = (await this.#lib.workspace.listGitFiles([`libs/${this.#lib.name}`], { untracked: true })).filter((file) => this.#isHashed(file));
27
+ const hasher = new Bun.CryptoHasher("sha256");
28
+ for (const file of files) {
29
+ hasher.update(file);
30
+ hasher.update("\x00");
31
+ hasher.update(await this.#hashableContent(file));
32
+ hasher.update("\x00");
33
+ }
34
+ return hasher.digest("hex").slice(0, 32);
35
+ }
36
+ async read() {
37
+ const manifest = await this.#lib.getPackageJson();
38
+ const akan = manifest[LibSource.manifestKey];
39
+ return akan?.source ?? null;
40
+ }
41
+ async write({ origin, sha }) {
42
+ const [manifest, hash] = await Promise.all([this.#lib.getPackageJson(), this.computeHash()]);
43
+ const akan = manifest[LibSource.manifestKey] ?? {};
44
+ const stamp = { origin, sha, hash, syncedAt: new Date().toISOString() };
45
+ await this.#lib.setPackageJson({ ...manifest, [LibSource.manifestKey]: { ...akan, source: stamp } });
46
+ return stamp;
47
+ }
48
+ async syncStamp({ origin, sha }) {
49
+ const [current, hash] = await Promise.all([this.read(), this.computeHash()]);
50
+ if (current?.origin === origin && current.sha === sha && current.hash === hash)
51
+ return { stamp: current, changed: false };
52
+ return { stamp: await this.write({ origin, sha }), changed: true };
53
+ }
54
+ async status() {
55
+ const [stamp, hash] = await Promise.all([this.read(), this.computeHash()]);
56
+ const drift = !stamp ? "unstamped" : stamp.hash === hash ? "clean" : "drifted";
57
+ return { lib: this.#lib.name, drift, stamp, hash };
58
+ }
59
+ }
60
+ function formatLibStatuses(statuses) {
61
+ const marks = { clean: "clean ", drifted: "DRIFTED ", unstamped: "unstamped" };
62
+ const sections = [
63
+ "Akan Library Source Status",
64
+ "",
65
+ ...statuses.map((status) => {
66
+ const origin = status.stamp ? `${status.stamp.origin}@${status.stamp.sha}` : "no akan.source in package.json";
67
+ return ` ${marks[status.drift]} libs/${status.lib} ${origin}`;
68
+ }),
69
+ "",
70
+ `drifted: ${statuses.filter((status) => status.drift === "drifted").length} / ${statuses.length}`
71
+ ];
72
+ return sections.join(`
73
+ `);
74
+ }
75
+
76
+ export { LibSource, formatLibStatuses };
@@ -37,10 +37,10 @@ var resolveMobileTargets = async (app, selection) => {
37
37
  if (!selection && targets.length === 1)
38
38
  return targets;
39
39
  if (!selection) {
40
- const choices2 = await getMobileTargetChoices(app);
41
- if (choices2.length === 1)
42
- return resolveMobileTargets(app, choices2[0]);
43
- throw new Error(`Multiple mobile targets found for ${app.name}. Pass --target <${choices2.join("|")}|all>.`);
40
+ const choices = await getMobileTargetChoices(app);
41
+ if (choices.length === 1)
42
+ return resolveMobileTargets(app, choices[0]);
43
+ throw new Error(`Multiple mobile targets found for ${app.name}. Pass --target <${choices.join("|")}|all>.`);
44
44
  }
45
45
  if (selection === "all") {
46
46
  if (Object.keys(config.mobile.targets).length > 1)
@@ -1,26 +1,29 @@
1
1
  // @bun
2
- import {
3
- AkanContextAnalyzer
4
- } from "./index-mssqn2sq.js";
5
- import {
6
- Prompter
7
- } from "./index-7myvn0f0.js";
8
2
  import {
9
3
  runner,
10
4
  script
11
- } from "./index-6rhabeev.js";
5
+ } from "./index-ft1sfjz0.js";
12
6
  import {
13
- AGENT_BLOCK_END,
14
7
  AGENT_BLOCK_START,
15
- AppExecutor,
16
- LibExecutor,
8
+ AGENT_BLOCK_END,
9
+ stampBlockVersion,
10
+ readDevkitVersion,
11
+ upsertAgentBlock,
17
12
  renderRecipeEntries,
18
- upsertAgentBlock
19
- } from "./index-vg0sj9nc.js";
13
+ CLAUDE_COMMENT_RULE,
14
+ AppExecutor,
15
+ LibExecutor
16
+ } from "./index-k1n0h7dk.js";
17
+ import {
18
+ collectRecipeSources2,
19
+ scanRecipes2
20
+ } from "./index-by5gpp8x.js";
20
21
  import {
21
- collectRecipeSources,
22
- scanRecipes
23
- } from "./index-xys926f2.js";
22
+ AkanContextAnalyzer
23
+ } from "./index-5y7qba1c.js";
24
+ import {
25
+ Prompter
26
+ } from "./index-h7yw2z9n.js";
24
27
 
25
28
  // pkgs/@akanjs/cli/agent/agent.script.ts
26
29
  import { Logger as Logger2 } from "akanjs/common";
@@ -74,8 +77,8 @@ Keep a sample only while you are still learning its pattern; delete it once your
74
77
  `;
75
78
  };
76
79
  var renderRecipeIndex = async (workspace) => {
77
- const sources = await collectRecipeSources(`${workspace.workspaceRoot}/pkgs/akanjs/ui`, "akanjs/ui", "recipe");
78
- const recipes = scanRecipes(sources);
80
+ const sources = await collectRecipeSources2(`${workspace.workspaceRoot}/pkgs/akanjs/ui`, "akanjs/ui", "recipe");
81
+ const recipes = scanRecipes2(sources);
79
82
  const scopeIndexPointer = `App and lib recipes are **not** listed here. Each app/lib carries its own generated index \u2014
80
83
  \`apps/<app>/AGENTS.md\` / \`libs/<lib>/AGENTS.md\` (\`## Recipes In Scope\`) \u2014 regenerated by \`akan sync\` and
81
84
  verified by \`akan lint\`. When working inside an app or lib, consult that file before consuming or authoring a look.`;
@@ -87,15 +90,14 @@ ${scopeIndexPointer}
87
90
  `;
88
91
  return `## Recipes
89
92
 
90
- Framework UI recipes (Tailwind-variant look factories), importable from every app and lib. Consume by exact name \u2014
91
- \`import { <name> } from "akanjs/ui"\`, then \`<name>(variants?, className?)\`. The second arg merges internally and
92
- takes **an array too**, so never wrap it in \`cn()\`: \`<name>({}, ["h-full", isWide && "w-full", className])\`.
93
- Do not guess recipe names, import paths, or variant values; the list below carries
94
- the full contract (\`*\` marks the default, \`key?\` is a boolean flag), so there is no need to open the recipe file to
95
- consume one. tsc still reports variant mistakes. **Before inlining a repeated surface (card, box,
96
- tile, \u2026): reuse a recipe, or add one as \`apps/<app>/ui/Recipe/<name>.ts\` (one recipe per file, re-exported from
97
- that folder's \`index.ts\`) \u2014 never re-implement the same look inline in several places, and never author a
98
- near-duplicate.** Full authoring/consumption policy: the \`recipeRule\` guideline.
93
+ Framework UI recipes (Tailwind-variant look factories), importable from every app and lib: \`import { <name> } from
94
+ "akanjs/ui"\`, then \`<name>(variants?, className?)\`. The second arg merges internally and takes **an array too**, so
95
+ never wrap it in \`cn()\`: \`<name>({}, ["h-full", isWide && "w-full", className])\`. The list below is the full
96
+ contract (\`*\` marks the default, \`key?\` is a boolean flag) \u2014 do not guess a name, path, or variant value, and do
97
+ not open the recipe file to consume one. **Before inlining a repeated surface (card, box, tile, \u2026): reuse a recipe,
98
+ or add one as \`apps/<app>/ui/Recipe/<name>.ts\`, one per file, re-exported from that folder's \`index.ts\` \u2014 never
99
+ re-implement the same look inline in several places, and never author a near-duplicate.** Full policy: the
100
+ \`recipeRule\` guideline.
99
101
 
100
102
  ${renderRecipeEntries(recipes)}
101
103
 
@@ -103,19 +105,33 @@ ${scopeIndexPointer}
103
105
 
104
106
  `;
105
107
  };
108
+ var renderGuides = async (context) => {
109
+ const conventions = (await Prompter.getInstruction("conventions")).trim();
110
+ if (context.pkgs.some((pkg) => pkg.name === "akanjs"))
111
+ return conventions;
112
+ const onboarding = (await Prompter.getInstruction("workspaceOnboarding")).trim();
113
+ const appName = context.apps[0]?.name ?? "app";
114
+ return `${conventions}
115
+
116
+ ${onboarding.replaceAll("<%= appName %>", appName)}`;
117
+ };
106
118
  var renderManagedBlock = async (workspace) => {
107
119
  const context = await AkanContextAnalyzer.analyze(workspace);
108
120
  const frameworkGuide = await Prompter.getInstruction("framework");
109
121
  const appNames = context.apps.map((app) => app.name);
110
122
  const sampleCleanup = await renderSampleCleanup(workspace, appNames);
111
123
  const recipeIndex = await renderRecipeIndex(workspace);
112
- return `## Workspace
124
+ const guides = await renderGuides(context);
125
+ const version = await readDevkitVersion();
126
+ const block = `## Workspace
113
127
 
114
128
  - Repo: ${context.repoName}
115
129
  - Apps: ${context.apps.map((app) => app.name).join(", ") || "none"}
116
130
  - Libraries: ${context.libs.map((lib) => lib.name).join(", ") || "none"}
117
131
  - Packages: ${context.pkgs.map((pkg) => pkg.name).join(", ") || "none"}
118
132
 
133
+ ${guides}
134
+
119
135
  ${sampleCleanup}## Akan Module Abstracts
120
136
 
121
137
  - Before changing a domain, service, or scalar module, read its \`*.abstract.md\` file first.
@@ -131,15 +147,12 @@ If generated output is stale or broken, update the owning source file and run \`
131
147
 
132
148
  ${recipeIndex}## MCP Workflow Policy
133
149
 
134
- - Prefer Akan MCP workflows before direct source edits.
135
- - Direct source edits are denied when an allowlisted Akan workflow or repair tool can perform the change.
136
- - Use \`akan mcp --mode plan\` to inspect \`list_workflows\`, \`explain_workflow\`, and \`plan_workflow\`.
137
- - If \`plan_workflow\` returns \`planPath\` or \`next.tool=apply_workflow\`, call \`apply_workflow({ planPath })\` before editing source files directly.
138
- - Use \`akan mcp --mode apply\` only for allowlisted \`apply_workflow\`, \`run_validation\`, and repair tools.
139
- - After \`apply_workflow\`, run \`run_validation\` with \`validationTarget\` when present; otherwise use \`applyReportPath\`.
140
- - If no workflow exists, or apply reports unsupported/no-op/failed diagnostics that require manual action, keep edits scoped to owning source files and never patch generated files directly.
141
- - For compound requests, split the request into workflows and apply each \`planPath\` in order, such as \`create-module\` followed by \`add-field\`.
142
- - **CLI-only fallback (MCP not connected):** \`akan mcp\` starts a stdio MCP server, so the \`list_workflows\`/\`plan_workflow\`/\`apply_workflow\` tools exist only when your agent is wired to it as an MCP client. When they are unavailable, the CLI is a first-class equivalent: \`akan workflow list\` / \`explain <name>\` / \`plan <name> ... --format json --out <planPath>\` / \`apply <planPath> --format json\`, \`akan doctor --strict --format json\` for validation, and \`akan repair generated|imports|module-shape --app <app> --format json\` for repairs. Scaffolding primitives (\`create-module\`/\`create-scalar\`/\`create-service\` take the target app/lib as a POSITIONAL arg; \`add-field\`/\`add-enum-field\` use \`--app\`/\`--module\` flags) call the same code the workflows do.
150
+ - **Prefer an Akan workflow to a direct source edit.** A direct edit is denied when an allowlisted workflow or repair tool can make the change.
151
+ - Inspect with \`akan mcp --mode plan\` (\`list_workflows\`, \`explain_workflow\`, \`plan_workflow\`); apply with \`akan mcp --mode apply\`, which allowlists \`apply_workflow\`, \`run_validation\`, and the repair tools.
152
+ - If \`plan_workflow\` returns \`planPath\` or \`next.tool=apply_workflow\`, call \`apply_workflow({ planPath })\` before editing source. Then run \`run_validation\` with \`validationTarget\` when present, otherwise \`applyReportPath\`.
153
+ - Split a compound request into workflows and apply each \`planPath\` in order, such as \`create-module\` followed by \`add-field\`.
154
+ - When no workflow exists, or apply reports unsupported/no-op/failed diagnostics needing manual action, keep edits scoped to owning source files and never patch generated files directly.
155
+ - **MCP not connected?** The CLI is a first-class equivalent \u2014 \`akan workflow list\` / \`explain\` / \`plan\` / \`apply\`, \`akan doctor\`, \`akan repair\`. See the onboarding guide's CLI-only fallback for the exact flags.
143
156
 
144
157
  ## Validation
145
158
 
@@ -148,7 +161,19 @@ ${context.validationCommands.map((command) => `- \`${command}\``).join(`
148
161
 
149
162
  ## Framework Guide
150
163
 
151
- ${frameworkGuide.trim()}`;
164
+ ${frameworkGuide.trim()}
165
+
166
+ ## Before You Finish
167
+
168
+ 1. \`bun run akan lint <appName>\` \u2014 Tailwind class order, \`Err\`, \`console\`, \`#private\` scope, unused imports.
169
+ 2. \`bun run akan typecheck <appName>\` \u2014 server/client boundary violations.
170
+ 3. \`bun run akan sync <appName>\` if you added, renamed, or deleted any file.
171
+ 4. Did you write or change a \`.tsx\` file? \`bun run akan quality ssr\` \u2014 did the server share hold, and did you add
172
+ a \`"use client"\` you cannot justify?
173
+ 5. Re-read the file you wrote against its section above.
174
+ 6. Did you add a comment? Delete it unless it documents an external constraint or a security decision.
175
+ 7. Did behavior or an invariant change? Update the module's \`*.abstract.md\`.`;
176
+ return version ? stampBlockVersion(block, version) : block;
152
177
  };
153
178
  var renderAgentsMd = async (workspace, existing) => {
154
179
  const block = await renderManagedBlock(workspace);
@@ -171,6 +196,8 @@ var renderClaudeMd = async (workspace) => {
171
196
  return `# ${context.repoName} \u2014 Claude Code Guide
172
197
 
173
198
  @AGENTS.md
199
+
200
+ ${CLAUDE_COMMENT_RULE}
174
201
  `;
175
202
  };
176
203
  var renderCursorRule = () => `---
@@ -218,7 +245,7 @@ class AgentRunner extends runner("agent") {
218
245
  written.push(`${exec.type}s/${exec.name}/AGENTS.md`);
219
246
  } catch (error) {
220
247
  const message = error instanceof Error ? error.message : String(error);
221
- Logger.rawLog(`Skipped ${exec.type}s/${exec.name}/AGENTS.md (${message})`, undefined, "warn");
248
+ Logger.rawLog(`Skipped ${exec.type}s/${exec.name}/AGENTS.md (${message})`, undefined, "error");
222
249
  }
223
250
  };
224
251
  for (const name of libNames)
@@ -1,36 +1,45 @@
1
1
  // @bun
2
2
  import {
3
- workflowRunArtifactPath,
4
- workflowSyncDir
5
- } from "./index-2c3e2xh8.js";
6
- import {
3
+ extractBlockVersion,
4
+ readDevkitVersion,
5
+ rootAllowedFiles,
6
+ rootAllowedDirs,
7
+ isScannedRootEntry,
7
8
  AppExecutor,
8
- LibExecutor,
9
- appRootAllowedDirs,
10
- appRootAllowedFiles,
11
- isScannedAppRootEntry
12
- } from "./index-vg0sj9nc.js";
13
- import {
14
- collectRecipeSources,
15
- findInlineRecipeDuplicates,
16
- scanRecipes
17
- } from "./index-xys926f2.js";
9
+ LibExecutor
10
+ } from "./index-k1n0h7dk.js";
18
11
  import {
19
12
  FileSys
20
- } from "./index-67546d0j.js";
13
+ } from "./index-ayy5ecax.js";
14
+ import {
15
+ collectRecipeSources2,
16
+ scanRecipes2,
17
+ findInlineRecipeDuplicates2
18
+ } from "./index-by5gpp8x.js";
19
+ import {
20
+ workflowRunArtifactPath,
21
+ workflowSyncDir
22
+ } from "./index-saheq2bs.js";
21
23
 
22
24
  // pkgs/@akanjs/devkit/akanContext.ts
23
25
  import { readdir } from "fs/promises";
24
26
  import path from "path";
25
27
  import { capitalize } from "akanjs/common";
26
- var resourceList = [
28
+ var guidelineResourceUri = (name) => `akan://guidelines/${name}`;
29
+ var staticResourceList = [
27
30
  { uri: "akan://docs/framework", name: "Akan framework guide", mimeType: "text/markdown" },
28
- { uri: "akan://guidelines/framework", name: "Framework guideline", mimeType: "text/markdown" },
29
- { uri: "akan://guidelines/modelSignal", name: "Model signal guideline", mimeType: "text/markdown" },
30
31
  { uri: "akan://workspace/summary", name: "Workspace summary", mimeType: "application/json" },
31
32
  { uri: "akan://workspace/apps", name: "Workspace apps", mimeType: "application/json" },
32
33
  { uri: "akan://workspace/modules", name: "Workspace modules", mimeType: "application/json" }
33
34
  ];
35
+ var buildResourceList = (guidelineNames) => [
36
+ ...staticResourceList,
37
+ ...guidelineNames.map((name) => ({
38
+ uri: guidelineResourceUri(name),
39
+ name: `${name} guideline`,
40
+ mimeType: "text/markdown"
41
+ }))
42
+ ];
34
43
  var cursorMcpConfigPath = ".cursor/mcp.json";
35
44
  var claudeMcpConfigPath = ".mcp.json";
36
45
  var codexMcpConfigPath = ".codex/config.toml";
@@ -124,7 +133,9 @@ var validationCommands = [
124
133
  "akan typecheck <app-name>",
125
134
  "akan test <app-or-lib-or-pkg>",
126
135
  "akan build <app-name>",
127
- "akan doctor --strict --format json"
136
+ "akan doctor --strict --format json",
137
+ "akan quality scan [--format json]",
138
+ "akan quality ssr [--format json]"
128
139
  ];
129
140
  var unknownGeneratedFilesFreshness = {
130
141
  status: "unknown",
@@ -365,6 +376,29 @@ class AkanContextAnalyzer {
365
376
  validationCommands
366
377
  };
367
378
  }
379
+ static async#agentGuideDrift(workspace) {
380
+ const installed = await readDevkitVersion();
381
+ if (!installed)
382
+ return null;
383
+ const content = await safeReadText(path.join(workspace.workspaceRoot, "AGENTS.md"));
384
+ if (content === null)
385
+ return null;
386
+ const stamped = extractBlockVersion(content);
387
+ if (stamped === installed)
388
+ return null;
389
+ const hint = "Re-render the AGENTS.md managed block from the installed framework release.";
390
+ if (!stamped)
391
+ return {
392
+ code: "agent-guide-unstamped",
393
+ message: `AGENTS.md carries no generated-version stamp, so its conventions may predate @akanjs/devkit ${installed}`,
394
+ hint
395
+ };
396
+ return {
397
+ code: "agent-guide-stale",
398
+ message: `AGENTS.md was generated by @akanjs/devkit ${stamped}, but ${installed} is installed`,
399
+ hint
400
+ };
401
+ }
368
402
  static async doctor(workspace, {
369
403
  strict = false,
370
404
  runIdOrPlan = null,
@@ -377,25 +411,37 @@ class AkanContextAnalyzer {
377
411
  repairAction("generated", "akan repair generated --app <app-or-lib>", "Refresh generated Akan files.", true),
378
412
  repairAction("format", "akan repair format --target <app-or-lib-or-pkg>", "Run the formatter/linter repair path.", true)
379
413
  ];
380
- for (const app of context.apps) {
381
- const appPath = path.join(workspace.workspaceRoot, app.path);
382
- for (const entry of await safeReadDir(appPath)) {
383
- if (!isScannedAppRootEntry(entry.name))
414
+ for (const sys of [...context.apps, ...context.libs]) {
415
+ const sysPath = path.join(workspace.workspaceRoot, sys.path);
416
+ for (const entry of await safeReadDir(sysPath)) {
417
+ if (!isScannedRootEntry(sys.type, entry.name))
384
418
  continue;
385
- const allowed = entry.isDirectory() ? appRootAllowedDirs.has(entry.name) : appRootAllowedFiles.has(entry.name);
419
+ const allowed = entry.isDirectory() ? rootAllowedDirs[sys.type].has(entry.name) : rootAllowedFiles[sys.type].has(entry.name);
386
420
  if (!allowed) {
387
- const action = repairAction("module-shape", `akan repair module-shape --app ${app.name}`, "Review app root shape and remove or move the unknown entry.", false);
421
+ const action = repairAction("module-shape", `akan repair module-shape --app ${sys.name}`, `Review ${sys.type} root shape and remove or move the unknown entry.`, false);
388
422
  diagnostics.push({
389
423
  severity: "error",
390
- code: "app-root-unknown-entry",
391
- path: `${app.path}/${entry.name}`,
392
- message: `Unexpected ${entry.isDirectory() ? "folder" : "file"} in app root: ${app.path}/${entry.name}`,
424
+ code: `${sys.type}-root-unknown-entry`,
425
+ path: `${sys.path}/${entry.name}`,
426
+ message: `Unexpected ${entry.isDirectory() ? "folder" : "file"} in ${sys.type} root: ${sys.path}/${entry.name}`,
393
427
  repairActions: [action]
394
428
  });
395
429
  repairActions.push(action);
396
430
  }
397
431
  }
398
432
  }
433
+ const agentDrift = await AkanContextAnalyzer.#agentGuideDrift(workspace);
434
+ if (agentDrift) {
435
+ const action = repairAction("generated", "akan agent install agents-md", agentDrift.hint, true);
436
+ diagnostics.push({
437
+ severity: "warning",
438
+ code: agentDrift.code,
439
+ path: "AGENTS.md",
440
+ message: agentDrift.message,
441
+ repairActions: [action]
442
+ });
443
+ repairActions.push(action);
444
+ }
399
445
  for (const sys of [...context.apps, ...context.libs]) {
400
446
  for (const module of sys.modules) {
401
447
  if (!module.abstract.exists) {
@@ -447,10 +493,10 @@ class AkanContextAnalyzer {
447
493
  }
448
494
  }
449
495
  for (const sys of [...context.apps, ...context.libs]) {
450
- const sources = await collectRecipeSources(path.join(workspace.workspaceRoot, sys.path, "ui"), "ui");
496
+ const sources = await collectRecipeSources2(path.join(workspace.workspaceRoot, sys.path, "ui"), "ui");
451
497
  if (sources.length === 0)
452
498
  continue;
453
- const recipes = scanRecipes(sources);
499
+ const recipes = scanRecipes2(sources);
454
500
  const files = [];
455
501
  const glob = new Bun.Glob("**/*.tsx");
456
502
  for await (const abs of glob.scan({ cwd: path.join(workspace.workspaceRoot, sys.path), absolute: true })) {
@@ -461,7 +507,7 @@ class AkanContextAnalyzer {
461
507
  content: await Bun.file(abs).text().catch(() => "")
462
508
  });
463
509
  }
464
- const duplicates = findInlineRecipeDuplicates(recipes, files);
510
+ const duplicates = findInlineRecipeDuplicates2(recipes, files);
465
511
  if (duplicates.length === 0)
466
512
  continue;
467
513
  const preview = duplicates.slice(0, 3).map((duplicate) => `${path.relative(workspace.workspaceRoot, duplicate.path)}:${duplicate.line}`).join(", ");
@@ -472,7 +518,7 @@ class AkanContextAnalyzer {
472
518
  message: `${sys.name}: ${duplicates.length} inline className(s) re-author a recipe fingerprint (${preview}${duplicates.length > 3 ? ", \u2026" : ""}) \u2014 consume the recipe instead.`
473
519
  });
474
520
  }
475
- const scanNames = async (uiDirPath, basename) => new Set(scanRecipes(await collectRecipeSources(uiDirPath, "ui", basename)).map((info) => info.name));
521
+ const scanNames = async (uiDirPath, basename) => new Set(scanRecipes2(await collectRecipeSources2(uiDirPath, "ui", basename)).map((info) => info.name));
476
522
  const declaredByImport = new Map;
477
523
  declaredByImport.set("akanjs/ui", await scanNames(path.join(workspace.workspaceRoot, "pkgs/akanjs/ui"), "recipe"));
478
524
  for (const sys of [...context.apps, ...context.libs])
@@ -587,4 +633,4 @@ class AkanContextAnalyzer {
587
633
  }
588
634
  }
589
635
 
590
- export { resourceList, codexMcpConfigPath, akanMcpInstallTargets, akanMcpInstallConfigPaths, createAkanMcpServer, createAkanCodexMcpServerBlock, upsertCodexMcpServerBlock, renderDoctorText, AkanContextAnalyzer };
636
+ export { buildResourceList, codexMcpConfigPath, akanMcpInstallTargets, akanMcpInstallConfigPaths, createAkanMcpServer, createAkanCodexMcpServerBlock, upsertCodexMcpServerBlock, renderDoctorText, AkanContextAnalyzer };
@@ -0,0 +1,7 @@
1
+ // @bun
2
+ // pkgs/@akanjs/devkit/getDirname.ts
3
+ import path from "path";
4
+ import { fileURLToPath } from "url";
5
+ var getDirname2 = (url) => path.dirname(fileURLToPath(url));
6
+
7
+ export { getDirname2 };
@@ -5,7 +5,7 @@ import * as path from "path";
5
5
  import chalk from "chalk";
6
6
  import * as ts from "typescript";
7
7
 
8
- class TypeChecker {
8
+ class TypeChecker2 {
9
9
  configPath;
10
10
  configFile;
11
11
  config;
@@ -143,12 +143,12 @@ ${summary.join(", ")} found${output.join(`
143
143
  try {
144
144
  const { diagnostics } = this.check(filePath);
145
145
  return diagnostics.length === 0;
146
- } catch (error) {
146
+ } catch (_error) {
147
147
  return false;
148
148
  }
149
149
  }
150
150
  static checkProject(configPath) {
151
- const parsedConfig = TypeChecker.parseConfig(configPath);
151
+ const parsedConfig = TypeChecker2.parseConfig(configPath);
152
152
  const host = ts.createIncrementalCompilerHost(parsedConfig.options);
153
153
  const builderProgram = ts.createIncrementalProgram({
154
154
  rootNames: parsedConfig.fileNames,
@@ -166,19 +166,19 @@ ${summary.join(", ")} found${output.join(`
166
166
  diagnostics,
167
167
  errors,
168
168
  warnings,
169
- message: TypeChecker.formatDiagnosticMessages(diagnostics)
169
+ message: TypeChecker2.formatDiagnosticMessages(diagnostics)
170
170
  };
171
171
  }
172
172
  static parseConfig(configPath) {
173
173
  const configFile = ts.readConfigFile(configPath, (fileName) => ts.sys.readFile(fileName));
174
174
  const configDiagnostics = configFile.error ? [configFile.error] : [];
175
175
  if (!configFile.config) {
176
- const message = TypeChecker.formatDiagnosticMessages(configDiagnostics);
176
+ const message = TypeChecker2.formatDiagnosticMessages(configDiagnostics);
177
177
  throw new Error(message || `Error reading tsconfig.json: ${configPath}`);
178
178
  }
179
179
  const parsedConfig = ts.parseJsonConfigFileContent(configFile.config, ts.sys, path.dirname(configPath), undefined, configPath);
180
180
  if (parsedConfig.errors.length > 0)
181
- throw new Error(TypeChecker.formatDiagnosticMessages(parsedConfig.errors));
181
+ throw new Error(TypeChecker2.formatDiagnosticMessages(parsedConfig.errors));
182
182
  return parsedConfig;
183
183
  }
184
184
  static formatDiagnosticMessages(diagnostics) {
@@ -190,4 +190,4 @@ ${summary.join(", ")} found${output.join(`
190
190
  }
191
191
  }
192
192
 
193
- export { TypeChecker };
193
+ export { TypeChecker2 };
@@ -19,12 +19,14 @@ var prepareDevWatchBatch = ({
19
19
  generatedFiles: indexSync.changedFiles
20
20
  });
21
21
  if (indexSync.errors.length > 0 && !devPlan.actions.includes("report-error")) {
22
- devPlan.actions = [...devPlan.actions, "report-error"].sort();
22
+ const withReport = [...devPlan.actions, "report-error"];
23
+ devPlan.actions = withReport.sort();
23
24
  }
24
25
  return {
25
26
  files,
26
27
  kinds,
27
28
  expandedBatch,
29
+ devPlan,
28
30
  event: { type: "invalidate", kinds, files, generation, devPlan },
29
31
  hasSyncErrors: indexSync.errors.length > 0
30
32
  };
@@ -2,16 +2,16 @@
2
2
  import {
3
3
  runner,
4
4
  script
5
- } from "./index-6rhabeev.js";
5
+ } from "./index-ft1sfjz0.js";
6
6
  import {
7
7
  PkgExecutor
8
- } from "./index-vg0sj9nc.js";
9
- import {
10
- TypeScriptDependencyScanner
11
- } from "./index-46tjzh6s.js";
8
+ } from "./index-k1n0h7dk.js";
12
9
  import {
13
10
  FileSys
14
- } from "./index-67546d0j.js";
11
+ } from "./index-ayy5ecax.js";
12
+ import {
13
+ TypeScriptDependencyScanner2
14
+ } from "./index-mfm35dhh.js";
15
15
 
16
16
  // pkgs/@akanjs/cli/package/package.runner.ts
17
17
  import path2 from "path";
@@ -140,7 +140,7 @@ class PackageRunner extends runner("package") {
140
140
  await workspace.setPkgTsPaths(pkgName);
141
141
  }
142
142
  async removePackage(pkg) {
143
- await pkg.workspace.exec(`rm -rf pkgs/${pkg.name}`);
143
+ await pkg.workspace.removeDir(`pkgs/${pkg.name}`);
144
144
  await pkg.workspace.unsetPkgTsPaths(pkg.name);
145
145
  }
146
146
  async scanSync(pkg) {
@@ -150,7 +150,7 @@ class PackageRunner extends runner("package") {
150
150
  async buildPackage(pkg) {
151
151
  await $`rm -rf ${pkg.dist.cwdPath}`;
152
152
  await pkg.dist.mkdir(pkg.dist.cwdPath);
153
- const scanner = await TypeScriptDependencyScanner.from(pkg);
153
+ const scanner = await TypeScriptDependencyScanner2.from(pkg);
154
154
  const { npmDeps, npmDevDeps, missingDeps } = await scanner.getPackageBuildDependencies(pkg.name);
155
155
  const packageRuntimeDependencies = {
156
156
  "@akanjs/devkit": ["tailwind-scrollbar"]
@@ -163,13 +163,17 @@ class PackageRunner extends runner("package") {
163
163
  "tailwind-scrollbar"
164
164
  ].filter((dep) => dep !== "akanjs" && dep !== "@akanjs/devkit");
165
165
  }
166
- const bundledRuntimeDeps = new Set(pkg.name === "@akanjs/cli" ? ["@akanjs/devkit"] : []);
166
+ const packageBundledRuntimeDependencies = {
167
+ "@akanjs/cli": ["@akanjs/devkit"],
168
+ akanjs: ["use-agentic"]
169
+ };
170
+ const bundledRuntimeDeps = new Set(packageBundledRuntimeDependencies[pkg.name] ?? []);
167
171
  const forcedRuntimeDeps = packageRuntimeDependencies[pkg.name] ?? [];
168
172
  const forcedRuntimeDevDeps = packageRuntimeDevDependencies[pkg.name] ?? [];
169
173
  const [rootPackageJson, pkgJson] = await Promise.all([pkg.workspace.getPackageJson(), pkg.getPackageJson()]);
170
174
  const optionalPeerDeps = new Set(Object.entries(pkgJson.peerDependenciesMeta ?? {}).filter(([, meta]) => meta.optional).map(([dep]) => dep));
171
175
  const packageRuntimeDeps = [...new Set([...npmDeps, ...forcedRuntimeDeps])].filter((dep) => !optionalPeerDeps.has(dep) && !bundledRuntimeDeps.has(dep));
172
- const packageRuntimeDevDeps = [...new Set([...npmDevDeps, ...forcedRuntimeDevDeps])].filter((dep) => !optionalPeerDeps.has(dep));
176
+ const packageRuntimeDevDeps = [...new Set([...npmDevDeps, ...forcedRuntimeDevDeps])].filter((dep) => !optionalPeerDeps.has(dep) && !bundledRuntimeDeps.has(dep));
173
177
  const rootDeps = { ...rootPackageJson.dependencies, ...rootPackageJson.devDependencies };
174
178
  const missingForcedDeps = forcedRuntimeDeps.filter((dep) => !rootDeps[dep]);
175
179
  const missingForcedDevDeps = forcedRuntimeDevDeps.filter((dep) => !rootDeps[dep]);
@@ -268,7 +272,7 @@ class PackageRunner extends runner("package") {
268
272
  }
269
273
  if (!pkgJson.version)
270
274
  throw new Error(`[package] dist package version is missing for ${pkg.name}`);
271
- if (!pkgJson.publishConfig || pkgJson.publishConfig.access !== "public") {
275
+ if (pkgJson.publishConfig?.access !== "public") {
272
276
  throw new Error(`[package] ${pkg.name} must publish with publishConfig.access=public`);
273
277
  }
274
278
  if (!await Bun.file(`${pkg.dist.cwdPath}/README.md`).exists()) {
@@ -9,7 +9,7 @@ class FileSys {
9
9
  return await Bun.file(path).exists();
10
10
  }
11
11
  static async dirExists(path) {
12
- return await stat(path).then((stat2) => stat2.isDirectory()).catch(() => false);
12
+ return await stat(path).then((stat) => stat.isDirectory()).catch(() => false);
13
13
  }
14
14
  static async exists(path) {
15
15
  return await stat(path).then(() => true).catch(() => false);