@akanjs/cli 3.0.0-alpha.6 → 3.0.0-alpha.61

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