@akanjs/cli 3.0.0-alpha.7 → 3.0.0-alpha.71

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 (129) 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-pggr92jj.js → agent.command-xdvw642m.js} +9 -7
  6. package/{application.command-692yap1d.js → application.command-g3f7xj25.js} +31 -6
  7. package/{applicationBuildRunner-ha3y8ew8.js → applicationBuildRunner-af0wtspv.js} +19 -14
  8. package/{applicationReleasePackager-gg8ccbeh.js → applicationReleasePackager-93nzdfsy.js} +1 -1
  9. package/buildBatch.proc.js +7 -5
  10. package/{capacitorApp-p0z5yab2.js → capacitorApp-b0ppke3m.js} +29 -27
  11. package/{cloud.command-wqfsyzv2.js → cloud.command-0hmsv44q.js} +12 -10
  12. package/commandManifest.json +1 -1
  13. package/{context.command-9bxtdn0w.js → context.command-x92afrnf.js} +17 -15
  14. package/{dependencyScanner-f6grrep3.js → dependencyScanner-rbs2407b.js} +1 -1
  15. package/getDirname-ka11hm7e.js +8 -0
  16. package/{guideline.command-nfqahxex.js → guideline.command-hpq08q66.js} +8 -6
  17. package/guidelines/agentRule/agentRule.generate.json +31 -0
  18. package/guidelines/agentRule/agentRule.instruction.md +525 -0
  19. package/guidelines/conventions/conventions.generate.json +25 -0
  20. package/guidelines/conventions/conventions.instruction.md +839 -0
  21. package/guidelines/cssRule/cssRule.instruction.md +7 -0
  22. package/guidelines/framework/framework.generate.json +3 -2
  23. package/guidelines/framework/framework.instruction.md +1 -9
  24. package/guidelines/mcpRule/mcpRule.generate.json +25 -0
  25. package/guidelines/mcpRule/mcpRule.instruction.md +217 -0
  26. package/guidelines/queryRule/queryRule.generate.json +31 -0
  27. package/guidelines/queryRule/queryRule.instruction.md +143 -0
  28. package/guidelines/runtimeRule/runtimeRule.generate.json +25 -0
  29. package/guidelines/runtimeRule/runtimeRule.instruction.md +176 -0
  30. package/guidelines/transportRule/transportRule.generate.json +31 -0
  31. package/guidelines/transportRule/transportRule.instruction.md +60 -0
  32. package/guidelines/workspaceOnboarding/workspaceOnboarding.generate.json +24 -0
  33. package/guidelines/workspaceOnboarding/workspaceOnboarding.instruction.md +263 -0
  34. package/guidelines/workspaceRecipes/workspaceRecipes.generate.json +33 -0
  35. package/guidelines/workspaceRecipes/workspaceRecipes.instruction.md +402 -0
  36. package/incrementalBuilder.proc.js +8 -6
  37. package/index-1577bej2.js +7 -0
  38. package/{index-vg0sj9nc.js → index-5f4gnt4c.js} +258 -97
  39. package/{index-bjpxzr6s.js → index-5vy2jzar.js} +1 -1
  40. package/{index-mssqn2sq.js → index-6d5r8q6k.js} +54 -15
  41. package/{index-p3f4q532.js → index-737aj4fm.js} +14 -13
  42. package/{index-2c3e2xh8.js → index-8besc14m.js} +1 -1
  43. package/{index-qb61h0k7.js → index-abyjt72y.js} +333 -540
  44. package/{index-xhgtkekh.js → index-cbfp5wyp.js} +1 -1
  45. package/{index-5dw57f0d.js → index-cp00yns2.js} +80 -14
  46. package/{index-ztpw544k.js → index-ety6takb.js} +3 -3
  47. package/index-f39512t6.js +255 -0
  48. package/{index-6rhabeev.js → index-gvq17wqn.js} +5 -3
  49. package/{index-js368gk9.js → index-hs70d8ed.js} +6 -6
  50. package/{index-7myvn0f0.js → index-j37qq1f2.js} +1 -1
  51. package/{index-azv0xjb5.js → index-jybbmwed.js} +8 -4
  52. package/{index-4hek879p.js → index-m5gfc86a.js} +1 -1
  53. package/{index-w6wbb612.js → index-nq3avsf6.js} +13 -13
  54. package/{index-vtj3zps1.js → index-p27h7535.js} +8 -8
  55. package/{index-5pmx7bkg.js → index-pehn44hr.js} +3 -3
  56. package/{index-j9cxndc0.js → index-qnv0aqmm.js} +9 -6
  57. package/{index-gmy45vz2.js → index-r691v756.js} +4 -4
  58. package/{index-0cj2zxbm.js → index-sh01r3pm.js} +1 -1
  59. package/{index-a1e0j63x.js → index-sqsfv8wg.js} +15 -7
  60. package/{index-gex7c8kd.js → index-wbbswwqt.js} +51 -24
  61. package/{index-bz2sfzzt.js → index-wybbcgxn.js} +4 -4
  62. package/index.js +21 -19
  63. package/{library.command-md9715jz.js → library.command-8vgrdtwy.js} +6 -4
  64. package/{localRegistry.command-exkbamyj.js → localRegistry.command-0gtqj0r0.js} +11 -9
  65. package/{module.command-w5aaddxk.js → module.command-rama4zya.js} +10 -8
  66. package/{package.command-yfkrcy0z.js → package.command-6jxapdsg.js} +6 -4
  67. package/package.json +4 -5
  68. package/{page.command-echbavp4.js → page.command-hxy7x525.js} +6 -4
  69. package/{primitive.command-9fbhspw2.js → primitive.command-4bf6xcc3.js} +11 -9
  70. package/{quality.command-3pnt9yep.js → quality.command-h5edwegz.js} +192 -97
  71. package/{recipeScanner-tghkzrrt.js → recipeScanner-zrzev25x.js} +3 -3
  72. package/{repair.command-carj4vax.js → repair.command-rgqf6px6.js} +8 -6
  73. package/{routeSourceValidator-e4jmbfcx.js → routeSourceValidator-7fycdqyv.js} +1 -1
  74. package/{scalar.command-51ryzypc.js → scalar.command-6krkz6tw.js} +9 -7
  75. package/templates/app/env/env.client.debug.ts.template +1 -5
  76. package/templates/app/env/env.client.develop.ts.template +1 -5
  77. package/templates/app/env/env.client.local.ts.template +1 -5
  78. package/templates/app/env/env.client.main.ts.template +1 -5
  79. package/templates/app/env/env.client.testing.ts.template +1 -5
  80. package/templates/app/env/env.client.type.ts +2 -6
  81. package/templates/app/env/env.server.debug.ts.template +1 -5
  82. package/templates/app/env/env.server.develop.ts.template +1 -5
  83. package/templates/app/env/env.server.local.ts.template +1 -5
  84. package/templates/app/env/env.server.main.ts.template +1 -5
  85. package/templates/app/main.ts +3 -1
  86. package/templates/app/page/styles.css.template +3 -3
  87. package/templates/appSample/srvkit/AuthGuard.ts +9 -5
  88. package/templates/appSample/srvkit/SessionInternalArg.ts +4 -3
  89. package/templates/crudPages/[__model__Id]/edit/page.tsx +1 -1
  90. package/templates/crudPages/new/page.tsx +1 -1
  91. package/templates/crudSinglePage/page.tsx +1 -1
  92. package/templates/env/_env.server.type.ts +7 -5
  93. package/templates/lib/db.ts +1 -1
  94. package/templates/lib/dict.ts +1 -1
  95. package/templates/lib/sig.ts +1 -1
  96. package/templates/lib/srv.ts +6 -2
  97. package/templates/module/__Model__.Util.tsx +1 -1
  98. package/templates/module/__Model__.Zone.tsx +3 -5
  99. package/templates/workspaceRoot/AGENTS.md.template +4 -972
  100. package/templates/workspaceRoot/CLAUDE.md.template +19 -0
  101. package/templates/workspaceRoot/biome.json.template +4 -244
  102. package/templates/workspaceRoot/docs/AI-DEVELOPMENT.md.template +10 -14
  103. package/templates/workspaceRoot/docs/GENERATED.md.template +12 -10
  104. package/templates/workspaceRoot/package.json.template +1 -1
  105. package/{typeChecker-kravn7ns.js → typeChecker-g6bh0xfv.js} +1 -1
  106. package/typecheck.proc.js +1 -1
  107. package/{workflow.command-91wmvm8k.js → workflow.command-dwhwrqhd.js} +16 -14
  108. package/{workspace.command-wa10mfd9.js → workspace.command-f5h11gjn.js} +71 -52
  109. package/templates/workspaceRoot/.cursor/rules/akan-page-routing.mdc.template +0 -43
  110. package/templates/workspaceRoot/.cursor/rules/akan-sync-conventions.mdc.template +0 -34
  111. package/templates/workspaceRoot/.cursor/rules/application-test-commands.mdc.template +0 -16
  112. package/templates/workspaceRoot/.cursor/rules/change-scope.mdc.template +0 -12
  113. package/templates/workspaceRoot/.cursor/rules/client-server-boundaries.mdc.template +0 -18
  114. package/templates/workspaceRoot/.cursor/rules/coding-comments.mdc.template +0 -25
  115. package/templates/workspaceRoot/.cursor/rules/coding-style.mdc.template +0 -44
  116. package/templates/workspaceRoot/.cursor/rules/domain-module-conventions.mdc.template +0 -17
  117. package/templates/workspaceRoot/.cursor/rules/format-lint.mdc.template +0 -12
  118. package/templates/workspaceRoot/.cursor/rules/lint-enforced-rules.mdc.template +0 -19
  119. package/templates/workspaceRoot/.cursor/rules/module-file-playbook.mdc.template +0 -62
  120. package/templates/workspaceRoot/.cursor/rules/naming-conventions.mdc.template +0 -15
  121. package/templates/workspaceRoot/.cursor/rules/react-component-style.mdc.template +0 -21
  122. package/templates/workspaceRoot/.cursor/rules/repo-overview.mdc.template +0 -17
  123. package/templates/workspaceRoot/.cursor/rules/scalar-modeling.mdc.template +0 -99
  124. package/templates/workspaceRoot/.cursor/rules/secrets-and-env.mdc.template +0 -13
  125. package/templates/workspaceRoot/.cursor/rules/service-signal-conventions.mdc.template +0 -83
  126. package/templates/workspaceRoot/.cursor/rules/srvkit-adapters-and-guards.mdc.template +0 -50
  127. package/templates/workspaceRoot/.cursor/rules/ssr-first.mdc.template +0 -59
  128. package/templates/workspaceRoot/.cursor/rules/typescript-imports.mdc.template +0 -16
  129. /package/{index-r24hmh0q.js → index-yb2wsc7f.js} +0 -0
