@akanjs/cli 3.0.0-alpha.5 → 3.0.0-alpha.50

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 (110) 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-20e0cwbs.js} +2 -2
  5. package/{agent.command-b0fcx3r2.js → agent.command-nqs3bw0g.js} +8 -7
  6. package/{application.command-cgmy00gd.js → application.command-k2v46pxf.js} +7 -6
  7. package/{applicationBuildRunner-n1svrwgt.js → applicationBuildRunner-t5v6tcrm.js} +6 -7
  8. package/{applicationReleasePackager-gg8ccbeh.js → applicationReleasePackager-93nzdfsy.js} +1 -1
  9. package/buildBatch.proc.js +6 -5
  10. package/{capacitorApp-yyw35vg4.js → capacitorApp-927ahhz2.js} +29 -26
  11. package/{cloud.command-1m7hfdpk.js → cloud.command-7jwmxtez.js} +11 -10
  12. package/{context.command-b5heat2a.js → context.command-6kn3tady.js} +16 -15
  13. package/{dependencyScanner-f6grrep3.js → dependencyScanner-rbs2407b.js} +1 -1
  14. package/getDirname-ka11hm7e.js +8 -0
  15. package/{guideline.command-9ew6aqhm.js → guideline.command-pjdrndqk.js} +7 -6
  16. package/guidelines/agentRule/agentRule.generate.json +31 -0
  17. package/guidelines/agentRule/agentRule.instruction.md +413 -0
  18. package/guidelines/conventions/conventions.generate.json +25 -0
  19. package/guidelines/conventions/conventions.instruction.md +752 -0
  20. package/guidelines/cssRule/cssRule.instruction.md +7 -0
  21. package/guidelines/framework/framework.instruction.md +1 -0
  22. package/guidelines/mcpRule/mcpRule.generate.json +25 -0
  23. package/guidelines/mcpRule/mcpRule.instruction.md +164 -0
  24. package/guidelines/workspaceOnboarding/workspaceOnboarding.generate.json +24 -0
  25. package/guidelines/workspaceOnboarding/workspaceOnboarding.instruction.md +638 -0
  26. package/incrementalBuilder.proc.js +7 -6
  27. package/index-1577bej2.js +7 -0
  28. package/{index-jn9vfpwz.js → index-24h5bqpj.js} +54 -15
  29. package/{index-hrwzz2wf.js → index-4z05haak.js} +1 -1
  30. package/{index-m4kpmmtj.js → index-5em412h5.js} +1 -1
  31. package/{index-6npsfpxr.js → index-5fe016p2.js} +3 -3
  32. package/{index-bjpxzr6s.js → index-5vy2jzar.js} +1 -1
  33. package/{index-exhpp31y.js → index-6bs24rb4.js} +15 -7
  34. package/{index-nx1vbtsc.js → index-7etegaht.js} +14 -14
  35. package/{index-wrrb8rxk.js → index-7w5vv8jk.js} +4 -4
  36. package/{index-meh7mmz9.js → index-7zd96bng.js} +8 -4
  37. package/{index-p69fcwe1.js → index-866yqz3y.js} +13 -13
  38. package/{index-jkynkyfk.js → index-8rr621r2.js} +5 -3
  39. package/{index-47qxw1tz.js → index-90zyjrby.js} +170 -65
  40. package/{index-pfrt7qwh.js → index-97cw8sg1.js} +4 -4
  41. package/{index-p6txr1ss.js → index-bwt525ne.js} +9 -11
  42. package/{index-qp089jp0.js → index-j37qq1f2.js} +1 -1
  43. package/{index-c94nq3c1.js → index-k7sm38v6.js} +3 -3
  44. package/{index-j9cxndc0.js → index-qnv0aqmm.js} +9 -6
  45. package/{index-0cj2zxbm.js → index-sh01r3pm.js} +1 -1
  46. package/{index-0fj4yg7k.js → index-wr999y03.js} +105 -227
  47. package/{index-nfxq39kj.js → index-xe3ychw5.js} +37 -6
  48. package/{index-60a3jfcz.js → index-xrdrttjb.js} +8 -8
  49. package/{index-mn96y1nk.js → index-y59x51f1.js} +6 -6
  50. package/{index-mterpj70.js → index-zgkey4d0.js} +86 -17
  51. package/index.js +20 -19
  52. package/{library.command-aa1g8nsa.js → library.command-7h3yezwc.js} +5 -4
  53. package/{localRegistry.command-kshva1ck.js → localRegistry.command-vnkc41gj.js} +10 -9
  54. package/{module.command-b1zzgqde.js → module.command-4g11k3xd.js} +9 -8
  55. package/{package.command-9qv6vr9s.js → package.command-z7dd6vy2.js} +5 -4
  56. package/package.json +3 -4
  57. package/{page.command-k16pw244.js → page.command-y996708m.js} +5 -4
  58. package/{primitive.command-s60dn1ae.js → primitive.command-4fcn8k83.js} +10 -9
  59. package/{quality.command-p3vaze52.js → quality.command-hx5af2yn.js} +193 -112
  60. package/{recipeScanner-tghkzrrt.js → recipeScanner-zrzev25x.js} +3 -3
  61. package/{repair.command-mfv749ha.js → repair.command-ysxd8pef.js} +7 -6
  62. package/{routeSourceValidator-cncd92fv.js → routeSourceValidator-7fycdqyv.js} +3 -39
  63. package/{scalar.command-hs3nwzv1.js → scalar.command-3chgxybs.js} +8 -7
  64. package/templates/app/env/env.server.debug.ts.template +1 -5
  65. package/templates/app/env/env.server.develop.ts.template +1 -5
  66. package/templates/app/env/env.server.local.ts.template +1 -5
  67. package/templates/app/env/env.server.main.ts.template +1 -5
  68. package/templates/app/page/styles.css.template +3 -3
  69. package/templates/appSample/srvkit/AuthGuard.ts +9 -5
  70. package/templates/appSample/srvkit/SessionInternalArg.ts +4 -3
  71. package/templates/crudPages/[__model__Id]/edit/page.tsx +1 -1
  72. package/templates/crudPages/new/page.tsx +1 -1
  73. package/templates/crudSinglePage/page.tsx +1 -1
  74. package/templates/env/_env.server.type.ts +7 -5
  75. package/templates/lib/db.ts +1 -1
  76. package/templates/lib/dict.ts +1 -1
  77. package/templates/lib/sig.ts +1 -1
  78. package/templates/lib/srv.ts +6 -2
  79. package/templates/module/__Model__.Util.tsx +1 -1
  80. package/templates/module/__Model__.Zone.tsx +3 -5
  81. package/templates/workspaceRoot/AGENTS.md.template +0 -967
  82. package/templates/workspaceRoot/CLAUDE.md.template +19 -0
  83. package/templates/workspaceRoot/biome.json.template +4 -220
  84. package/templates/workspaceRoot/docs/AI-DEVELOPMENT.md.template +15 -16
  85. package/templates/workspaceRoot/docs/GENERATED.md.template +12 -10
  86. package/{typeChecker-kravn7ns.js → typeChecker-g6bh0xfv.js} +1 -1
  87. package/typecheck.proc.js +1 -1
  88. package/{workflow.command-z0e0fk0z.js → workflow.command-endxfzv5.js} +15 -14
  89. package/{workspace.command-3q1d6kk4.js → workspace.command-5f2nmmd5.js} +70 -52
  90. package/templates/workspaceRoot/.cursor/rules/akan-page-routing.mdc.template +0 -43
  91. package/templates/workspaceRoot/.cursor/rules/akan-sync-conventions.mdc.template +0 -34
  92. package/templates/workspaceRoot/.cursor/rules/application-test-commands.mdc.template +0 -16
  93. package/templates/workspaceRoot/.cursor/rules/change-scope.mdc.template +0 -12
  94. package/templates/workspaceRoot/.cursor/rules/client-server-boundaries.mdc.template +0 -18
  95. package/templates/workspaceRoot/.cursor/rules/coding-comments.mdc.template +0 -25
  96. package/templates/workspaceRoot/.cursor/rules/coding-style.mdc.template +0 -44
  97. package/templates/workspaceRoot/.cursor/rules/domain-module-conventions.mdc.template +0 -17
  98. package/templates/workspaceRoot/.cursor/rules/format-lint.mdc.template +0 -12
  99. package/templates/workspaceRoot/.cursor/rules/lint-enforced-rules.mdc.template +0 -18
  100. package/templates/workspaceRoot/.cursor/rules/module-file-playbook.mdc.template +0 -61
  101. package/templates/workspaceRoot/.cursor/rules/naming-conventions.mdc.template +0 -15
  102. package/templates/workspaceRoot/.cursor/rules/react-component-style.mdc.template +0 -21
  103. package/templates/workspaceRoot/.cursor/rules/repo-overview.mdc.template +0 -17
  104. package/templates/workspaceRoot/.cursor/rules/scalar-modeling.mdc.template +0 -58
  105. package/templates/workspaceRoot/.cursor/rules/secrets-and-env.mdc.template +0 -13
  106. package/templates/workspaceRoot/.cursor/rules/service-signal-conventions.mdc.template +0 -83
  107. package/templates/workspaceRoot/.cursor/rules/srvkit-adapters-and-guards.mdc.template +0 -50
  108. package/templates/workspaceRoot/.cursor/rules/ssr-first.mdc.template +0 -59
  109. package/templates/workspaceRoot/.cursor/rules/typescript-imports.mdc.template +0 -16
  110. /package/{index-r24hmh0q.js → index-yb2wsc7f.js} +0 -0
