@akanjs/cli 3.0.0-alpha.11 → 3.0.0-alpha.111

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 (140) 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-mjh5ppsq.js} +3 -5
  5. package/{agent.command-a3n15c6t.js → agent.command-d2gfxfck.js} +11 -13
  6. package/{application.command-rdm3et0g.js → application.command-pj2m7584.js} +65 -38
  7. package/{applicationBuildRunner-6m8n71w4.js → applicationBuildRunner-kxwbssgn.js} +27 -23
  8. package/{applicationReleasePackager-gg8ccbeh.js → applicationReleasePackager-4yasxwv9.js} +8 -9
  9. package/buildBatch.proc.js +12 -14
  10. package/capacitorApp-36rbr858.js +12 -0
  11. package/{cloud.command-56aeekqn.js → cloud.command-gxqp8xdv.js} +15 -18
  12. package/commandManifest.json +1 -1
  13. package/{context.command-ysxawe7h.js → context.command-ez8a0zyz.js} +19 -23
  14. package/dependencyScanner-shs82ynp.js +8 -0
  15. package/devTui-40ggmd99.js +553 -0
  16. package/devTuiView-c08ccbat.js +9 -0
  17. package/getDirname-dr3jz1xg.js +7 -0
  18. package/{guideline.command-yqf9a372.js → guideline.command-xmqwx8zm.js} +24 -22
  19. package/guidelines/agentRule/agentRule.generate.json +31 -0
  20. package/guidelines/agentRule/agentRule.instruction.md +531 -0
  21. package/guidelines/conventions/conventions.instruction.md +493 -369
  22. package/guidelines/framework/framework.generate.json +3 -2
  23. package/guidelines/framework/framework.instruction.md +0 -9
  24. package/guidelines/mcpRule/mcpRule.generate.json +35 -0
  25. package/guidelines/mcpRule/mcpRule.instruction.md +460 -0
  26. package/guidelines/modelStore/modelStore.instruction.md +12 -0
  27. package/guidelines/modelTemplate/modelTemplate.instruction.md +5 -0
  28. package/guidelines/queryRule/queryRule.generate.json +31 -0
  29. package/guidelines/queryRule/queryRule.instruction.md +178 -0
  30. package/guidelines/runtimeRule/runtimeRule.generate.json +25 -0
  31. package/guidelines/runtimeRule/runtimeRule.instruction.md +220 -0
  32. package/guidelines/ssrRule/ssrRule.instruction.md +51 -5
  33. package/guidelines/transportRule/transportRule.generate.json +31 -0
  34. package/guidelines/transportRule/transportRule.instruction.md +60 -0
  35. package/guidelines/workspaceOnboarding/workspaceOnboarding.instruction.md +54 -764
  36. package/guidelines/workspaceRecipes/workspaceRecipes.generate.json +33 -0
  37. package/guidelines/workspaceRecipes/workspaceRecipes.instruction.md +467 -0
  38. package/incrementalBuilder.proc.js +26 -25
  39. package/{index-8pd02prk.js → index-259dsnz3.js} +58 -25
  40. package/{index-yzsxc30b.js → index-2tv3fdx4.js} +975 -234
  41. package/index-3d9y066c.js +76 -0
  42. package/{index-76rn3g2c.js → index-47pj3aee.js} +4 -4
  43. package/{index-m90fn8xe.js → index-4edzf1gr.js} +32 -32
  44. package/{index-3789fdpg.js → index-600zhnve.js} +94 -61
  45. package/{index-1577bej2.js → index-6wwzs1bc.js} +2 -2
  46. package/{index-cv9cxs5k.js → index-7737gxcp.js} +18 -18
  47. package/{index-a5cs7d8r.js → index-8few9nyh.js} +13 -13
  48. package/{index-mewacxye.js → index-8pm30m73.js} +57 -57
  49. package/{index-wnp7hwq7.js → index-92zztqy1.js} +7 -7
  50. package/{index-fgc8r6dj.js → index-a5vmm6x4.js} +3 -1
  51. package/{index-cav1azma.js → index-a64esmtb.js} +1106 -1369
  52. package/{index-nwqqssnb.js → index-aj7tyh5w.js} +26 -23
  53. package/{index-xys926f2.js → index-by5gpp8x.js} +4 -4
  54. package/{index-yzmqvg23.js → index-fz5q3gd7.js} +8 -8
  55. package/{index-kcsa1zff.js → index-gyd220p2.js} +4 -4
  56. package/{index-j37qq1f2.js → index-h7yw2z9n.js} +7 -7
  57. package/{index-bjpxzr6s.js → index-hwkygn9s.js} +51 -3
  58. package/index-j9yhg404.js +159 -0
  59. package/{index-46tjzh6s.js → index-kcxmv48c.js} +29 -29
  60. package/{index-0cj2zxbm.js → index-kn1h7p1x.js} +12 -11
  61. package/{index-e4b2ae6b.js → index-ktb5njgy.js} +41 -34
  62. package/{index-etwh7sd3.js → index-mak3cd28.js} +100 -32
  63. package/{index-xjd3rqzj.js → index-n68jx79w.js} +6 -15
  64. package/{index-z1s5f4e0.js → index-ne2qy0nk.js} +36 -37
  65. package/{index-65aeh789.js → index-pnqbkf5p.js} +1 -1
  66. package/{index-67546d0j.js → index-q0y2jq9j.js} +14 -2
  67. package/{index-4mgkgba3.js → index-rwg2aa4f.js} +14 -14
  68. package/{index-bzznxgcx.js → index-v0qh106m.js} +1002 -128
  69. package/{index-j9cxndc0.js → index-w8kmx1b6.js} +20 -11
  70. package/{index-h5whaj26.js → index-ws5e0j51.js} +16 -12
  71. package/{index-9va8zayb.js → index-wt7qrtd4.js} +27 -27
  72. package/index.js +22 -25
  73. package/{library.command-x6t29a30.js → library.command-nbsky672.js} +11 -10
  74. package/{localRegistry.command-649g1n7b.js → localRegistry.command-ts6t5e5a.js} +20 -23
  75. package/{module.command-8w8ys5t3.js → module.command-29vqzrh8.js} +13 -16
  76. package/{package.command-zgy7f3fz.js → package.command-axv0abmn.js} +8 -10
  77. package/package.json +5 -7
  78. package/{page.command-yjmnkyk2.js → page.command-vs9s3zrh.js} +7 -9
  79. package/{primitive.command-gfqaagsg.js → primitive.command-cbxmqbtc.js} +13 -16
  80. package/{quality.command-ec8r1dn3.js → quality.command-gmbs7nws.js} +184 -334
  81. package/recipeScanner-fpagtgc1.js +11 -0
  82. package/{repair.command-p9ey6mkm.js → repair.command-bqsjptsj.js} +12 -14
  83. package/{routeSourceValidator-e4jmbfcx.js → routeSourceValidator-apyqw7wk.js} +90 -19
  84. package/{scalar.command-9dhmqhtm.js → scalar.command-6k1vhe9s.js} +11 -14
  85. package/subspace.command-35a3t6vv.js +755 -0
  86. package/templates/app/env/env.client.debug.ts.template +1 -5
  87. package/templates/app/env/env.client.develop.ts.template +1 -5
  88. package/templates/app/env/env.client.local.ts.template +1 -5
  89. package/templates/app/env/env.client.main.ts.template +1 -5
  90. package/templates/app/env/env.client.testing.ts.template +1 -5
  91. package/templates/app/env/env.client.type.ts +2 -6
  92. package/templates/app/env/env.server.debug.ts.template +1 -5
  93. package/templates/app/env/env.server.develop.ts.template +1 -5
  94. package/templates/app/env/env.server.local.ts.template +1 -5
  95. package/templates/app/env/env.server.main.ts.template +1 -5
  96. package/templates/app/main.ts +3 -1
  97. package/templates/app/page/_index.tsx +3 -3
  98. package/templates/app/page/_layout.tsx +31 -32
  99. package/templates/appSample/lib/task/task.signal.ts +11 -6
  100. package/templates/appSample/page/task/[taskId]/_index.tsx +22 -27
  101. package/templates/appSample/page/task/[taskId]/edit.tsx +25 -30
  102. package/templates/appSample/page/task/_index.tsx +3 -7
  103. package/templates/appSample/page/task/_layout.tsx +4 -15
  104. package/templates/appSample/page/task/new.tsx +4 -10
  105. package/templates/appSample/srvkit/AuthGuard.ts +2 -2
  106. package/templates/crudPages/[__model__Id]/edit/page.tsx +27 -29
  107. package/templates/crudPages/[__model__Id]/page.tsx +24 -32
  108. package/templates/crudPages/new/page.tsx +26 -25
  109. package/templates/crudPages/page.tsx +27 -26
  110. package/templates/crudSinglePage/page.tsx +26 -21
  111. package/templates/env/_env.server.type.ts +7 -5
  112. package/templates/lib/db.ts +1 -1
  113. package/templates/lib/dict.ts +1 -1
  114. package/templates/lib/sig.ts +5 -1
  115. package/templates/lib/srv.ts +6 -2
  116. package/templates/module/__model__.dictionary.ts +4 -1
  117. package/templates/module/__model__.signal.ts +2 -3
  118. package/templates/module/__model__.store.ts +1 -2
  119. package/templates/workspaceRoot/.gitignore.template +0 -1
  120. package/templates/workspaceRoot/.husky/pre-commit.template +16 -0
  121. package/templates/workspaceRoot/.vscode/settings.json.template +1 -1
  122. package/templates/workspaceRoot/AGENTS.md.template +4 -0
  123. package/templates/workspaceRoot/CLAUDE.md.template +19 -0
  124. package/templates/workspaceRoot/biome.json.template +1 -1
  125. package/templates/workspaceRoot/docs/AI-DEVELOPMENT.md.template +10 -14
  126. package/templates/workspaceRoot/docs/GENERATED.md.template +12 -10
  127. package/templates/workspaceRoot/package.json.template +2 -4
  128. package/typeChecker-3xgrzc2v.js +7 -0
  129. package/typecheck.proc.js +4 -5
  130. package/{workflow.command-ycwc6zr6.js → workflow.command-nwpcdc8p.js} +22 -25
  131. package/{workspace.command-xhyhajxa.js → workspace.command-aczjxzbf.js} +64 -66
  132. package/capacitorApp-mr81qdxq.js +0 -61
  133. package/dependencyScanner-f6grrep3.js +0 -9
  134. package/getDirname-m9s0cmhq.js +0 -8
  135. package/index-mxvakhsm.js +0 -66
  136. package/index-r24hmh0q.js +0 -4
  137. package/recipeScanner-tghkzrrt.js +0 -12
  138. package/typeChecker-kravn7ns.js +0 -8
  139. /package/{index-0wae5ebk.js → index-meh558jy.js} +0 -0
  140. /package/{index-ss469dec.js → index-nz8prx9g.js} +0 -0