@@ -7,14 +7,17 @@ import {
7
7
  command,
8
8
  runner,
9
9
  script
10
- } from "./index-6rhabeev.js";
10
+ } from "./index-gvq17wqn.js";
11
11
  import {
12
- appRootAllowedFiles,
13
- libFacetRootAllowedFiles
14
- } from "./index-vg0sj9nc.js";
12
+ isAllowedLibFacetRootFile,
13
+ rootAllowedDirs,
14
+ rootAllowedFiles
15
+ } from "./index-5f4gnt4c.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";
@@ -22,10 +25,79 @@ import { readdir, readFile, stat } from "fs/promises";
22
25
  import path from "path";
23
26
  import { RESERVED_ROUTE_CONFIG_EXPORTS } from "akanjs/common";
24
27
  import ignore from "ignore";
25
- import ts2 from "typescript";
28
+ import ts3 from "typescript";
26
29
 
27
- // pkgs/@akanjs/devkit/ssrScanner.ts
30
+ // pkgs/@akanjs/devkit/formSetterScanner.ts
28
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";
29
101
  var STATIC_COMPONENT_MIN_MASS = 4;
30
102
  var MIXED_COMPONENT_MIN_MASS = 10;
31
103
  var MIXED_COMPONENT_MAX_TOUCHES = 2;