@@ -7,24 +7,96 @@ import {
7
7
  command,
8
8
  runner,
9
9
  script
10
- } from "./index-jkynkyfk.js";
10
+ } from "./index-8rr621r2.js";
11
11
  import {
12
- appRootAllowedFiles,
13
- libFacetRootAllowedFiles
14
- } from "./index-47qxw1tz.js";
12
+ isAllowedLibFacetRootFile,
13
+ rootAllowedDirs,
14
+ rootAllowedFiles
15
+ } from "./index-90zyjrby.js";
15
16
  import"./index-mxvakhsm.js";
17
+ import"./index-1577bej2.js";
16
18
  import"./index-67546d0j.js";
17
- import"./index-r24hmh0q.js";
19
+ import"./index-yb2wsc7f.js";
18
20
 
19
21
  // pkgs/@akanjs/devkit/qualityScanner.ts
20
22
  import { createHash } from "crypto";
21
23
  import { readdir, readFile, stat } from "fs/promises";
22
24
  import path from "path";
25
+ import { RESERVED_ROUTE_CONFIG_EXPORTS } from "akanjs/common";
23
26
  import ignore from "ignore";
24
- import ts2 from "typescript";
27
+ import ts3 from "typescript";
25
28
 
26
- // pkgs/@akanjs/devkit/ssrScanner.ts
29
+ // pkgs/@akanjs/devkit/formSetterScanner.ts
27
30
  import ts from "typescript";