@@ -1,177 +1,95 @@
1
1
  // @bun
2
- import {
3
- AbstractDoc
4
- } from "./index-0wae5ebk.js";
5
2
  import {
6
3
  Workspace,
7
- command,
8
4
  runner,
9
- script
10
- } from "./index-etwh7sd3.js";
5
+ script,
6
+ command
7
+ } from "./index-mak3cd28.js";
8
+ import"./index-q0y2jq9j.js";
11
9
  import {
12
- appRootAllowedFiles,
13
- libFacetRootAllowedFiles
14
- } from "./index-yzsxc30b.js";
15
- import"./index-mxvakhsm.js";
16
- import"./index-1577bej2.js";
17
- import"./index-67546d0j.js";
18
- import"./index-r24hmh0q.js";
10
+ rootAllowedFiles,
11
+ rootAllowedDirs,
12
+ isAllowedLibFacetRootFile
13
+ } from "./index-2tv3fdx4.js";
14
+ import"./index-6wwzs1bc.js";
15
+ import {
16
+ AbstractDoc
17
+ } from "./index-meh558jy.js";
19
18
 
20
19
  // pkgs/@akanjs/devkit/qualityScanner.ts
21
20
  import { createHash } from "crypto";
22
21
  import { readdir, readFile, stat } from "fs/promises";
23
- import path3 from "path";
22
+ import path from "path";
24
23
  import { RESERVED_ROUTE_CONFIG_EXPORTS } from "akanjs/common";
25
24
  import ignore from "ignore";
26
- import ts4 from "typescript";
25
+ import ts3 from "typescript";
27
26
 
28
- // pkgs/@akanjs/devkit/mcpScanner.ts
29
- import path from "path";
27
+ // pkgs/@akanjs/devkit/formSetterScanner.ts
30
28
  import ts from "typescript";
31
29
 
32
- class McpScanner {
30
+ class FormSetterScanner {
31
+ static #fieldSetter = /^set[A-Za-z0-9_$]*On[A-Za-z0-9_$]*$/;
33
32
  scan(sourceFiles) {
34
- const dictionaries = new Map(sourceFiles.filter((sourceFile) => sourceFile.file.endsWith(".dictionary.ts")).map((sourceFile) => [path.dirname(sourceFile.file), sourceFile]));
35
- return sourceFiles.filter((sourceFile) => sourceFile.file.endsWith(".signal.ts")).flatMap((sourceFile) => this.#scanSignal(sourceFile, dictionaries.get(path.dirname(sourceFile.file))));
36
- }
37
- #scanSignal(signal, dictionary) {
38
- const exposed = McpScanner.#exposedDeclarations(signal);
39
- if (!exposed.length)
40
- return [];
41
- const refName = path.basename(signal.file, ".signal.ts").replace(/^_+/, "");
42
- const described = dictionary ? McpScanner.#describedEntries(dictionary) : new Map;
43
- return [
44
- ...exposed.filter(({ name, kind }) => !McpScanner.#isDescribed(described, refName, name, kind)).map(({ name, kind, line }) => ({
45
- rule: "akan.mcp.missing-description",
46
- scope: "mcp",
47
- severity: "warning",
48
- file: signal.file,
49
- line,
50
- message: `MCP-exposed ${kind} "${name}" has no dictionary .desc(); an agent sees its name and nothing else.`
51
- })),
52
- ...exposed.filter(({ guards }) => guards === "missing").map(({ name, kind, line }) => ({
53
- rule: "akan.mcp.unguarded-exposure",
54
- scope: "mcp",
55
- severity: "warning",
56
- file: signal.file,
57
- line,
58
- message: `MCP-exposed ${kind} "${name}" declares no guards; a slice's guards map never reaches a named slice.`
59
- }))
60
- ];
61
- }
62
- static #isDescribed(described, refName, name, kind) {
63
- if (described.get(kind)?.has(name))
64
- return true;
65
- return kind === "slice" && !!described.get("endpoint")?.has(`${refName}List${McpScanner.#capitalize(name)}`);
33
+ return sourceFiles.filter((sourceFile) => sourceFile.file.endsWith(".tsx")).flatMap((f) => this.#scanFile(f));
66
34
  }