@@ -143,31 +215,31 @@ class SsrScanner {
143
215
  });
144
216
  }
145
217
  }
146
- ts.forEachChild(node, visit);
218
+ ts2.forEachChild(node, visit);
147
219
  };
148
- ts.forEachChild(sourceFile.sourceFile, visit);
220
+ ts2.forEachChild(sourceFile.sourceFile, visit);
149
221
  return warnings;
150
222
  }
151
223
  #isMountEffect(sourceFile, node) {
152
- if (!ts.isCallExpression(node))
224
+ if (!ts2.isCallExpression(node))
153
225
  return false;
154
226
  const callee = node.expression.getText(sourceFile);
155
227
  if (callee !== "useEffect" && callee !== "useLayoutEffect")
156
228
  return false;
157
229
  const deps = node.arguments[1];
158
- return !!deps && ts.isArrayLiteralExpression(deps) && deps.elements.length === 0;
230
+ return !!deps && ts2.isArrayLiteralExpression(deps) && deps.elements.length === 0;
159
231
  }
160
232
  #getLoadCalls(sourceFile, node) {
161
233
  const calls = [];
162
234
  const visit = (child) => {
163
- if (ts.isCallExpression(child)) {
235
+ if (ts2.isCallExpression(child)) {
164
236
  const callee = child.expression.getText(sourceFile);
165
237
  if (/^fetch\.[a-z]/.test(callee) || /^st\.do\.(init|get|view|load|list|count|insight)[A-Z]/.test(callee))
166
238
  calls.push({ callee, node: child });
167
239
  }
168
- ts.forEachChild(child, visit);
240
+ ts2.forEachChild(child, visit);
169
241
  };
170
- ts.forEachChild(node, visit);
242
+ ts2.forEachChild(node, visit);
171
243
  return calls;
172
244
  }
173
245
  #getTemplateStateWarnings(sourceFile) {