31
+
32
+ class FormSetterScanner {
33
+ static #fieldSetter = /^set[A-Za-z0-9_$]*On[A-Za-z0-9_$]*$/;
34
+ scan(sourceFiles) {
35
+ return sourceFiles.filter((sourceFile) => sourceFile.file.endsWith(".tsx")).flatMap((f) => this.#scanFile(f));
36
+ }
37
+ #scanFile({ file, sourceFile }) {
38
+ const wrapped = [];
39
+ const visit = (node) => {
40
+ if (ts.isJsxAttribute(node)) {
41
+ const setter = FormSetterScanner.#wrappedSetterOf(node);
42
+ if (setter)
43
+ wrapped.push({ setter, line: FormSetterScanner.#lineOf(sourceFile, node) });
44
+ }
45
+ ts.forEachChild(node, visit);
46
+ };
47
+ visit(sourceFile);
48
+ if (!wrapped.length)
49
+ return [];
50
+ const setters = [...new Set(wrapped.map((entry) => entry.setter))].sort();
51
+ return [
52
+ {
53
+ rule: "akan.agent.unpublished-form-setter",
54
+ scope: "agent",
55
+ severity: "warning",
56
+ 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(", ")}.`,
57
+ file,
58
+ line: wrapped[0]?.line,
59
+ locations: wrapped.map(({ line }) => ({ file, line }))
60
+ }
61
+ ];
62
+ }
63
+ static #wrappedSetterOf(attribute) {
64
+ if (!ts.isIdentifier(attribute.name) || !/^on[A-Z]/.test(attribute.name.text))
65
+ return null;
66
+ const initializer = attribute.initializer;
67
+ if (!initializer || !ts.isJsxExpression(initializer) || !initializer.expression)
68
+ return null;
69
+ const handler = initializer.expression;
70
+ if (!ts.isArrowFunction(handler) && !ts.isFunctionExpression(handler))
71
+ return null;
72
+ if (!handler.parameters.length)
73
+ return null;
74
+ return FormSetterScanner.#setterCallIn(handler.body);
75
+ }
76
+ static #setterCallIn(node) {
77
+ let found = null;
78
+ const visit = (current) => {
79
+ if (found)
80
+ return;
81
+ if (ts.isCallExpression(current) && ts.isPropertyAccessExpression(current.expression)) {
82
+ const { expression, name } = current.expression;
83
+ if (ts.isPropertyAccessExpression(expression) && ts.isIdentifier(expression.expression) && expression.expression.text === "st" && expression.name.text === "do" && FormSetterScanner.#fieldSetter.test(name.text)) {
84
+ found = name.text;
85
+ return;
86
+ }
87
+ }
88
+ ts.forEachChild(current, visit);
89
+ };
90
+ visit(node);
91
+ return found;
92
+ }
93
+ static #lineOf(sourceFile, node) {
94
+ return sourceFile.getLineAndCharacterOfPosition(node.getStart(sourceFile)).line + 1;
95
+ }
96
+ }
97
+
98
+ // pkgs/@akanjs/devkit/ssrScanner.ts
99
+ import ts2 from "typescript";
28
100
  var STATIC_COMPONENT_MIN_MASS = 4;
29
101
  var MIXED_COMPONENT_MIN_MASS = 10;
30
102
  var MIXED_COMPONENT_MAX_TOUCHES = 2;
@@ -142,31 +214,31 @@ class SsrScanner {
142
214
  });
143
215
  }
144
216
  }
145
- ts.forEachChild(node, visit);
217
+ ts2.forEachChild(node, visit);
146
218
  };
147
- ts.forEachChild(sourceFile.sourceFile, visit);
219
+ ts2.forEachChild(sourceFile.sourceFile, visit);
148
220
  return warnings;
149
221
  }
150
222
  #isMountEffect(sourceFile, node) {
151
- if (!ts.isCallExpression(node))
223
+ if (!ts2.isCallExpression(node))
152
224
  return false;
153
225
  const callee = node.expression.getText(sourceFile);
154
226
  if (callee !== "useEffect" && callee !== "useLayoutEffect")
155
227
  return false;
156
228
  const deps = node.arguments[1];
157
- return !!deps && ts.isArrayLiteralExpression(deps) && deps.elements.length === 0;
229
+ return !!deps && ts2.isArrayLiteralExpression(deps) && deps.elements.length === 0;
158
230
  }
159
231
  #getLoadCalls(sourceFile, node) {
160
232
  const calls = [];
161
233
  const visit = (child) => {
162
- if (ts.isCallExpression(child)) {
234
+ if (ts2.isCallExpression(child)) {
163
235
  const callee = child.expression.getText(sourceFile);
164
236
  if (/^fetch\.[a-z]/.test(callee) || /^st\.do\.(init|get|view|load|list|count|insight)[A-Z]/.test(callee))
165
237
  calls.push({ callee, node: child });
166
238
  }
167
- ts.forEachChild(child, visit);
239
+ ts2.forEachChild(child, visit);
168
240
  };
169
- ts.forEachChild(node, visit);
241
+ ts2.forEachChild(node, visit);
170
242
  return calls;
171
243
  }
172
244
  #getTemplateStateWarnings(sourceFile) {
@@ -174,7 +246,7 @@ class SsrScanner {
174
246
  return [];
175
247
  const warnings = [];
176
248
  const visit = (node) => {
177
- if (ts.isCallExpression(node) && node.expression.getText(sourceFile.sourceFile) === "useState") {
249
+ if (ts2.isCallExpression(node) && node.expression.getText(sourceFile.sourceFile) === "useState") {
178
250
  warnings.push({
179
251
  rule: "akan.ssr.template-client-state",
180
252
  scope: "ssr",
@@ -184,9 +256,9 @@ class SsrScanner {
184
256
  message: "Template holds form state in useState. Templates are store-driven and carry no local state."
185
257
  });
186
258
  }
187
- ts.forEachChild(node, visit);
259
+ ts2.forEachChild(node, visit);
188
260
  };
189
- ts.forEachChild(sourceFile.sourceFile, visit);
261
+ ts2.forEachChild(sourceFile.sourceFile, visit);
190
262
  return warnings;
191
263
  }
192
264
  #measureBalance(sourceFiles) {
@@ -227,14 +299,14 @@ class SsrScanner {
227
299
  return components;
228
300
  }
229
301
  #getComponentNodes(statement) {
230
- if (ts.isFunctionDeclaration(statement) && statement.body)
302
+ if (ts2.isFunctionDeclaration(statement) && statement.body)
231
303
  return [{ name: statement.name?.text ?? "default", node: statement.body }];
232
- if (!ts.isVariableStatement(statement))
304
+ if (!ts2.isVariableStatement(statement))
233
305
  return [];
234
306
  return statement.declarationList.declarations.flatMap((declaration) => {
235
- if (!ts.isIdentifier(declaration.name) || !declaration.initializer)
307
+ if (!ts2.isIdentifier(declaration.name) || !declaration.initializer)
236
308
  return [];
237
- if (!ts.isArrowFunction(declaration.initializer) && !ts.isFunctionExpression(declaration.initializer))
309
+ if (!ts2.isArrowFunction(declaration.initializer) && !ts2.isFunctionExpression(declaration.initializer))
238
310
  return [];
239
311
  return [{ name: declaration.name.text, node: declaration.initializer }];
240
312
  });
@@ -242,7 +314,7 @@ class SsrScanner {
242
314
  #getTouches(sourceFile, node) {
243
315
  const touches = [];
244
316
  const visit = (child) => {
245
- if (ts.isCallExpression(child)) {
317
+ if (ts2.isCallExpression(child)) {
246
318
  const callee = child.expression.getText(sourceFile);
247
319
  const bareName = callee.split(".").pop() ?? callee;
248
320
  if (callee === "createContext" || callee === "lazy")
@@ -250,60 +322,60 @@ class SsrScanner {
250
322
  else if (/^use[A-Z]/.test(bareName) && !SsrScanner.#serverSafeCalls.has(bareName))
251
323
  touches.push(bareName);
252
324
  }
253
- if (ts.isJsxAttribute(child) && /^on[A-Z]/.test(child.name.getText(sourceFile)))
325
+ if (ts2.isJsxAttribute(child) && /^on[A-Z]/.test(child.name.getText(sourceFile)))
254
326
  touches.push(child.name.getText(sourceFile));
255
- if (ts.isPropertyAccessExpression(child)) {
327
+ if (ts2.isPropertyAccessExpression(child)) {
256
328
  const root = getAccessRoot(child);
257
329
  if (root === "st")
258
330
  touches.push("st");
259
331
  else if (SsrScanner.#clientGlobals.has(root))
260
332
  touches.push(root);
261
333
  }
262
- ts.forEachChild(child, visit);
334
+ ts2.forEachChild(child, visit);
263
335
  };
264
- ts.forEachChild(node, visit);
336
+ ts2.forEachChild(node, visit);
265
337
  return touches;
266
338
  }
267
339
  #getTagNames(sourceFile, node) {
268
340
  const tags = new Set;
269
341
  const visit = (child) => {
270
- if (ts.isJsxOpeningElement(child) || ts.isJsxSelfClosingElement(child))
342
+ if (ts2.isJsxOpeningElement(child) || ts2.isJsxSelfClosingElement(child))
271
343
  tags.add(child.tagName.getText(sourceFile).split(".")[0]);
272
- ts.forEachChild(child, visit);
344
+ ts2.forEachChild(child, visit);
273
345
  };
274
- ts.forEachChild(node, visit);
346
+ ts2.forEachChild(node, visit);
275
347
  return tags;
276
348
  }
277
349
  #getMass(node) {
278
350
  let mass = 0;
279
351
  const visit = (child) => {
280
- if (ts.isJsxOpeningElement(child) || ts.isJsxSelfClosingElement(child))
352
+ if (ts2.isJsxOpeningElement(child) || ts2.isJsxSelfClosingElement(child))
281
353
  mass += 1;
282
- ts.forEachChild(child, visit);
354
+ ts2.forEachChild(child, visit);
283
355
  };
284
- ts.forEachChild(node, visit);
356
+ ts2.forEachChild(node, visit);
285
357
  return mass;
286
358
  }
287
359
  #hasUseClient(sourceFile) {
288
360
  const first = sourceFile.statements[0];
289
- if (!first || !ts.isExpressionStatement(first) || !ts.isStringLiteral(first.expression))
361
+ if (!first || !ts2.isExpressionStatement(first) || !ts2.isStringLiteral(first.expression))
290
362
  return false;
291
363
  return first.expression.text === "use client";
292
364
  }
293
365
  #hasVendorImport(sourceFile) {
294
- return sourceFile.statements.some((statement) => ts.isImportDeclaration(statement) && isVendorSpecifier(getSpecifier(statement)));
366
+ return sourceFile.statements.some((statement) => ts2.isImportDeclaration(statement) && isVendorSpecifier(getSpecifier(statement)));
295
367
  }
296
368
  #getVendorNames(sourceFile) {
297
369
  const names = new Set;
298
370
  for (const statement of sourceFile.statements) {
299
- if (!ts.isImportDeclaration(statement) || !isVendorSpecifier(getSpecifier(statement)))
371
+ if (!ts2.isImportDeclaration(statement) || !isVendorSpecifier(getSpecifier(statement)))
300
372
  continue;
301
373
  const clause = statement.importClause;
302
374
  if (clause?.name)
303
375
  names.add(clause.name.text);
304
- if (clause?.namedBindings && ts.isNamespaceImport(clause.namedBindings))
376
+ if (clause?.namedBindings && ts2.isNamespaceImport(clause.namedBindings))
305
377
  names.add(clause.namedBindings.name.text);
306
- if (clause?.namedBindings && ts.isNamedImports(clause.namedBindings))
378
+ if (clause?.namedBindings && ts2.isNamedImports(clause.namedBindings))
307
379
  for (const element of clause.namedBindings.elements)
308
380
  names.add(element.name.text);
309
381
  }
@@ -311,10 +383,10 @@ class SsrScanner {
311
383
  }
312
384
  #importsClientRuntime(sourceFile) {
313
385
  for (const statement of sourceFile.statements) {
314
- if (!ts.isImportDeclaration(statement))
386
+ if (!ts2.isImportDeclaration(statement))
315
387
  continue;
316
388
  const bindings = statement.importClause?.namedBindings;
317
- if (!bindings || !ts.isNamedImports(bindings))
389
+ if (!bindings || !ts2.isNamedImports(bindings))
318
390
  continue;
319
391
  if (bindings.elements.some((element) => SsrScanner.#clientRuntimeImports.has(element.name.text)))
320
392
  return true;
@@ -365,7 +437,7 @@ function getShare(serverMass, clientMass) {
365
437
  return total === 0 ? 1 : serverMass / total;
366
438
  }
367
439
  function getSpecifier(statement) {
368
- return ts.isStringLiteral(statement.moduleSpecifier) ? statement.moduleSpecifier.text : "";
440
+ return ts2.isStringLiteral(statement.moduleSpecifier) ? statement.moduleSpecifier.text : "";
369
441
  }
370
442
  function isVendorSpecifier(specifier) {
371
443
  if (specifier === "" || specifier.startsWith(".") || specifier.startsWith("/"))
@@ -378,9 +450,9 @@ function isVendorSpecifier(specifier) {
378
450
  }
379
451
  function getAccessRoot(node) {
380
452
  let current = node;
381
- while (ts.isPropertyAccessExpression(current))
453
+ while (ts2.isPropertyAccessExpression(current))
382
454
  current = current.expression;
383
- return ts.isIdentifier(current) ? current.text : "";
455
+ return ts2.isIdentifier(current) ? current.text : "";
384
456
  }
385
457
 
386
458
  // pkgs/@akanjs/devkit/qualityScanner.ts
@@ -404,7 +476,8 @@ var SUGGESTED_RULES = [
404
476
  "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.",
405
477
  "Use domain module folders consistently: lib/<model> for database modules, lib/_<service> for service modules, and lib/__scalar/<scalar> for scalar modules.",
406
478
  "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.",
407
- "Move shared app utilities to apps/*/common instead of creating apps/*/base.",
479
+ "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.",
480
+ "Move shared app and lib utilities to common/ instead of creating apps/*/base or libs/*/base.",
408
481
  "Avoid large mixed-purpose class files; class export files should import helpers from neighboring utility files instead of declaring them inline."
409
482
  ];
410
483
  var CONVENTION_SUFFIXES = [
@@ -415,20 +488,6 @@ var CONVENTION_SUFFIXES = [
415
488
  ".signal.ts",
416
489
  ".store.ts"
417
490
  ];
418
- var PAGE_RESERVED_EXPORTS = new Set([
419
- "pageConfig",
420
- "head",
421
- "metadata",
422
- "generateHead",
423
- "generateMetadata",
424
- "fonts",
425
- "manifest",
426
- "theme",
427
- "reconnect",
428
- "wsConnect",
429
- "layoutStyle",
430
- "gaTrackingId"
431
- ]);
432
491
  var RULE_FIXES = {
433
492
  "akan.global.duplicate-exported-function-name": "Rename one of the exports, or if they are the same thing, extract it into one shared module and import it in both places.",
434
493
  "akan.global.duplicate-exported-function-body": "Extract the shared implementation into a single exported helper and import it, instead of copying the body.",
@@ -441,10 +500,14 @@ var RULE_FIXES = {
441
500
  "akan.file.window-augmentation": "Move the Window augmentation into an approved browser integration file and keep it isolated.",
442
501
  "akan.file.prototype-mutation": "Avoid prototype mutation, or isolate it in an approved low-level integration file.",
443
502
  "akan.file.class-export-global-declaration": "Move the helper to a sibling file and import it into the class module.",
503
+ "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.",
444
504
  "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`.",
445
505
  "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.",
446
506
  "akan.layout.app-root-file": "Move the file into a conventional app folder (common, env, lib, page, private, public, script, srvkit, ui, or webkit).",
447
- "akan.layout.lib-root-file": "Move the file into a domain module folder under lib/; keep lib root limited to generated support facets.",
507
+ "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.",
508
+ "akan.layout.lib-root-file": "Move the file into a conventional lib folder (common, env, lib, page, private, public, srvkit, ui, or webkit).",
509
+ "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.",
510
+ "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.",
448
511
  "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.",
449
512
  "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.',
450
513
  "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.",
@@ -472,6 +535,7 @@ class AkanQualityScanner {
472
535
  ...sourceFiles.flatMap((sourceFile) => this.#scanConventionQuality(sourceFile)),
473
536
  ...sourceFiles.flatMap((sourceFile) => this.#scanLayoutQuality(sourceFile)),
474
537
  ...abstractFiles.flatMap((abstractFile) => this.#scanAbstractQuality(abstractFile)),
538
+ ...new FormSetterScanner().scan(sourceFiles),
475
539
  ...ssr.warnings
476
540
  ];
477
541
  return {
@@ -524,7 +588,7 @@ class AkanQualityScanner {
524
588
  file,
525
589
  absolutePath,
526
590
  content,
527
- sourceFile: ts2.createSourceFile(file, content, ts2.ScriptTarget.Latest, true, getScriptKind(file))
591
+ sourceFile: ts3.createSourceFile(file, content, ts3.ScriptTarget.Latest, true, getScriptKind(file))
528
592
  };
529
593
  }
530
594
  async#readTextFile(workspaceRoot, file) {
@@ -683,25 +747,30 @@ class AkanQualityScanner {
683
747
  ];
684
748
  }
685
749
  #scanLayoutQuality(sourceFile) {
686
- const segments = sourceFile.file.split("/");
687
750
  const warnings = [];
688
- if (segments[0] === "apps" && segments.length === 3 && !appRootAllowedFiles.has(segments[2])) {
689
- warnings.push({
690
- rule: "akan.layout.app-root-file",
691
- scope: "layout",
692
- severity: "warning",
693
- file: sourceFile.file,
694
- message: `Unexpected app root file "${segments[2]}". Keep application code in conventional app folders.`
695
- });
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
+ }
696
765
  }
697
- const libRootFile = getLibRootFile(sourceFile.file);
698
- if (libRootFile && !libFacetRootAllowedFiles.has(libRootFile)) {
766
+ const libFacetFile = getLibFacetRootFile(sourceFile.file);
767
+ if (libFacetFile && !isAllowedLibFacetRootFile(libFacetFile)) {
699
768
  warnings.push({
700
- rule: "akan.layout.lib-root-file",
769
+ rule: "akan.layout.lib-facet-file",
701
770
  scope: "layout",
702
771
  severity: "warning",
703
772
  file: sourceFile.file,
704
- 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.`
705
774
  });
706
775
  }
707
776
  const moduleUiWarning = getModuleUiWarning(sourceFile.file);
@@ -771,7 +840,7 @@ function getExportedFunctionLikes(sourceFile) {
771
840
  const declarations = [];
772
841
  const nameExempt = isPageRouteFile(sourceFile.file) || isUiComponentFile(sourceFile.file);
773
842
  for (const statement of sourceFile.sourceFile.statements) {
774
- if (ts2.isFunctionDeclaration(statement) && statement.name && isExported(statement)) {
843
+ if (ts3.isFunctionDeclaration(statement) && statement.name && isExported(statement)) {
775
844
  declarations.push({
776
845
  name: statement.name.text,
777
846
  kind: "function",
@@ -781,7 +850,7 @@ function getExportedFunctionLikes(sourceFile) {
781
850
  duplicateNameExempt: nameExempt || isConventionDuplicateNameExempt(sourceFile.file, false)
782
851
  });
783
852
  }
784
- if (ts2.isClassDeclaration(statement) && statement.name && isExported(statement)) {
853
+ if (ts3.isClassDeclaration(statement) && statement.name && isExported(statement)) {
785
854
  declarations.push({
786
855
  name: statement.name.text,
787
856
  kind: "class",
@@ -791,9 +860,9 @@ function getExportedFunctionLikes(sourceFile) {
791
860
  duplicateNameExempt: nameExempt || isConventionDuplicateNameExempt(sourceFile.file, isEnumClassStatement(sourceFile.sourceFile, statement))
792
861
  });
793
862
  }
794
- if (ts2.isVariableStatement(statement) && isExported(statement)) {
863
+ if (ts3.isVariableStatement(statement) && isExported(statement)) {
795
864
  for (const declaration of statement.declarationList.declarations) {
796
- if (!ts2.isIdentifier(declaration.name) || !isFunctionLikeInitializer(declaration.initializer))
865
+ if (!ts3.isIdentifier(declaration.name) || !isFunctionLikeInitializer(declaration.initializer))
797
866
  continue;
798
867
  declarations.push({
799
868
  name: declaration.name.text,
@@ -832,14 +901,14 @@ function isInLibModule(file) {
832
901
  return (segments[0] === "apps" || segments[0] === "libs") && segments.includes("lib");
833
902
  }
834
903
  function isEnumClassStatement(sourceFile, statement) {
835
- if (!ts2.isClassDeclaration(statement))
904
+ if (!ts3.isClassDeclaration(statement))
836
905
  return false;
837
- const heritageClause = statement.heritageClauses?.find((clause) => clause.token === ts2.SyntaxKind.ExtendsKeyword);
906
+ const heritageClause = statement.heritageClauses?.find((clause) => clause.token === ts3.SyntaxKind.ExtendsKeyword);
838
907
  const expression = heritageClause?.types[0]?.expression;
839
908
  return !!expression && expression.getText(sourceFile).startsWith("enumOf(");
840
909
  }
841
910
  function getExportedClassNames(sourceFile) {
842
- return sourceFile.statements.filter((statement) => ts2.isClassDeclaration(statement) && !!statement.name).filter((statement) => isExported(statement)).map((statement) => statement.name.text);
911
+ return sourceFile.statements.filter((statement) => ts3.isClassDeclaration(statement) && !!statement.name).filter((statement) => isExported(statement)).map((statement) => statement.name.text);
843
912
  }
844
913
  function isComponentDeclarationFile(file) {
845
914
  if (!file.endsWith(".tsx"))
@@ -855,7 +924,7 @@ function isComponentDeclarationFile(file) {
855
924
  function getComponentFileDeclarations(sourceFile) {
856
925
  const reExportedNames = new Set;
857
926
  for (const statement of sourceFile.statements) {
858
- if (ts2.isExportDeclaration(statement) && statement.exportClause && ts2.isNamedExports(statement.exportClause)) {
927
+ if (ts3.isExportDeclaration(statement) && statement.exportClause && ts3.isNamedExports(statement.exportClause)) {
859
928
  for (const element of statement.exportClause.elements)
860
929
  reExportedNames.add((element.propertyName ?? element.name).text);
861
930
  }
@@ -865,19 +934,19 @@ function getComponentFileDeclarations(sourceFile) {
865
934
  const isDefaultExport = isDefaultExportStatement(statement);
866
935
  const inlineExported = isExported(statement);
867
936
  const add = (name, kind, line) => declarations.push({ name, kind, line, exported: inlineExported || reExportedNames.has(name), isDefaultExport });
868
- if (ts2.isInterfaceDeclaration(statement))
937
+ if (ts3.isInterfaceDeclaration(statement))
869
938
  add(statement.name.text, "interface", getLine(sourceFile, statement));
870
- else if (ts2.isTypeAliasDeclaration(statement))
939
+ else if (ts3.isTypeAliasDeclaration(statement))
871
940
  add(statement.name.text, "type", getLine(sourceFile, statement));
872
- else if (ts2.isEnumDeclaration(statement))
941
+ else if (ts3.isEnumDeclaration(statement))
873
942
  add(statement.name.text, "enum", getLine(sourceFile, statement));
874
- else if (ts2.isFunctionDeclaration(statement) && statement.name)
943
+ else if (ts3.isFunctionDeclaration(statement) && statement.name)
875
944
  add(statement.name.text, "function", getLine(sourceFile, statement));
876
- else if (ts2.isClassDeclaration(statement) && statement.name)
945
+ else if (ts3.isClassDeclaration(statement) && statement.name)
877
946
  add(statement.name.text, "class", getLine(sourceFile, statement));
878
- else if (ts2.isVariableStatement(statement)) {
947
+ else if (ts3.isVariableStatement(statement)) {
879
948
  for (const declaration of statement.declarationList.declarations) {
880
- if (!ts2.isIdentifier(declaration.name))
949
+ if (!ts3.isIdentifier(declaration.name))
881
950
  continue;
882
951
  const kind = isFunctionLikeInitializer(declaration.initializer) ? "function" : "variable";
883
952
  add(declaration.name.text, kind, getLine(sourceFile, declaration));
@@ -889,15 +958,15 @@ function getComponentFileDeclarations(sourceFile) {
889
958
  function getCompoundComponentNames(sourceFile) {
890
959
  const names = new Set;
891
960
  for (const statement of sourceFile.statements) {
892
- if (!ts2.isExpressionStatement(statement))
961
+ if (!ts3.isExpressionStatement(statement))
893
962
  continue;
894
963
  const { expression } = statement;
895
- if (!ts2.isBinaryExpression(expression) || expression.operatorToken.kind !== ts2.SyntaxKind.EqualsToken)
964
+ if (!ts3.isBinaryExpression(expression) || expression.operatorToken.kind !== ts3.SyntaxKind.EqualsToken)
896
965
  continue;
897
- if (!ts2.isPropertyAccessExpression(expression.left) || !isPascalCaseName(expression.left.name.text))
966
+ if (!ts3.isPropertyAccessExpression(expression.left) || !isPascalCaseName(expression.left.name.text))
898
967
  continue;
899
968
  names.add(expression.left.name.text);
900
- if (ts2.isIdentifier(expression.right) && isPascalCaseName(expression.right.text))
969
+ if (ts3.isIdentifier(expression.right) && isPascalCaseName(expression.right.text))
901
970
  names.add(expression.right.text);
902
971
  }
903
972
  return names;
@@ -911,15 +980,15 @@ function isRestrictedInternalKind(kind) {
911
980
  function isAllowedComponentExport(declaration, isPage) {
912
981
  if (isComponentValueKind(declaration.kind) && isPascalCaseName(declaration.name))
913
982
  return true;
914
- return isPage && PAGE_RESERVED_EXPORTS.has(declaration.name);
983
+ return isPage && RESERVED_ROUTE_CONFIG_EXPORTS.has(declaration.name);
915
984
  }
916
985
  function isPascalCaseName(name) {
917
986
  return /^[A-Z]/.test(name) && !/^[A-Z0-9_]+$/.test(name);
918
987
  }
919
988
  function isDefaultExportStatement(statement) {
920
- if (ts2.isExportAssignment(statement))
989
+ if (ts3.isExportAssignment(statement))
921
990
  return !statement.isExportEquals;
922
- return !!(ts2.getCombinedModifierFlags(statement) & ts2.ModifierFlags.Default);
991
+ return !!(ts3.getCombinedModifierFlags(statement) & ts3.ModifierFlags.Default);
923
992
  }
924
993
  function getPlaceholderExportWarnings(sourceFile) {
925
994
  if (!sourceFile.file.endsWith("/index.ts") && !sourceFile.file.endsWith("/index.tsx"))
@@ -949,7 +1018,7 @@ function getDictionaryTextWarnings(sourceFile) {
949
1018
  function getGlobalMutationWarnings(sourceFile) {
950
1019
  const warnings = [];
951
1020
  for (const statement of sourceFile.sourceFile.statements) {
952
- if (ts2.isModuleDeclaration(statement) && statement.name.getText(sourceFile.sourceFile) === "global") {
1021
+ if (ts3.isModuleDeclaration(statement) && statement.name.getText(sourceFile.sourceFile) === "global") {
953
1022
  warnings.push({
954
1023
  rule: "akan.file.global-declaration",
955
1024
  scope: "file",
@@ -959,7 +1028,7 @@ function getGlobalMutationWarnings(sourceFile) {
959
1028
  message: "Global declarations require an explicit low-level integration allowlist."
960
1029
  });
961
1030
  }
962
- if (ts2.isInterfaceDeclaration(statement) && statement.name.text === "Window") {
1031
+ if (ts3.isInterfaceDeclaration(statement) && statement.name.text === "Window") {
963
1032
  warnings.push({
964
1033
  rule: "akan.file.window-augmentation",
965
1034
  scope: "file",
@@ -969,7 +1038,7 @@ function getGlobalMutationWarnings(sourceFile) {
969
1038
  message: "Window augmentation should be isolated to approved browser integration files."
970
1039
  });
971
1040
  }
972
- if (ts2.isExpressionStatement(statement) && statement.expression.getText(sourceFile.sourceFile).includes(".prototype.")) {
1041
+ if (ts3.isExpressionStatement(statement) && statement.expression.getText(sourceFile.sourceFile).includes(".prototype.")) {
973
1042
  warnings.push({
974
1043
  rule: "akan.file.prototype-mutation",
975
1044
  scope: "file",
@@ -987,23 +1056,23 @@ function getTopLevelDeclarations(sourceFile) {
987
1056
  }
988
1057
  function getTopLevelDeclaration(sourceFile, statement) {
989
1058
  const line = getLine(sourceFile, statement);
990
- if (ts2.isClassDeclaration(statement) && statement.name) {
1059
+ if (ts3.isClassDeclaration(statement) && statement.name) {
991
1060
  return [{ name: statement.name.text, kind: "class", line, exported: isExported(statement), node: statement }];
992
1061
  }
993
- if (ts2.isFunctionDeclaration(statement) && statement.name) {
1062
+ if (ts3.isFunctionDeclaration(statement) && statement.name) {
994
1063
  return [{ name: statement.name.text, kind: "function", line, exported: isExported(statement), node: statement }];
995
1064
  }
996
- if (ts2.isInterfaceDeclaration(statement)) {
1065
+ if (ts3.isInterfaceDeclaration(statement)) {
997
1066
  return [{ name: statement.name.text, kind: "interface", line, exported: isExported(statement), node: statement }];
998
1067
  }
999
- if (ts2.isTypeAliasDeclaration(statement)) {
1068
+ if (ts3.isTypeAliasDeclaration(statement)) {
1000
1069
  return [{ name: statement.name.text, kind: "type", line, exported: isExported(statement), node: statement }];
1001
1070
  }
1002
- if (ts2.isEnumDeclaration(statement)) {
1071
+ if (ts3.isEnumDeclaration(statement)) {
1003
1072
  return [{ name: statement.name.text, kind: "enum", line, exported: isExported(statement), node: statement }];
1004
1073
  }
1005
- if (ts2.isVariableStatement(statement)) {
1006
- return statement.declarationList.declarations.filter((declaration) => ts2.isIdentifier(declaration.name)).map((declaration) => ({
1074
+ if (ts3.isVariableStatement(statement)) {
1075
+ return statement.declarationList.declarations.filter((declaration) => ts3.isIdentifier(declaration.name)).map((declaration) => ({
1007
1076
  name: declaration.name.text,
1008
1077
  kind: "variable",
1009
1078
  line: getLine(sourceFile, declaration),
@@ -1011,7 +1080,7 @@ function getTopLevelDeclaration(sourceFile, statement) {
1011
1080
  node: statement
1012
1081
  }));
1013
1082
  }
1014
- if (ts2.isExportDeclaration(statement)) {
1083
+ if (ts3.isExportDeclaration(statement)) {
1015
1084
  return [{ name: "export declaration", kind: "export", line, exported: true, node: statement }];
1016
1085
  }
1017
1086
  return [];
@@ -1036,9 +1105,9 @@ function isAllowedConstantDeclaration(modelName, declaration) {
1036
1105
  return false;
1037
1106
  if ([`${modelName}Input`, `${modelName}Object`, modelName, `Light${modelName}`, `${modelName}Insight`].includes(declaration.name))
1038
1107
  return true;
1039
- if (!ts2.isClassDeclaration(declaration.node))
1108
+ if (!ts3.isClassDeclaration(declaration.node))
1040
1109
  return false;
1041
- const heritageClause = declaration.node.heritageClauses?.find((clause) => clause.token === ts2.SyntaxKind.ExtendsKeyword);
1110
+ const heritageClause = declaration.node.heritageClauses?.find((clause) => clause.token === ts3.SyntaxKind.ExtendsKeyword);
1042
1111
  const expression = heritageClause?.types[0]?.expression;
1043
1112
  return !!expression && expression.getText().startsWith("enumOf(");
1044
1113
  }
@@ -1055,7 +1124,19 @@ function getConventionDescription(suffix, modelName) {
1055
1124
  return `${modelName}Internal, ${modelName}Slice, or ${modelName}Endpoint`;
1056
1125
  return `${modelName}Store`;
1057
1126
  }
1058
- function getLibRootFile(file) {
1127
+ var sysRootTypes = { apps: "app", libs: "lib" };
1128
+ function getSysRootEntry(file) {
1129
+ const segments = file.split("/");
1130
+ const root = segments[0];
1131
+ const name = segments[2];
1132
+ if (!root || !name || segments.length < 3)
1133
+ return null;
1134
+ const type = sysRootTypes[root];
1135
+ if (!type)
1136
+ return null;
1137
+ return { type, name, isDir: segments.length > 3 };
1138
+ }
1139
+ function getLibFacetRootFile(file) {
1059
1140
  const segments = file.split("/");
1060
1141
  if ((segments[0] === "libs" || segments[0] === "apps") && segments.length === 4 && segments[2] === "lib")
1061
1142
  return segments[3];
@@ -1104,13 +1185,13 @@ function getModuleInfo(file) {
1104
1185
  return { moduleName, fileName, kind: "database" };
1105
1186
  }
1106
1187
  function isExportedConst(declaration) {
1107
- return declaration.exported && ts2.isVariableStatement(declaration.node) && (declaration.node.declarationList.flags & ts2.NodeFlags.Const) !== 0;
1188
+ return declaration.exported && ts3.isVariableStatement(declaration.node) && (declaration.node.declarationList.flags & ts3.NodeFlags.Const) !== 0;
1108
1189
  }
1109
1190
  function isExported(node) {
1110
- return !!ts2.getCombinedModifierFlags(node) && !!(ts2.getCombinedModifierFlags(node) & ts2.ModifierFlags.Export);
1191
+ return !!ts3.getCombinedModifierFlags(node) && !!(ts3.getCombinedModifierFlags(node) & ts3.ModifierFlags.Export);
1111
1192
  }
1112
1193
  function isFunctionLikeInitializer(node) {
1113
- return !!node && (ts2.isArrowFunction(node) || ts2.isFunctionExpression(node));
1194
+ return !!node && (ts3.isArrowFunction(node) || ts3.isFunctionExpression(node));
1114
1195
  }
1115
1196
  function getBodyFingerprint(sourceFile, node) {
1116
1197
  if (!node)
@@ -1132,7 +1213,7 @@ async function isDirectory(absolutePath) {
1132
1213
  }
1133
1214
  }
1134
1215
  function getScriptKind(file) {
1135
- return file.endsWith(".tsx") ? ts2.ScriptKind.TSX : ts2.ScriptKind.TS;
1216
+ return file.endsWith(".tsx") ? ts3.ScriptKind.TSX : ts3.ScriptKind.TS;
1136
1217
  }
1137
1218
  function getLine(sourceFile, node) {
1138
1219
  return sourceFile.getLineAndCharacterOfPosition(node.getStart(sourceFile)).line + 1;