67
- static #exposedDeclarations(signal) {
68
- const found = [];
35
+ #scanFile({ file, sourceFile }) {
36
+ const wrapped = [];
69
37
  const visit = (node) => {
70
- if (McpScanner.#isExposeOption(node)) {
71
- const declaration = McpScanner.#enclosingDeclaration(node, signal.sourceFile);
72
- if (declaration)
73
- found.push(declaration);
38
+ if (ts.isJsxAttribute(node)) {
39
+ const setter = FormSetterScanner.#wrappedSetterOf(node);
40
+ if (setter)
41
+ wrapped.push({ setter, line: FormSetterScanner.#lineOf(sourceFile, node) });
74
42
  }
75
43
  ts.forEachChild(node, visit);
76
44
  };
77
- visit(signal.sourceFile);
78
- return found;
79
- }
80
- static #isExposeOption(node) {
81
- if (!ts.isPropertyAssignment(node) || McpScanner.#propertyName(node) !== "mcp")
82
- return false;
83
- if (!ts.isObjectLiteralExpression(node.initializer))
84
- return false;
85
- return node.initializer.properties.some((property) => ts.isPropertyAssignment(property) && McpScanner.#propertyName(property) === "expose" && property.initializer.kind === ts.SyntaxKind.TrueKeyword);
86
- }
87
- static #enclosingDeclaration(option, sourceFile) {
88
- let declaration = null;
89
- for (let node = option.parent;node; node = node.parent) {
90
- if (!declaration && ts.isPropertyAssignment(node)) {
91
- declaration = node;
92
- continue;
45
+ visit(sourceFile);
46
+ if (!wrapped.length)
47
+ return [];
48
+ const setters = [...new Set(wrapped.map((entry) => entry.setter))].sort();
49
+ return [
50
+ {
51
+ rule: "akan.agent.unpublished-form-setter",
52
+ scope: "agent",
53
+ severity: "warning",
54
+ message: setters.length === 1 ? `${setters[0]} is reached through a wrapper, so that field publishes no agent tool and carries no data-akan-action.` : `${setters.length} field setters are reached through a wrapper, so those fields publish no agent tool and carry no data-akan-action: ${setters.join(", ")}.`,
55
+ file,
56
+ line: wrapped[0]?.line,
57
+ locations: wrapped.map(({ line }) => ({ file, line }))
93
58
  }
94
- const kind = McpScanner.#factoryKind(node);
95
- if (!kind || !declaration)
96
- continue;
97
- const name = McpScanner.#propertyName(declaration);
98
- if (!name)
99
- return null;
100
- const line = sourceFile.getLineAndCharacterOfPosition(declaration.getStart(sourceFile)).line + 1;
101
- return { name, kind, line, guards: McpScanner.#guardState(option) };
102
- }
103
- return null;
104
- }
105
- static #guardState(option) {
106
- const options = option.parent;
107
- if (!ts.isObjectLiteralExpression(options))
108
- return "unknown";
109
- if (options.properties.some((property) => ts.isSpreadAssignment(property)))
110
- return "unknown";
111
- return options.properties.some((property) => ts.isPropertyAssignment(property) && McpScanner.#propertyName(property) === "guards") ? "declared" : "missing";
59
+ ];
112
60
  }
113
- static #factoryKind(node) {
114
- if (!ts.isCallExpression(node) || !ts.isIdentifier(node.expression))
61
+ static #wrappedSetterOf(attribute) {
62
+ if (!ts.isIdentifier(attribute.name) || !/^on[A-Z]/.test(attribute.name.text))
115
63
  return null;
116
- const factory = node.expression.text;
117
- return factory === "slice" || factory === "endpoint" ? factory : null;
118
- }
119
- static #describedEntries(dictionary) {
120
- const described = new Map;
121
- const visit = (node) => {
122
- const stage = McpScanner.#dictionaryStage(node);
123
- if (stage) {
124
- for (const [name, chain] of McpScanner.#stageEntries(node)) {
125
- if (!chain.has("desc"))
126
- continue;
127
- const names = described.get(stage) ?? new Set;
128
- names.add(name);
129
- described.set(stage, names);
64
+ const initializer = attribute.initializer;
65
+ if (!initializer || !ts.isJsxExpression(initializer) || !initializer.expression)
66
+ return null;
67
+ const handler = initializer.expression;
68
+ if (!ts.isArrowFunction(handler) && !ts.isFunctionExpression(handler))
69
+ return null;
70
+ if (!handler.parameters.length)
71
+ return null;
72
+ return FormSetterScanner.#setterCallIn(handler.body);
73
+ }
74
+ static #setterCallIn(node) {
75
+ let found = null;
76
+ const visit = (current) => {
77
+ if (found)
78
+ return;
79
+ if (ts.isCallExpression(current) && ts.isPropertyAccessExpression(current.expression)) {
80
+ const { expression, name } = current.expression;
81
+ if (ts.isPropertyAccessExpression(expression) && ts.isIdentifier(expression.expression) && expression.expression.text === "st" && expression.name.text === "do" && FormSetterScanner.#fieldSetter.test(name.text)) {
82
+ found = name.text;
83
+ return;
130
84
  }
131
85
  }
132
- ts.forEachChild(node, visit);
86
+ ts.forEachChild(current, visit);
133
87
  };
134
- visit(dictionary.sourceFile);
135
- return described;
136
- }
137
- static #dictionaryStage(node) {
138
- if (!ts.isCallExpression(node) || !ts.isPropertyAccessExpression(node.expression))
139
- return null;
140
- const stage = node.expression.name.text;
141
- return stage === "endpoint" || stage === "slice" ? stage : null;
142
- }
143
- static #stageEntries(stage) {
144
- const callback = stage.arguments[0];
145
- if (!callback || !ts.isArrowFunction(callback))
146
- return [];
147
- const body = ts.isParenthesizedExpression(callback.body) ? callback.body.expression : callback.body;
148
- if (!ts.isObjectLiteralExpression(body))
149
- return [];
150
- return body.properties.flatMap((property) => {
151
- if (!ts.isPropertyAssignment(property))
152
- return [];
153
- const name = McpScanner.#propertyName(property);
154
- return name ? [[name, McpScanner.#chainCalls(property.initializer)]] : [];
155
- });
156
- }
157
- static #chainCalls(expression) {
158
- const calls = new Set;
159
- let current = expression;
160
- while (ts.isCallExpression(current) || ts.isPropertyAccessExpression(current)) {
161
- if (ts.isPropertyAccessExpression(current))
162
- calls.add(current.name.text);
163
- current = current.expression;
164
- }
165
- return calls;
166
- }
167
- static #propertyName(property) {
168
- const { name } = property;
169
- if (ts.isIdentifier(name) || ts.isStringLiteral(name))
170
- return name.text;
171
- return null;
88
+ visit(node);
89
+ return found;
172
90
  }
173
- static #capitalize(value) {
174
- return value.charAt(0).toUpperCase() + value.slice(1);
91
+ static #lineOf(sourceFile, node) {
92
+ return sourceFile.getLineAndCharacterOfPosition(node.getStart(sourceFile)).line + 1;
175
93
  }
176
94
  }
177
95
 
@@ -535,122 +453,6 @@ function getAccessRoot(node) {
535
453
  return ts2.isIdentifier(current) ? current.text : "";
536
454
  }
537
455
 