@@ -175,7 +247,7 @@ class SsrScanner {
175
247
  return [];
176
248
  const warnings = [];
177
249
  const visit = (node) => {
178
- if (ts.isCallExpression(node) && node.expression.getText(sourceFile.sourceFile) === "useState") {
250
+ if (ts2.isCallExpression(node) && node.expression.getText(sourceFile.sourceFile) === "useState") {
179
251
  warnings.push({
180
252
  rule: "akan.ssr.template-client-state",
181
253
  scope: "ssr",
@@ -185,9 +257,9 @@ class SsrScanner {
185
257
  message: "Template holds form state in useState. Templates are store-driven and carry no local state."
186
258
  });
187
259
  }
188
- ts.forEachChild(node, visit);
260
+ ts2.forEachChild(node, visit);
189
261
  };
190
- ts.forEachChild(sourceFile.sourceFile, visit);
262
+ ts2.forEachChild(sourceFile.sourceFile, visit);
191
263
  return warnings;
192
264
  }
193
265
  #measureBalance(sourceFiles) {
@@ -228,14 +300,14 @@ class SsrScanner {
228
300
  return components;
229
301
  }
230
302
  #getComponentNodes(statement) {
231
- if (ts.isFunctionDeclaration(statement) && statement.body)
303
+ if (ts2.isFunctionDeclaration(statement) && statement.body)
232
304
  return [{ name: statement.name?.text ?? "default", node: statement.body }];
233
- if (!ts.isVariableStatement(statement))
305
+ if (!ts2.isVariableStatement(statement))
234
306
  return [];
235
307
  return statement.declarationList.declarations.flatMap((declaration) => {
236
- if (!ts.isIdentifier(declaration.name) || !declaration.initializer)
308
+ if (!ts2.isIdentifier(declaration.name) || !declaration.initializer)
237
309
  return [];
238
- if (!ts.isArrowFunction(declaration.initializer) && !ts.isFunctionExpression(declaration.initializer))
310
+ if (!ts2.isArrowFunction(declaration.initializer) && !ts2.isFunctionExpression(declaration.initializer))
239
311
  return [];
240
312
  return [{ name: declaration.name.text, node: declaration.initializer }];
241
313
  });
@@ -243,7 +315,7 @@ class SsrScanner {
243
315
  #getTouches(sourceFile, node) {
244
316
  const touches = [];
245
317
  const visit = (child) => {
246
- if (ts.isCallExpression(child)) {
318
+ if (ts2.isCallExpression(child)) {
247
319
  const callee = child.expression.getText(sourceFile);
248
320
  const bareName = callee.split(".").pop() ?? callee;
249
321
  if (callee === "createContext" || callee === "lazy")
@@ -251,60 +323,60 @@ class SsrScanner {
251
323
  else if (/^use[A-Z]/.test(bareName) && !SsrScanner.#serverSafeCalls.has(bareName))
252
324
  touches.push(bareName);
253
325
  }
254
- 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)))
255
327
  touches.push(child.name.getText(sourceFile));
256
- if (ts.isPropertyAccessExpression(child)) {
328
+ if (ts2.isPropertyAccessExpression(child)) {
257
329
  const root = getAccessRoot(child);
258
330
  if (root === "st")
259
331
  touches.push("st");
260
332
  else if (SsrScanner.#clientGlobals.has(root))
261
333
  touches.push(root);
262
334
  }
263
- ts.forEachChild(child, visit);
335
+ ts2.forEachChild(child, visit);
264
336
  };
265
- ts.forEachChild(node, visit);
337
+ ts2.forEachChild(node, visit);
266
338
  return touches;
267
339
  }
268
340
  #getTagNames(sourceFile, node) {
269
341
  const tags = new Set;
270
342
  const visit = (child) => {
271
- if (ts.isJsxOpeningElement(child) || ts.isJsxSelfClosingElement(child))
343
+ if (ts2.isJsxOpeningElement(child) || ts2.isJsxSelfClosingElement(child))
272
344
  tags.add(child.tagName.getText(sourceFile).split(".")[0]);
273
- ts.forEachChild(child, visit);
345
+ ts2.forEachChild(child, visit);
274
346
  };
275
- ts.forEachChild(node, visit);
347
+ ts2.forEachChild(node, visit);
276
348
  return tags;
277
349
  }
278
350
  #getMass(node) {
279
351
  let mass = 0;
280
352
  const visit = (child) => {
281
- if (ts.isJsxOpeningElement(child) || ts.isJsxSelfClosingElement(child))
353
+ if (ts2.isJsxOpeningElement(child) || ts2.isJsxSelfClosingElement(child))
282
354
  mass += 1;
283
- ts.forEachChild(child, visit);
355
+ ts2.forEachChild(child, visit);
284
356
  };
285
- ts.forEachChild(node, visit);
357
+ ts2.forEachChild(node, visit);
286
358
  return mass;
287
359
  }
288
360
  #hasUseClient(sourceFile) {
289
361
  const first = sourceFile.statements[0];
290
- if (!first || !ts.isExpressionStatement(first) || !ts.isStringLiteral(first.expression))
362
+ if (!first || !ts2.isExpressionStatement(first) || !ts2.isStringLiteral(first.expression))
291
363
  return false;
292
364
  return first.expression.text === "use client";
293
365
  }
294
366
  #hasVendorImport(sourceFile) {
295
- return sourceFile.statements.some((statement) => ts.isImportDeclaration(statement) && isVendorSpecifier(getSpecifier(statement)));
367
+ return sourceFile.statements.some((statement) => ts2.isImportDeclaration(statement) && isVendorSpecifier(getSpecifier(statement)));
296
368
  }
297
369
  #getVendorNames(sourceFile) {
298
370
  const names = new Set;
299
371
  for (const statement of sourceFile.statements) {
300
- if (!ts.isImportDeclaration(statement) || !isVendorSpecifier(getSpecifier(statement)))
372
+ if (!ts2.isImportDeclaration(statement) || !isVendorSpecifier(getSpecifier(statement)))
301
373
  continue;
302
374
  const clause = statement.importClause;
303
375
  if (clause?.name)
304
376
  names.add(clause.name.text);
305
- if (clause?.namedBindings && ts.isNamespaceImport(clause.namedBindings))
377
+ if (clause?.namedBindings && ts2.isNamespaceImport(clause.namedBindings))
306
378
  names.add(clause.namedBindings.name.text);
307
- if (clause?.namedBindings && ts.isNamedImports(clause.namedBindings))
379
+ if (clause?.namedBindings && ts2.isNamedImports(clause.namedBindings))
308
380
  for (const element of clause.namedBindings.elements)
309
381
  names.add(element.name.text);
310
382
  }
@@ -312,10 +384,10 @@ class SsrScanner {
312
384
  }
313
385
  #importsClientRuntime(sourceFile) {
314
386
  for (const statement of sourceFile.statements) {
315
- if (!ts.isImportDeclaration(statement))
387
+ if (!ts2.isImportDeclaration(statement))
316
388
  continue;
317
389
  const bindings = statement.importClause?.namedBindings;
318
- if (!bindings || !ts.isNamedImports(bindings))
390
+ if (!bindings || !ts2.isNamedImports(bindings))
319
391
  continue;
320
392
  if (bindings.elements.some((element) => SsrScanner.#clientRuntimeImports.has(element.name.text)))
321
393
  return true;
@@ -366,7 +438,7 @@ function getShare(serverMass, clientMass) {
366
438
  return total === 0 ? 1 : serverMass / total;
367
439
  }
368
440
  function getSpecifier(statement) {
369
- return ts.isStringLiteral(statement.moduleSpecifier) ? statement.moduleSpecifier.text : "";
441
+ return ts2.isStringLiteral(statement.moduleSpecifier) ? statement.moduleSpecifier.text : "";
370
442
  }
371
443
  function isVendorSpecifier(specifier) {
372
444
  if (specifier === "" || specifier.startsWith(".") || specifier.startsWith("/"))
@@ -379,9 +451,9 @@ function isVendorSpecifier(specifier) {
379
451
  }
380
452
  function getAccessRoot(node) {
381
453
  let current = node;
382
- while (ts.isPropertyAccessExpression(current))
454
+ while (ts2.isPropertyAccessExpression(current))
383
455
  current = current.expression;
384
- return ts.isIdentifier(current) ? current.text : "";
456
+ return ts2.isIdentifier(current) ? current.text : "";
385
457
  }
386
458
 
387
459
  // pkgs/@akanjs/devkit/qualityScanner.ts
@@ -405,7 +477,8 @@ var SUGGESTED_RULES = [
405
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.",
406
478
  "Use domain module folders consistently: lib/<model> for database modules, lib/_<service> for service modules, and lib/__scalar/<scalar> for scalar modules.",
407
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.",
408
- "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.",
409
482
  "Avoid large mixed-purpose class files; class export files should import helpers from neighboring utility files instead of declaring them inline."
410
483
  ];
411
484
  var CONVENTION_SUFFIXES = [
@@ -428,10 +501,14 @@ var RULE_FIXES = {
428
501
  "akan.file.window-augmentation": "Move the Window augmentation into an approved browser integration file and keep it isolated.",
429
502
  "akan.file.prototype-mutation": "Avoid prototype mutation, or isolate it in an approved low-level integration file.",
430
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.",
431
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`.",
432
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.",
433
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).",
434
- "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.",
435
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.",
436
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.',
437
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.",
@@ -459,6 +536,7 @@ class AkanQualityScanner {
459
536
  ...sourceFiles.flatMap((sourceFile) => this.#scanConventionQuality(sourceFile)),
460
537
  ...sourceFiles.flatMap((sourceFile) => this.#scanLayoutQuality(sourceFile)),
461
538
  ...abstractFiles.flatMap((abstractFile) => this.#scanAbstractQuality(abstractFile)),
539
+ ...new FormSetterScanner().scan(sourceFiles),
462
540
  ...ssr.warnings
463
541
  ];
464
542
  return {
@@ -511,7 +589,7 @@ class AkanQualityScanner {
511
589
  file,
512
590
  absolutePath,
513
591
  content,
514
- sourceFile: ts2.createSourceFile(file, content, ts2.ScriptTarget.Latest, true, getScriptKind(file))
592
+ sourceFile: ts3.createSourceFile(file, content, ts3.ScriptTarget.Latest, true, getScriptKind(file))
515
593
  };
516
594
  }
517
595
  async#readTextFile(workspaceRoot, file) {
@@ -670,25 +748,30 @@ class AkanQualityScanner {
670
748
  ];
671
749
  }
672
750
  #scanLayoutQuality(sourceFile) {
673
- const segments = sourceFile.file.split("/");
674
751
  const warnings = [];
675
- if (segments[0] === "apps" && segments.length === 3 && !appRootAllowedFiles.has(segments[2])) {
676
- warnings.push({
677
- rule: "akan.layout.app-root-file",
678
- scope: "layout",
679
- severity: "warning",
680
- file: sourceFile.file,
681
- message: `Unexpected app root file "${segments[2]}". Keep application code in conventional app folders.`
682
- });
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
+ }
683
766
  }
684
- const libRootFile = getLibRootFile(sourceFile.file);
685
- if (libRootFile && !libFacetRootAllowedFiles.has(libRootFile)) {
767
+ const libFacetFile = getLibFacetRootFile(sourceFile.file);
768
+ if (libFacetFile && !isAllowedLibFacetRootFile(libFacetFile)) {
686
769
  warnings.push({
687
- rule: "akan.layout.lib-root-file",
770
+ rule: "akan.layout.lib-facet-file",
688
771
  scope: "layout",
689
772
  severity: "warning",
690
773
  file: sourceFile.file,
691
- 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.`
692
775
  });
693
776
  }
694
777
  const moduleUiWarning = getModuleUiWarning(sourceFile.file);
@@ -758,7 +841,7 @@ function getExportedFunctionLikes(sourceFile) {
758
841
  const declarations = [];
759
842
  const nameExempt = isPageRouteFile(sourceFile.file) || isUiComponentFile(sourceFile.file);
760
843
  for (const statement of sourceFile.sourceFile.statements) {
761
- if (ts2.isFunctionDeclaration(statement) && statement.name && isExported(statement)) {
844
+ if (ts3.isFunctionDeclaration(statement) && statement.name && isExported(statement)) {
762
845
  declarations.push({
763
846
  name: statement.name.text,
764
847
  kind: "function",
@@ -768,7 +851,7 @@ function getExportedFunctionLikes(sourceFile) {
768
851
  duplicateNameExempt: nameExempt || isConventionDuplicateNameExempt(sourceFile.file, false)
769
852
  });
770
853
  }
771
- if (ts2.isClassDeclaration(statement) && statement.name && isExported(statement)) {
854
+ if (ts3.isClassDeclaration(statement) && statement.name && isExported(statement)) {
772
855
  declarations.push({
773
856
  name: statement.name.text,
774
857
  kind: "class",
@@ -778,9 +861,9 @@ function getExportedFunctionLikes(sourceFile) {
778
861
  duplicateNameExempt: nameExempt || isConventionDuplicateNameExempt(sourceFile.file, isEnumClassStatement(sourceFile.sourceFile, statement))
779
862
  });
780
863
  }
781
- if (ts2.isVariableStatement(statement) && isExported(statement)) {
864
+ if (ts3.isVariableStatement(statement) && isExported(statement)) {
782
865
  for (const declaration of statement.declarationList.declarations) {
783
- if (!ts2.isIdentifier(declaration.name) || !isFunctionLikeInitializer(declaration.initializer))
866
+ if (!ts3.isIdentifier(declaration.name) || !isFunctionLikeInitializer(declaration.initializer))
784
867
  continue;
785
868
  declarations.push({
786
869
  name: declaration.name.text,
@@ -819,14 +902,14 @@ function isInLibModule(file) {
819
902
  return (segments[0] === "apps" || segments[0] === "libs") && segments.includes("lib");
820
903
  }
821
904
  function isEnumClassStatement(sourceFile, statement) {
822
- if (!ts2.isClassDeclaration(statement))
905
+ if (!ts3.isClassDeclaration(statement))
823
906
  return false;
824
- const heritageClause = statement.heritageClauses?.find((clause) => clause.token === ts2.SyntaxKind.ExtendsKeyword);
907
+ const heritageClause = statement.heritageClauses?.find((clause) => clause.token === ts3.SyntaxKind.ExtendsKeyword);
825
908
  const expression = heritageClause?.types[0]?.expression;
826
909
  return !!expression && expression.getText(sourceFile).startsWith("enumOf(");
827
910
  }
828
911
  function getExportedClassNames(sourceFile) {
829
- 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);
830
913
  }
831
914
  function isComponentDeclarationFile(file) {
832
915
  if (!file.endsWith(".tsx"))
@@ -842,7 +925,7 @@ function isComponentDeclarationFile(file) {
842
925
  function getComponentFileDeclarations(sourceFile) {
843
926
  const reExportedNames = new Set;
844
927
  for (const statement of sourceFile.statements) {
845
- if (ts2.isExportDeclaration(statement) && statement.exportClause && ts2.isNamedExports(statement.exportClause)) {
928
+ if (ts3.isExportDeclaration(statement) && statement.exportClause && ts3.isNamedExports(statement.exportClause)) {
846
929
  for (const element of statement.exportClause.elements)
847
930
  reExportedNames.add((element.propertyName ?? element.name).text);
848
931
  }
@@ -852,19 +935,19 @@ function getComponentFileDeclarations(sourceFile) {
852
935
  const isDefaultExport = isDefaultExportStatement(statement);
853
936
  const inlineExported = isExported(statement);
854
937
  const add = (name, kind, line) => declarations.push({ name, kind, line, exported: inlineExported || reExportedNames.has(name), isDefaultExport });
855
- if (ts2.isInterfaceDeclaration(statement))
938
+ if (ts3.isInterfaceDeclaration(statement))
856
939
  add(statement.name.text, "interface", getLine(sourceFile, statement));
857
- else if (ts2.isTypeAliasDeclaration(statement))
940
+ else if (ts3.isTypeAliasDeclaration(statement))
858
941
  add(statement.name.text, "type", getLine(sourceFile, statement));
859
- else if (ts2.isEnumDeclaration(statement))
942
+ else if (ts3.isEnumDeclaration(statement))
860
943
  add(statement.name.text, "enum", getLine(sourceFile, statement));
861
- else if (ts2.isFunctionDeclaration(statement) && statement.name)
944
+ else if (ts3.isFunctionDeclaration(statement) && statement.name)
862
945
  add(statement.name.text, "function", getLine(sourceFile, statement));
863
- else if (ts2.isClassDeclaration(statement) && statement.name)
946
+ else if (ts3.isClassDeclaration(statement) && statement.name)
864
947
  add(statement.name.text, "class", getLine(sourceFile, statement));
865
- else if (ts2.isVariableStatement(statement)) {
948
+ else if (ts3.isVariableStatement(statement)) {
866
949
  for (const declaration of statement.declarationList.declarations) {
867
- if (!ts2.isIdentifier(declaration.name))
950
+ if (!ts3.isIdentifier(declaration.name))
868
951
  continue;
869
952
  const kind = isFunctionLikeInitializer(declaration.initializer) ? "function" : "variable";
870
953
  add(declaration.name.text, kind, getLine(sourceFile, declaration));
@@ -876,15 +959,15 @@ function getComponentFileDeclarations(sourceFile) {
876
959
  function getCompoundComponentNames(sourceFile) {
877
960
  const names = new Set;
878
961
  for (const statement of sourceFile.statements) {
879
- if (!ts2.isExpressionStatement(statement))
962
+ if (!ts3.isExpressionStatement(statement))
880
963
  continue;
881
964
  const { expression } = statement;
882
- if (!ts2.isBinaryExpression(expression) || expression.operatorToken.kind !== ts2.SyntaxKind.EqualsToken)
965
+ if (!ts3.isBinaryExpression(expression) || expression.operatorToken.kind !== ts3.SyntaxKind.EqualsToken)
883
966
  continue;
884
- if (!ts2.isPropertyAccessExpression(expression.left) || !isPascalCaseName(expression.left.name.text))
967
+ if (!ts3.isPropertyAccessExpression(expression.left) || !isPascalCaseName(expression.left.name.text))
885
968
  continue;
886
969
  names.add(expression.left.name.text);
887
- if (ts2.isIdentifier(expression.right) && isPascalCaseName(expression.right.text))
970
+ if (ts3.isIdentifier(expression.right) && isPascalCaseName(expression.right.text))
888
971
  names.add(expression.right.text);
889
972
  }
890
973
  return names;
@@ -904,9 +987,9 @@ function isPascalCaseName(name) {
904
987
  return /^[A-Z]/.test(name) && !/^[A-Z0-9_]+$/.test(name);
905
988
  }
906
989
  function isDefaultExportStatement(statement) {
907
- if (ts2.isExportAssignment(statement))
990
+ if (ts3.isExportAssignment(statement))
908
991
  return !statement.isExportEquals;
909
- return !!(ts2.getCombinedModifierFlags(statement) & ts2.ModifierFlags.Default);
992
+ return !!(ts3.getCombinedModifierFlags(statement) & ts3.ModifierFlags.Default);
910
993
  }
911
994
  function getPlaceholderExportWarnings(sourceFile) {
912
995
  if (!sourceFile.file.endsWith("/index.ts") && !sourceFile.file.endsWith("/index.tsx"))
@@ -936,7 +1019,7 @@ function getDictionaryTextWarnings(sourceFile) {
936
1019
  function getGlobalMutationWarnings(sourceFile) {
937
1020
  const warnings = [];
938
1021
  for (const statement of sourceFile.sourceFile.statements) {
939
- if (ts2.isModuleDeclaration(statement) && statement.name.getText(sourceFile.sourceFile) === "global") {
1022
+ if (ts3.isModuleDeclaration(statement) && statement.name.getText(sourceFile.sourceFile) === "global") {
940
1023
  warnings.push({
941
1024
  rule: "akan.file.global-declaration",
942
1025
  scope: "file",
@@ -946,7 +1029,7 @@ function getGlobalMutationWarnings(sourceFile) {
946
1029
  message: "Global declarations require an explicit low-level integration allowlist."
947
1030
  });
948
1031
  }
949
- if (ts2.isInterfaceDeclaration(statement) && statement.name.text === "Window") {
1032
+ if (ts3.isInterfaceDeclaration(statement) && statement.name.text === "Window") {
950
1033
  warnings.push({
951
1034
  rule: "akan.file.window-augmentation",
952
1035
  scope: "file",
@@ -956,7 +1039,7 @@ function getGlobalMutationWarnings(sourceFile) {
956
1039
  message: "Window augmentation should be isolated to approved browser integration files."
957
1040
  });
958
1041
  }
959
- if (ts2.isExpressionStatement(statement) && statement.expression.getText(sourceFile.sourceFile).includes(".prototype.")) {
1042
+ if (ts3.isExpressionStatement(statement) && statement.expression.getText(sourceFile.sourceFile).includes(".prototype.")) {
960
1043
  warnings.push({
961
1044
  rule: "akan.file.prototype-mutation",
962
1045
  scope: "file",
@@ -974,23 +1057,23 @@ function getTopLevelDeclarations(sourceFile) {
974
1057
  }
975
1058
  function getTopLevelDeclaration(sourceFile, statement) {
976
1059
  const line = getLine(sourceFile, statement);
977
- if (ts2.isClassDeclaration(statement) && statement.name) {
1060
+ if (ts3.isClassDeclaration(statement) && statement.name) {
978
1061
  return [{ name: statement.name.text, kind: "class", line, exported: isExported(statement), node: statement }];
979
1062
  }
980
- if (ts2.isFunctionDeclaration(statement) && statement.name) {
1063
+ if (ts3.isFunctionDeclaration(statement) && statement.name) {
981
1064
  return [{ name: statement.name.text, kind: "function", line, exported: isExported(statement), node: statement }];
982
1065
  }
983
- if (ts2.isInterfaceDeclaration(statement)) {
1066
+ if (ts3.isInterfaceDeclaration(statement)) {
984
1067
  return [{ name: statement.name.text, kind: "interface", line, exported: isExported(statement), node: statement }];
985
1068
  }
986
- if (ts2.isTypeAliasDeclaration(statement)) {
1069
+ if (ts3.isTypeAliasDeclaration(statement)) {
987
1070
  return [{ name: statement.name.text, kind: "type", line, exported: isExported(statement), node: statement }];
988
1071
  }
989
- if (ts2.isEnumDeclaration(statement)) {
1072
+ if (ts3.isEnumDeclaration(statement)) {
990
1073
  return [{ name: statement.name.text, kind: "enum", line, exported: isExported(statement), node: statement }];
991
1074
  }
992
- if (ts2.isVariableStatement(statement)) {
993
- 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) => ({
994
1077
  name: declaration.name.text,
995
1078
  kind: "variable",
996
1079
  line: getLine(sourceFile, declaration),
@@ -998,7 +1081,7 @@ function getTopLevelDeclaration(sourceFile, statement) {
998
1081
  node: statement
999
1082
  }));
1000
1083
  }
1001
- if (ts2.isExportDeclaration(statement)) {
1084
+ if (ts3.isExportDeclaration(statement)) {
1002
1085
  return [{ name: "export declaration", kind: "export", line, exported: true, node: statement }];
1003
1086
  }
1004
1087
  return [];
@@ -1023,9 +1106,9 @@ function isAllowedConstantDeclaration(modelName, declaration) {
1023
1106
  return false;
1024
1107
  if ([`${modelName}Input`, `${modelName}Object`, modelName, `Light${modelName}`, `${modelName}Insight`].includes(declaration.name))
1025
1108
  return true;
1026
- if (!ts2.isClassDeclaration(declaration.node))
1109
+ if (!ts3.isClassDeclaration(declaration.node))
1027
1110
  return false;
1028
- 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);
1029
1112
  const expression = heritageClause?.types[0]?.expression;
1030
1113
  return !!expression && expression.getText().startsWith("enumOf(");
1031
1114
  }
@@ -1042,7 +1125,19 @@ function getConventionDescription(suffix, modelName) {
1042
1125
  return `${modelName}Internal, ${modelName}Slice, or ${modelName}Endpoint`;
1043
1126
  return `${modelName}Store`;
1044
1127
  }
1045
- 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) {
1046
1141
  const segments = file.split("/");
1047
1142
  if ((segments[0] === "libs" || segments[0] === "apps") && segments.length === 4 && segments[2] === "lib")
1048
1143
  return segments[3];
@@ -1091,13 +1186,13 @@ function getModuleInfo(file) {
1091
1186
  return { moduleName, fileName, kind: "database" };
1092
1187
  }
1093
1188
  function isExportedConst(declaration) {
1094
- 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;
1095
1190
  }
1096
1191
  function isExported(node) {
1097
- return !!ts2.getCombinedModifierFlags(node) && !!(ts2.getCombinedModifierFlags(node) & ts2.ModifierFlags.Export);
1192
+ return !!ts3.getCombinedModifierFlags(node) && !!(ts3.getCombinedModifierFlags(node) & ts3.ModifierFlags.Export);
1098
1193
  }
1099
1194
  function isFunctionLikeInitializer(node) {
1100
- return !!node && (ts2.isArrowFunction(node) || ts2.isFunctionExpression(node));
1195
+ return !!node && (ts3.isArrowFunction(node) || ts3.isFunctionExpression(node));
1101
1196
  }
1102
1197
  function getBodyFingerprint(sourceFile, node) {
1103
1198
  if (!node)
@@ -1119,7 +1214,7 @@ async function isDirectory(absolutePath) {
1119
1214
  }
1120
1215
  }
1121
1216
  function getScriptKind(file) {
1122
- return file.endsWith(".tsx") ? ts2.ScriptKind.TSX : ts2.ScriptKind.TS;
1217
+ return file.endsWith(".tsx") ? ts3.ScriptKind.TSX : ts3.ScriptKind.TS;
1123
1218
  }
1124
1219
  function getLine(sourceFile, node) {
1125
1220
  return sourceFile.getLineAndCharacterOfPosition(node.getStart(sourceFile)).line + 1;
@@ -4,9 +4,9 @@ import {
4
4
  findInlineRecipeDuplicates,
5
5
  scanRecipes
6
6
  } from "./index-xys926f2.js";
7
- import"./index-r24hmh0q.js";
7
+ import"./index-yb2wsc7f.js";
8
8
  export {
9
- scanRecipes,
9
+ collectRecipeSources,
10
10
  findInlineRecipeDuplicates,
11
- collectRecipeSources
11
+ scanRecipes
12
12
  };