538
- // pkgs/@akanjs/devkit/storeScanner.ts
539
- import path2 from "path";
540
- import ts3 from "typescript";
541
-
542
- class StoreScanner {
543
- scan(sourceFiles) {
544
- const dictionaries = new Map(sourceFiles.filter((sourceFile) => sourceFile.file.endsWith(".dictionary.ts")).map((sourceFile) => [path2.dirname(sourceFile.file), sourceFile]));
545
- return sourceFiles.filter((sourceFile) => sourceFile.file.endsWith(".store.ts")).flatMap((sourceFile) => this.#scanStore(sourceFile, dictionaries.get(path2.dirname(sourceFile.file))));
546
- }
547
- #scanStore(store, dictionary) {
548
- const actions = StoreScanner.#customActions(store);
549
- if (!actions.length)
550
- return [];
551
- const described = dictionary ? StoreScanner.#describedEntries(dictionary) : new Map;
552
- return actions.filter(({ name, fetched }) => fetched.length && !fetched.includes(name)).filter(({ name }) => !described.get("store")?.has(name) && !described.get("endpoint")?.has(name)).map(({ name, line, fetched }) => ({
553
- rule: "akan.agent.missing-store-description",
554
- scope: "agent",
555
- severity: "warning",
556
- file: store.file,
557
- line,
558
- message: `Store action "${name}" calls ${fetched.map((key) => `${key}()`).join(", ")} under a different name and has no dictionary .store() entry, so an agent reading it has the name and nothing else.`
559
- }));
560
- }
561
- static #customActions(store) {
562
- const actions = [];
563
- const visit = (node) => {
564
- if (ts3.isClassDeclaration(node) && StoreScanner.#extendsStore(node)) {
565
- for (const member of node.members) {
566
- if (!ts3.isMethodDeclaration(member))
567
- continue;
568
- if (member.modifiers?.some((modifier) => modifier.kind === ts3.SyntaxKind.StaticKeyword))
569
- continue;
570
- const name = StoreScanner.#memberName(member);
571
- if (!name)
572
- continue;
573
- const line = store.sourceFile.getLineAndCharacterOfPosition(member.getStart(store.sourceFile)).line + 1;
574
- actions.push({ name, line, fetched: StoreScanner.#fetchedEndpoints(member) });
575
- }
576
- }
577
- ts3.forEachChild(node, visit);
578
- };
579
- visit(store.sourceFile);
580
- return actions;
581
- }
582
- static #extendsStore(node) {
583
- return !!node.heritageClauses?.some((clause) => clause.types.some((type) => ts3.isCallExpression(type.expression) && StoreScanner.#isStoreCall(type.expression)));
584
- }
585
- static #isStoreCall(expression) {
586
- return ts3.isIdentifier(expression.expression) && expression.expression.text === "store";
587
- }
588
- static #fetchedEndpoints(member) {
589
- const fetched = new Set;
590
- const visit = (node) => {
591
- if (ts3.isCallExpression(node) && ts3.isPropertyAccessExpression(node.expression) && ts3.isIdentifier(node.expression.expression) && node.expression.expression.text === "fetch")
592
- fetched.add(node.expression.name.text);
593
- ts3.forEachChild(node, visit);
594
- };
595
- visit(member);
596
- return [...fetched];
597
- }
598
- static #describedEntries(dictionary) {
599
- const described = new Map;
600
- const visit = (node) => {
601
- const stage = StoreScanner.#dictionaryStage(node);
602
- if (stage) {
603
- for (const [name, chain] of StoreScanner.#stageEntries(node)) {
604
- if (!chain.has("desc"))
605
- continue;
606
- const names = described.get(stage) ?? new Set;
607
- names.add(name);
608
- described.set(stage, names);
609
- }
610
- }
611
- ts3.forEachChild(node, visit);
612
- };
613
- visit(dictionary.sourceFile);
614
- return described;
615
- }
616
- static #dictionaryStage(node) {
617
- if (!ts3.isCallExpression(node) || !ts3.isPropertyAccessExpression(node.expression))
618
- return null;
619
- const stage = node.expression.name.text;
620
- return stage === "store" || stage === "endpoint" ? stage : null;
621
- }
622
- static #stageEntries(stage) {
623
- const callback = stage.arguments[0];
624
- if (!callback || !ts3.isArrowFunction(callback))
625
- return [];
626
- const body = ts3.isParenthesizedExpression(callback.body) ? callback.body.expression : callback.body;
627
- if (!ts3.isObjectLiteralExpression(body))
628
- return [];
629
- return body.properties.flatMap((property) => {
630
- if (!ts3.isPropertyAssignment(property))
631
- return [];
632
- const name = StoreScanner.#memberName(property);
633
- return name ? [[name, StoreScanner.#chainCalls(property.initializer)]] : [];
634
- });
635
- }
636
- static #chainCalls(expression) {
637
- const calls = new Set;
638
- let current = expression;
639
- while (ts3.isCallExpression(current) || ts3.isPropertyAccessExpression(current)) {
640
- if (ts3.isPropertyAccessExpression(current))
641
- calls.add(current.name.text);
642
- current = current.expression;
643
- }
644
- return calls;
645
- }
646
- static #memberName(member) {
647
- const { name } = member;
648
- if (!name || !ts3.isIdentifier(name) && !ts3.isStringLiteral(name))
649
- return null;
650
- return name.text;
651
- }
652
- }
653
-
654
456
  // pkgs/@akanjs/devkit/qualityScanner.ts
655
457
  var MAX_FILE_LINES = 2000;
656
458
  var PLACEHOLDER_EXPORT_NAMES = new Set([
@@ -672,7 +474,8 @@ var SUGGESTED_RULES = [
672
474
  "Keep apps/*/lib and libs/*/lib root files limited to generated support facets such as cnst.ts, db.ts, dict.ts, sig.ts, srv.ts, st.ts, useClient.ts, and useServer.ts.",
673
475
  "Use domain module folders consistently: lib/<model> for database modules, lib/_<service> for service modules, and lib/__scalar/<scalar> for scalar modules.",
674
476
  "Keep module UI filenames predictable: database modules use <Model>.Template/Unit/Util/View/Zone.tsx, service modules use <Service>.Util/Zone.tsx, and scalar modules use <Scalar>.Template/Unit.tsx.",
675
- "Move shared app utilities to apps/*/common instead of creating apps/*/base.",
477
+ "Hand a form control its store setter by reference so the field publishes an agent tool; put normalization in the control's transform prop and a multi-write in a _postSet<Field> hook on the store.",
478
+ "Move shared app and lib utilities to common/ instead of creating apps/*/base or libs/*/base.",
676
479
  "Avoid large mixed-purpose class files; class export files should import helpers from neighboring utility files instead of declaring them inline."
677
480
  ];
678
481
  var CONVENTION_SUFFIXES = [
@@ -690,25 +493,27 @@ var RULE_FIXES = {
690
493
  "akan.file.max-lines": "Break the file into smaller focused modules; keep one primary responsibility per file.",
691
494
  "akan.file.abstract-max-lines": "Run `akan compact <app-or-lib>` to rewrite the abstract with the AI editor, keeping only the invariants and workflows the source files cannot show.",
692
495
  "akan.file.placeholder-export": "Remove the placeholder export; generated indexes should only re-export real modules.",
496
+ "akan.file.bang-comment-in-client": "Rewrite the marker as `// FIXME:` or `// TODO:`. Keep the bang form for server, srvkit, and CLI files.",
693
497
  "akan.file.dictionary-stale-text": "Replace the scaffold text with real localized copy for this dictionary entry.",
694
498
  "akan.file.global-declaration": "Move the global declaration into an approved low-level integration file (e.g. webkit) and keep it isolated.",
695
499
  "akan.file.window-augmentation": "Move the Window augmentation into an approved browser integration file and keep it isolated.",
696
500
  "akan.file.prototype-mutation": "Avoid prototype mutation, or isolate it in an approved low-level integration file.",
697
501
  "akan.file.class-export-global-declaration": "Move the helper to a sibling file and import it into the class module.",
502
+ "akan.agent.unpublished-form-setter": "Pass the setter by reference where the wrapper only forwards (onChange={st.do.setTitleOnTask}); move normalization into the control's own transform prop; move a multi-write into a _postSet<Field> hook on the store, keeping the generated setter on the control.",
698
503
  "akan.file.component-internal-declaration": "Move the type or helper to a type/util file in ui/, webkit/, or common/ by purpose. If it is the component's props, declare it as `interface <Component>Props`.",
699
504
  "akan.file.component-export": "Move the value or type to a util/constant/type file in ui/, webkit/, or common/ and import it. Adding `export` is not a valid fix \u2014 only PascalCase components and their `<Component>Props` interface belong here.",
700
505
  "akan.layout.app-root-file": "Move the file into a conventional app folder (common, env, lib, page, private, public, script, srvkit, ui, or webkit).",
701
- "akan.layout.lib-root-file": "Move the file into a domain module folder under lib/; keep lib root limited to generated support facets.",
506
+ "akan.layout.app-root-folder": "Move the folder's contents into a conventional app folder (common, env, lib, page, private, public, script, srvkit, ui, or webkit) and delete it.",
507
+ "akan.layout.lib-root-file": "Move the file into a conventional lib folder (common, env, lib, page, private, public, srvkit, ui, or webkit).",
508
+ "akan.layout.lib-root-folder": "Move the folder's contents into a conventional lib folder (common, env, lib, page, private, public, srvkit, ui, or webkit) and delete it.",
509
+ "akan.layout.lib-facet-file": "Move the file into a domain module folder under lib/; keep the lib facet root limited to generated support facets.",
702
510
  "akan.layout.module-ui-file": "Rename the file to an allowed module UI name, or move it to ui/ if it is not a module component.",
703
511
  "akan.ssr.unnecessary-use-client": 'Delete the "use client" directive so the file renders on the server. If it exists only to wrap one client child, drop the wrapper and use the child directly.',
704
512
  "akan.ssr.client-static-component": "Move the component to a server file \u2014 a <Model>.Unit.tsx / <Model>.View.tsx for a module, or a ui/ file with no directive \u2014 and reference it from the client file.",
705
513
  "akan.ssr.client-static-markup": "Keep the interactive element in the client component and hoist the static subtree into a server component, then accept it as `children` or render it through a Unit/View reference.",
706
514
  "akan.ssr.client-mount-load": "Load the data in the route with `fetch.initX(...)` / `fetch.viewX(...)` and pass the init/view object down as a prop; the client store hydrates from it and the effect goes away.",
707
515
  "akan.ssr.module-missing-server-view": "Add a <Model>.Unit.tsx for list/card rendering and a <Model>.View.tsx for the detail surface, then have the Zone delegate to them.",
708
- "akan.ssr.template-client-state": "Bind the field to the store instead: `value={xForm.field}` with `onChange={st.do.setFieldOnX}`.",
709
- "akan.mcp.missing-description": "Add `.desc([en, ko])` to this entry in the module's dictionary \u2014 for a slice, either on the slice entry or on the `<model>List<Slice>` endpoint it generates. Describe when to reach for it, not what it is named.",
710
- "akan.agent.missing-store-description": "Add the action to the module dictionary's `.store()` stage with a `.desc([en, ko])` saying what it does for the user \u2014 not what the endpoint it calls does. That stage is optional everywhere else: an action named after its endpoint already reads as that endpoint's description.",
711
- "akan.mcp.unguarded-exposure": "Name the guards in the same option object as `mcp`: `init({ guards: [SignedIn], mcp: { expose: true } })`. Write `guards: [Public]` if anonymous reads are the intent \u2014 the access is the same, but only one of the two is a decision. The `slice()` call's guards map reaches the root slice and base CRUD, never a named slice."
516
+ "akan.ssr.template-client-state": "Bind the field to the store instead: `value={xForm.field}` with `onChange={st.do.setFieldOnX}`."
712
517
  };
713
518
  function getRuleFix(rule) {
714
519
  if (rule.startsWith("akan.convention"))
@@ -729,9 +534,8 @@ class AkanQualityScanner {
729
534
  ...sourceFiles.flatMap((sourceFile) => this.#scanConventionQuality(sourceFile)),
730
535
  ...sourceFiles.flatMap((sourceFile) => this.#scanLayoutQuality(sourceFile)),
731
536
  ...abstractFiles.flatMap((abstractFile) => this.#scanAbstractQuality(abstractFile)),
732
- ...ssr.warnings,
733
- ...new McpScanner().scan(sourceFiles),
734
- ...new StoreScanner().scan(sourceFiles)
537
+ ...new FormSetterScanner().scan(sourceFiles),
538
+ ...ssr.warnings
735
539
  ];
736
540
  return {
737
541
  workspaceRoot,
@@ -745,7 +549,7 @@ class AkanQualityScanner {
745
549
  const ignoreFilter = ignore().add(await this.#readGitIgnore(workspaceRoot));
746
550
  const files = [];
747
551
  for (const targetRoot of ["apps", "libs"]) {
748
- const absoluteTargetRoot = path3.join(workspaceRoot, targetRoot);
552
+ const absoluteTargetRoot = path.join(workspaceRoot, targetRoot);
749
553
  if (!await isDirectory(absoluteTargetRoot))
750
554
  continue;
751
555
  await this.#walkTargetFiles(workspaceRoot, absoluteTargetRoot, ignoreFilter, files);
@@ -753,7 +557,7 @@ class AkanQualityScanner {
753
557
  return files.sort();
754
558
  }
755
559
  async#readGitIgnore(workspaceRoot) {
756
- const gitIgnorePath = path3.join(workspaceRoot, ".gitignore");
560
+ const gitIgnorePath = path.join(workspaceRoot, ".gitignore");
757
561
  if (!await Bun.file(gitIgnorePath).exists())
758
562
  return [];
759
563
  return (await readFile(gitIgnorePath, "utf8")).split(/\r?\n/);
@@ -761,8 +565,8 @@ class AkanQualityScanner {
761
565
  async#walkTargetFiles(workspaceRoot, currentPath, ignoreFilter, files) {
762
566
  const entries = await readdir(currentPath, { withFileTypes: true });
763
567
  for (const entry of entries) {
764
- const absolutePath = path3.join(currentPath, entry.name);
765
- const relativePath = toPosix(path3.relative(workspaceRoot, absolutePath));
568
+ const absolutePath = path.join(currentPath, entry.name);
569
+ const relativePath = toPosix(path.relative(workspaceRoot, absolutePath));
766
570
  if (shouldSkipPath(relativePath, entry.isDirectory(), ignoreFilter))
767
571
  continue;
768
572
  if (entry.isDirectory()) {
@@ -777,17 +581,17 @@ class AkanQualityScanner {
777
581
  }
778
582
  }
779
583
  async#readSourceFile(workspaceRoot, file) {
780
- const absolutePath = path3.join(workspaceRoot, file);
584
+ const absolutePath = path.join(workspaceRoot, file);
781
585
  const content = await readFile(absolutePath, "utf8");
782
586
  return {
783
587
  file,
784
588
  absolutePath,
785
589
  content,
786
- sourceFile: ts4.createSourceFile(file, content, ts4.ScriptTarget.Latest, true, getScriptKind(file))
590
+ sourceFile: ts3.createSourceFile(file, content, ts3.ScriptTarget.Latest, true, getScriptKind(file))
787
591
  };
788
592
  }
789
593
  async#readTextFile(workspaceRoot, file) {
790
- return { file, content: await readFile(path3.join(workspaceRoot, file), "utf8") };
594
+ return { file, content: await readFile(path.join(workspaceRoot, file), "utf8") };
791
595
  }
792
596
  #scanGlobalQuality(sourceFiles) {
793
597
  const exportedFunctionLikes = sourceFiles.flatMap((sourceFile) => getExportedFunctionLikes(sourceFile));
@@ -841,6 +645,7 @@ class AkanQualityScanner {
841
645
  message: `File has ${lineCount} lines. Keep single files under ${MAX_FILE_LINES} lines.`
842
646
  });
843
647
  }
648
+ warnings.push(...getBangCommentWarnings(sourceFile));
844
649
  warnings.push(...getPlaceholderExportWarnings(sourceFile));
845
650
  warnings.push(...getDictionaryTextWarnings(sourceFile));
846
651
  warnings.push(...getGlobalMutationWarnings(sourceFile));
@@ -911,7 +716,7 @@ class AkanQualityScanner {
911
716
  const suffix = CONVENTION_SUFFIXES.find((candidate) => sourceFile.file.endsWith(candidate));
912
717
  if (!suffix)
913
718
  return [];
914
- const modelName = toPascalCase(path3.basename(sourceFile.file, suffix));
719
+ const modelName = toPascalCase(path.basename(sourceFile.file, suffix));
915
720
  const warnings = [];
916
721
  for (const declaration of getTopLevelDeclarations(sourceFile)) {
917
722
  if (isAllowedConventionDeclaration(suffix, modelName, declaration))
@@ -922,7 +727,7 @@ class AkanQualityScanner {
922
727
  severity: "warning",
923
728
  file: sourceFile.file,
924
729
  line: declaration.line,
925
- message: `${path3.basename(sourceFile.file)} should not declare top-level ${declaration.kind} "${declaration.name}". Allowed declarations: ${getConventionDescription(suffix, modelName)}.`
730
+ message: `${path.basename(sourceFile.file)} should not declare top-level ${declaration.kind} "${declaration.name}". Allowed declarations: ${getConventionDescription(suffix, modelName)}.`
926
731
  });
927
732
  }
928
733
  return warnings;
@@ -942,25 +747,30 @@ class AkanQualityScanner {
942
747
  ];
943
748
  }
944
749
  #scanLayoutQuality(sourceFile) {
945
- const segments = sourceFile.file.split("/");
946
750
  const warnings = [];
947
- if (segments[0] === "apps" && segments.length === 3 && !appRootAllowedFiles.has(segments[2])) {
948
- warnings.push({
949
- rule: "akan.layout.app-root-file",
950
- scope: "layout",
951
- severity: "warning",
952
- file: sourceFile.file,
953
- message: `Unexpected app root file "${segments[2]}". Keep application code in conventional app folders.`
954
- });
751
+ const rootEntry = getSysRootEntry(sourceFile.file);
752
+ if (rootEntry) {
753
+ const { type, name, isDir } = rootEntry;
754
+ const allowed = isDir ? rootAllowedDirs[type].has(name) : rootAllowedFiles[type].has(name);
755
+ const kind = isDir ? "folder" : "file";
756
+ if (!allowed) {
757
+ warnings.push({
758
+ rule: `akan.layout.${type}-root-${kind}`,
759
+ scope: "layout",
760
+ severity: "warning",
761
+ file: sourceFile.file,
762
+ message: `Unexpected ${type} root ${kind} "${name}". Keep ${type} code in conventional ${type} folders.`
763
+ });
764
+ }
955
765
  }
956
- const libRootFile = getLibRootFile(sourceFile.file);
957
- if (libRootFile && !libFacetRootAllowedFiles.has(libRootFile)) {
766
+ const libFacetFile = getLibFacetRootFile(sourceFile.file);
767
+ if (libFacetFile && !isAllowedLibFacetRootFile(libFacetFile)) {
958
768
  warnings.push({
959
- rule: "akan.layout.lib-root-file",
769
+ rule: "akan.layout.lib-facet-file",
960
770
  scope: "layout",
961
771
  severity: "warning",
962
772
  file: sourceFile.file,
963
- message: `Unexpected lib root file "${libRootFile}". Keep direct lib root files limited to generated support facets.`
773
+ message: `Unexpected lib facet root file "${libFacetFile}". Keep direct lib/ root files limited to generated support facets.`
964
774
  });
965
775
  }
966
776
  const moduleUiWarning = getModuleUiWarning(sourceFile.file);
@@ -1030,7 +840,7 @@ function getExportedFunctionLikes(sourceFile) {
1030
840
  const declarations = [];
1031
841
  const nameExempt = isPageRouteFile(sourceFile.file) || isUiComponentFile(sourceFile.file);
1032
842
  for (const statement of sourceFile.sourceFile.statements) {
1033
- if (ts4.isFunctionDeclaration(statement) && statement.name && isExported(statement)) {
843
+ if (ts3.isFunctionDeclaration(statement) && statement.name && isExported(statement)) {
1034
844
  declarations.push({
1035
845
  name: statement.name.text,
1036
846
  kind: "function",
@@ -1040,7 +850,7 @@ function getExportedFunctionLikes(sourceFile) {
1040
850
  duplicateNameExempt: nameExempt || isConventionDuplicateNameExempt(sourceFile.file, false)
1041
851
  });
1042
852
  }
1043
- if (ts4.isClassDeclaration(statement) && statement.name && isExported(statement)) {
853
+ if (ts3.isClassDeclaration(statement) && statement.name && isExported(statement)) {
1044
854
  declarations.push({
1045
855
  name: statement.name.text,
1046
856
  kind: "class",
@@ -1050,9 +860,9 @@ function getExportedFunctionLikes(sourceFile) {
1050
860
  duplicateNameExempt: nameExempt || isConventionDuplicateNameExempt(sourceFile.file, isEnumClassStatement(sourceFile.sourceFile, statement))
1051
861
  });
1052
862
  }
1053
- if (ts4.isVariableStatement(statement) && isExported(statement)) {
863
+ if (ts3.isVariableStatement(statement) && isExported(statement)) {
1054
864
  for (const declaration of statement.declarationList.declarations) {
1055
- if (!ts4.isIdentifier(declaration.name) || !isFunctionLikeInitializer(declaration.initializer))
865
+ if (!ts3.isIdentifier(declaration.name) || !isFunctionLikeInitializer(declaration.initializer))
1056
866
  continue;
1057
867
  declarations.push({
1058
868
  name: declaration.name.text,
@@ -1091,14 +901,14 @@ function isInLibModule(file) {
1091
901
  return (segments[0] === "apps" || segments[0] === "libs") && segments.includes("lib");
1092
902
  }
1093
903
  function isEnumClassStatement(sourceFile, statement) {
1094
- if (!ts4.isClassDeclaration(statement))
904
+ if (!ts3.isClassDeclaration(statement))
1095
905
  return false;
1096
- const heritageClause = statement.heritageClauses?.find((clause) => clause.token === ts4.SyntaxKind.ExtendsKeyword);
906
+ const heritageClause = statement.heritageClauses?.find((clause) => clause.token === ts3.SyntaxKind.ExtendsKeyword);
1097
907
  const expression = heritageClause?.types[0]?.expression;
1098
908
  return !!expression && expression.getText(sourceFile).startsWith("enumOf(");
1099
909
  }
1100
910
  function getExportedClassNames(sourceFile) {
1101
- return sourceFile.statements.filter((statement) => ts4.isClassDeclaration(statement) && !!statement.name).filter((statement) => isExported(statement)).map((statement) => statement.name.text);
911
+ return sourceFile.statements.filter((statement) => ts3.isClassDeclaration(statement)).filter((statement) => isExported(statement)).map((statement) => statement.name?.text).filter((name) => !!name);
1102
912
  }
1103
913
  function isComponentDeclarationFile(file) {
1104
914
  if (!file.endsWith(".tsx"))
@@ -1114,7 +924,7 @@ function isComponentDeclarationFile(file) {
1114
924
  function getComponentFileDeclarations(sourceFile) {
1115
925
  const reExportedNames = new Set;
1116
926
  for (const statement of sourceFile.statements) {
1117
- if (ts4.isExportDeclaration(statement) && statement.exportClause && ts4.isNamedExports(statement.exportClause)) {
927
+ if (ts3.isExportDeclaration(statement) && statement.exportClause && ts3.isNamedExports(statement.exportClause)) {
1118
928
  for (const element of statement.exportClause.elements)
1119
929
  reExportedNames.add((element.propertyName ?? element.name).text);
1120
930
  }
@@ -1124,19 +934,19 @@ function getComponentFileDeclarations(sourceFile) {
1124
934
  const isDefaultExport = isDefaultExportStatement(statement);
1125
935
  const inlineExported = isExported(statement);
1126
936
  const add = (name, kind, line) => declarations.push({ name, kind, line, exported: inlineExported || reExportedNames.has(name), isDefaultExport });
1127
- if (ts4.isInterfaceDeclaration(statement))
937
+ if (ts3.isInterfaceDeclaration(statement))
1128
938
  add(statement.name.text, "interface", getLine(sourceFile, statement));
1129
- else if (ts4.isTypeAliasDeclaration(statement))
939
+ else if (ts3.isTypeAliasDeclaration(statement))
1130
940
  add(statement.name.text, "type", getLine(sourceFile, statement));
1131
- else if (ts4.isEnumDeclaration(statement))
941
+ else if (ts3.isEnumDeclaration(statement))
1132
942
  add(statement.name.text, "enum", getLine(sourceFile, statement));
1133
- else if (ts4.isFunctionDeclaration(statement) && statement.name)
943
+ else if (ts3.isFunctionDeclaration(statement) && statement.name)
1134
944
  add(statement.name.text, "function", getLine(sourceFile, statement));
1135
- else if (ts4.isClassDeclaration(statement) && statement.name)
945
+ else if (ts3.isClassDeclaration(statement) && statement.name)
1136
946
  add(statement.name.text, "class", getLine(sourceFile, statement));
1137
- else if (ts4.isVariableStatement(statement)) {
947
+ else if (ts3.isVariableStatement(statement)) {
1138
948
  for (const declaration of statement.declarationList.declarations) {
1139
- if (!ts4.isIdentifier(declaration.name))
949
+ if (!ts3.isIdentifier(declaration.name))
1140
950
  continue;
1141
951
  const kind = isFunctionLikeInitializer(declaration.initializer) ? "function" : "variable";
1142
952
  add(declaration.name.text, kind, getLine(sourceFile, declaration));
@@ -1148,15 +958,15 @@ function getComponentFileDeclarations(sourceFile) {
1148
958
  function getCompoundComponentNames(sourceFile) {
1149
959
  const names = new Set;
1150
960
  for (const statement of sourceFile.statements) {
1151
- if (!ts4.isExpressionStatement(statement))
961
+ if (!ts3.isExpressionStatement(statement))
1152
962
  continue;
1153
963
  const { expression } = statement;
1154
- if (!ts4.isBinaryExpression(expression) || expression.operatorToken.kind !== ts4.SyntaxKind.EqualsToken)
964
+ if (!ts3.isBinaryExpression(expression) || expression.operatorToken.kind !== ts3.SyntaxKind.EqualsToken)
1155
965
  continue;
1156
- if (!ts4.isPropertyAccessExpression(expression.left) || !isPascalCaseName(expression.left.name.text))
966
+ if (!ts3.isPropertyAccessExpression(expression.left) || !isPascalCaseName(expression.left.name.text))
1157
967
  continue;
1158
968
  names.add(expression.left.name.text);
1159
- if (ts4.isIdentifier(expression.right) && isPascalCaseName(expression.right.text))
969
+ if (ts3.isIdentifier(expression.right) && isPascalCaseName(expression.right.text))
1160
970
  names.add(expression.right.text);
1161
971
  }
1162
972
  return names;
@@ -1176,9 +986,37 @@ function isPascalCaseName(name) {
1176
986
  return /^[A-Z]/.test(name) && !/^[A-Z0-9_]+$/.test(name);
1177
987
  }
1178
988
  function isDefaultExportStatement(statement) {
1179
- if (ts4.isExportAssignment(statement))
989
+ if (ts3.isExportAssignment(statement))
1180
990
  return !statement.isExportEquals;
1181
- return !!(ts4.getCombinedModifierFlags(statement) & ts4.ModifierFlags.Default);
991
+ if (!ts3.canHaveModifiers(statement))
992
+ return false;
993
+ return !!ts3.getModifiers(statement)?.some((modifier) => modifier.kind === ts3.SyntaxKind.DefaultKeyword);
994
+ }
995
+ function getBangCommentWarnings(sourceFile) {
996
+ if (!isClientReachableFile(sourceFile.file))
997
+ return [];
998
+ return findPatternLines(sourceFile.content, /(?:^|[^\s/][ \t]+)\/[*/]!/).map((line) => ({
999
+ rule: "akan.file.bang-comment-in-client",
1000
+ scope: "file",
1001
+ severity: "warning",
1002
+ file: sourceFile.file,
1003
+ line,
1004
+ message: "A `//!` or `/*!` marker survives minification and ships to the browser."
1005
+ }));
1006
+ }
1007
+ function isClientReachableFile(file) {
1008
+ if (/\.(test|spec)\.tsx?$/.test(file))
1009
+ return false;
1010
+ if (/\.(constant|store)\.ts$/.test(file))
1011
+ return true;
1012
+ if (/\.(Template|Unit|Util|View|Zone)\.tsx$/.test(file))
1013
+ return true;
1014
+ const segments = file.split("/");
1015
+ if (segments.includes("ui") || segments.includes("webkit"))
1016
+ return /\.tsx?$/.test(file);
1017
+ if (segments.includes("common"))
1018
+ return file.endsWith(".ts");
1019
+ return segments.includes("page") && file.endsWith(".tsx");
1182
1020
  }
1183
1021
  function getPlaceholderExportWarnings(sourceFile) {
1184
1022
  if (!sourceFile.file.endsWith("/index.ts") && !sourceFile.file.endsWith("/index.tsx"))
@@ -1208,7 +1046,7 @@ function getDictionaryTextWarnings(sourceFile) {
1208
1046
  function getGlobalMutationWarnings(sourceFile) {
1209
1047
  const warnings = [];
1210
1048
  for (const statement of sourceFile.sourceFile.statements) {
1211
- if (ts4.isModuleDeclaration(statement) && statement.name.getText(sourceFile.sourceFile) === "global") {
1049
+ if (ts3.isModuleDeclaration(statement) && statement.name.getText(sourceFile.sourceFile) === "global") {
1212
1050
  warnings.push({
1213
1051
  rule: "akan.file.global-declaration",
1214
1052
  scope: "file",
@@ -1218,7 +1056,7 @@ function getGlobalMutationWarnings(sourceFile) {
1218
1056
  message: "Global declarations require an explicit low-level integration allowlist."
1219
1057
  });
1220
1058
  }
1221
- if (ts4.isInterfaceDeclaration(statement) && statement.name.text === "Window") {
1059
+ if (ts3.isInterfaceDeclaration(statement) && statement.name.text === "Window") {
1222
1060
  warnings.push({
1223
1061
  rule: "akan.file.window-augmentation",
1224
1062
  scope: "file",
@@ -1228,7 +1066,7 @@ function getGlobalMutationWarnings(sourceFile) {
1228
1066
  message: "Window augmentation should be isolated to approved browser integration files."
1229
1067
  });
1230
1068
  }
1231
- if (ts4.isExpressionStatement(statement) && statement.expression.getText(sourceFile.sourceFile).includes(".prototype.")) {
1069
+ if (ts3.isExpressionStatement(statement) && statement.expression.getText(sourceFile.sourceFile).includes(".prototype.")) {
1232
1070
  warnings.push({
1233
1071
  rule: "akan.file.prototype-mutation",
1234
1072
  scope: "file",
@@ -1246,23 +1084,23 @@ function getTopLevelDeclarations(sourceFile) {
1246
1084
  }
1247
1085
  function getTopLevelDeclaration(sourceFile, statement) {
1248
1086
  const line = getLine(sourceFile, statement);
1249
- if (ts4.isClassDeclaration(statement) && statement.name) {
1087
+ if (ts3.isClassDeclaration(statement) && statement.name) {
1250
1088
  return [{ name: statement.name.text, kind: "class", line, exported: isExported(statement), node: statement }];
1251
1089
  }
1252
- if (ts4.isFunctionDeclaration(statement) && statement.name) {
1090
+ if (ts3.isFunctionDeclaration(statement) && statement.name) {
1253
1091
  return [{ name: statement.name.text, kind: "function", line, exported: isExported(statement), node: statement }];
1254
1092
  }
1255
- if (ts4.isInterfaceDeclaration(statement)) {
1093
+ if (ts3.isInterfaceDeclaration(statement)) {
1256
1094
  return [{ name: statement.name.text, kind: "interface", line, exported: isExported(statement), node: statement }];
1257
1095
  }
1258
- if (ts4.isTypeAliasDeclaration(statement)) {
1096
+ if (ts3.isTypeAliasDeclaration(statement)) {
1259
1097
  return [{ name: statement.name.text, kind: "type", line, exported: isExported(statement), node: statement }];
1260
1098
  }
1261
- if (ts4.isEnumDeclaration(statement)) {
1099
+ if (ts3.isEnumDeclaration(statement)) {
1262
1100
  return [{ name: statement.name.text, kind: "enum", line, exported: isExported(statement), node: statement }];
1263
1101
  }
1264
- if (ts4.isVariableStatement(statement)) {
1265
- return statement.declarationList.declarations.filter((declaration) => ts4.isIdentifier(declaration.name)).map((declaration) => ({
1102
+ if (ts3.isVariableStatement(statement)) {
1103
+ return statement.declarationList.declarations.filter((declaration) => ts3.isIdentifier(declaration.name)).map((declaration) => ({
1266
1104
  name: declaration.name.text,
1267
1105
  kind: "variable",
1268
1106
  line: getLine(sourceFile, declaration),
@@ -1270,7 +1108,7 @@ function getTopLevelDeclaration(sourceFile, statement) {
1270
1108
  node: statement
1271
1109
  }));
1272
1110
  }
1273
- if (ts4.isExportDeclaration(statement)) {
1111
+ if (ts3.isExportDeclaration(statement)) {
1274
1112
  return [{ name: "export declaration", kind: "export", line, exported: true, node: statement }];
1275
1113
  }
1276
1114
  return [];
@@ -1295,9 +1133,9 @@ function isAllowedConstantDeclaration(modelName, declaration) {
1295
1133
  return false;
1296
1134
  if ([`${modelName}Input`, `${modelName}Object`, modelName, `Light${modelName}`, `${modelName}Insight`].includes(declaration.name))
1297
1135
  return true;
1298
- if (!ts4.isClassDeclaration(declaration.node))
1136
+ if (!ts3.isClassDeclaration(declaration.node))
1299
1137
  return false;
1300
- const heritageClause = declaration.node.heritageClauses?.find((clause) => clause.token === ts4.SyntaxKind.ExtendsKeyword);
1138
+ const heritageClause = declaration.node.heritageClauses?.find((clause) => clause.token === ts3.SyntaxKind.ExtendsKeyword);
1301
1139
  const expression = heritageClause?.types[0]?.expression;
1302
1140
  return !!expression && expression.getText().startsWith("enumOf(");
1303
1141
  }
@@ -1314,7 +1152,19 @@ function getConventionDescription(suffix, modelName) {
1314
1152
  return `${modelName}Internal, ${modelName}Slice, or ${modelName}Endpoint`;
1315
1153
  return `${modelName}Store`;
1316
1154
  }
1317
- function getLibRootFile(file) {
1155
+ var sysRootTypes = { apps: "app", libs: "lib" };
1156
+ function getSysRootEntry(file) {
1157
+ const segments = file.split("/");
1158
+ const root = segments[0];
1159
+ const name = segments[2];
1160
+ if (!root || !name || segments.length < 3)
1161
+ return null;
1162
+ const type = sysRootTypes[root];
1163
+ if (!type)
1164
+ return null;
1165
+ return { type, name, isDir: segments.length > 3 };
1166
+ }
1167
+ function getLibFacetRootFile(file) {
1318
1168
  const segments = file.split("/");
1319
1169
  if ((segments[0] === "libs" || segments[0] === "apps") && segments.length === 4 && segments[2] === "lib")
1320
1170
  return segments[3];
@@ -1363,13 +1213,13 @@ function getModuleInfo(file) {
1363
1213
  return { moduleName, fileName, kind: "database" };
1364
1214
  }
1365
1215
  function isExportedConst(declaration) {
1366
- return declaration.exported && ts4.isVariableStatement(declaration.node) && (declaration.node.declarationList.flags & ts4.NodeFlags.Const) !== 0;
1216
+ return declaration.exported && ts3.isVariableStatement(declaration.node) && (declaration.node.declarationList.flags & ts3.NodeFlags.Const) !== 0;
1367
1217
  }
1368
1218
  function isExported(node) {
1369
- return !!ts4.getCombinedModifierFlags(node) && !!(ts4.getCombinedModifierFlags(node) & ts4.ModifierFlags.Export);
1219
+ return !!ts3.getCombinedModifierFlags(node) && !!(ts3.getCombinedModifierFlags(node) & ts3.ModifierFlags.Export);
1370
1220
  }
1371
1221
  function isFunctionLikeInitializer(node) {
1372
- return !!node && (ts4.isArrowFunction(node) || ts4.isFunctionExpression(node));
1222
+ return !!node && (ts3.isArrowFunction(node) || ts3.isFunctionExpression(node));
1373
1223
  }
1374
1224
  function getBodyFingerprint(sourceFile, node) {
1375
1225
  if (!node)
@@ -1391,7 +1241,7 @@ async function isDirectory(absolutePath) {
1391
1241
  }
1392
1242
  }
1393
1243
  function getScriptKind(file) {
1394
- return file.endsWith(".tsx") ? ts4.ScriptKind.TSX : ts4.ScriptKind.TS;
1244
+ return file.endsWith(".tsx") ? ts3.ScriptKind.TSX : ts3.ScriptKind.TS;
1395
1245
  }
1396
1246
  function getLine(sourceFile, node) {
1397
1247
  return sourceFile.getLineAndCharacterOfPosition(node.getStart(sourceFile)).line + 1;
@@ -1412,7 +1262,7 @@ function toPascalCase(value) {
1412
1262
  return value.replace(/(^|[-_./])([a-zA-Z0-9])/g, (_, __, char) => char.toUpperCase()).replace(/[-_./]/g, "");
1413
1263
  }
1414
1264
  function toPosix(value) {
1415
- return value.split(path3.sep).join("/");
1265
+ return value.split(path.sep).join("/");
1416
1266
  }
1417
1267
  function groupBy(items, getKey) {
1418
1268
  const grouped = new Map;