@akanjs/cli 3.0.0-beta.19 → 3.0.0-beta.20

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 (95) hide show
  1. package/.build-stamp +1 -1
  2. package/{CodeAgent-6cm97kay.js → CodeAgent-awv258wb.js} +2 -2
  3. package/{CodeTui-za4jsm41.js → CodeTui-1nqh35ez.js} +20 -22
  4. package/TunnelShare-scf4e4pd.js +9 -0
  5. package/{agent.command-e0wpbbyg.js → agent.command-1s9ag7sb.js} +8 -9
  6. package/akanCode.js +4 -4
  7. package/{application.command-8wyxqc8q.js → application.command-49hwy6va.js} +25 -56
  8. package/{applicationBuildRunner-n9gzaxd0.js → applicationBuildRunner-ybyp97pv.js} +33 -59
  9. package/{applicationReleasePackager-vpggfqgq.js → applicationReleasePackager-esnrphw8.js} +40 -60
  10. package/buildBatch.proc.js +19 -24
  11. package/{capacitorApp-q3z0qht0.js → capacitorApp-1synz4en.js} +3 -4
  12. package/{cloud.command-4zwa5dsx.js → cloud.command-d7rqtzt9.js} +16 -26
  13. package/cloud.script-bkbpcr96.js +19 -0
  14. package/{code.command-468c9d7s.js → code.command-nn48xzej.js} +22 -37
  15. package/context.command-s3mcmvpa.js +48 -0
  16. package/context.runner-k99nb5pk.js +21 -0
  17. package/{dependencyScanner-0z2h2tks.js → dependencyScanner-ye2e6pdz.js} +2 -2
  18. package/{devTui-ffjsarqa.js → devTui-q7k7bakd.js} +5 -9
  19. package/fileSys-5ngz93ga.js +9 -0
  20. package/{guideline.command-vhw13pqr.js → guideline.command-sct4qnt5.js} +4 -5
  21. package/guidelines/conventions/conventions.instruction.md +5 -1
  22. package/guidelines/mcpRule/mcpRule.instruction.md +7 -5
  23. package/incrementalBuilder.proc.js +30 -48
  24. package/{index-s5rhm04y.js → index-0bq14vr1.js} +1 -1
  25. package/{index-10dkrrww.js → index-13ffe9wp.js} +4 -4
  26. package/{index-srmay5jw.js → index-22d57v4c.js} +84 -67
  27. package/{index-53176s7j.js → index-31yr441d.js} +10 -17
  28. package/index-3hx1z96c.js +87 -0
  29. package/{index-4m929vvn.js → index-3sq321ak.js} +10 -18
  30. package/{index-bhtmj233.js → index-3t7g9e4r.js} +41 -78
  31. package/{index-q0y2jq9j.js → index-5b6tavv3.js} +6 -3
  32. package/{index-47pj3aee.js → index-5e0n4hqx.js} +11 -12
  33. package/{index-7ep8qtvp.js → index-5pc1qrbv.js} +5 -8
  34. package/{index-esk82rvk.js → index-8mna0947.js} +34 -71
  35. package/{index-sq2dzkzm.js → index-8sf7wmdr.js} +6 -6
  36. package/{index-crmeqnmf.js → index-9d9bgcx4.js} +283 -722
  37. package/{index-bwf965h6.js → index-ahemdr5n.js} +6 -6
  38. package/{index-ab0c38ea.js → index-as7xmyz8.js} +20 -20
  39. package/{index-bc88j1ry.js → index-axzjaqve.js} +94 -142
  40. package/{index-3d9y066c.js → index-bjwxs904.js} +9 -8
  41. package/{index-667w0yah.js → index-c6eggbee.js} +2 -5
  42. package/{index-a1w6934z.js → index-ddxvdd1p.js} +169 -305
  43. package/{index-92zztqy1.js → index-dy0jx9ay.js} +2 -17
  44. package/{index-22xtgmhn.js → index-dzf3r49a.js} +1 -1
  45. package/{index-89hy31kf.js → index-fgq75haa.js} +172 -287
  46. package/{index-by5gpp8x.js → index-hj6w4ka1.js} +2 -1
  47. package/{index-hwkygn9s.js → index-khpfpcsd.js} +302 -33
  48. package/{index-vqx8bdxz.js → index-mxav6j3c.js} +78 -231
  49. package/{index-2446k7b4.js → index-myhc3c1j.js} +512 -588
  50. package/{index-3jrp517k.js → index-nv5976x3.js} +45 -128
  51. package/{index-zqgj2rt2.js → index-przybvy5.js} +69 -147
  52. package/{index-n3cv0tm4.js → index-smme4r47.js} +1 -1
  53. package/{index-qdgzsnhg.js → index-w2zdcw3k.js} +110 -231
  54. package/{index-14zrtgfa.js → index-wa5jbxj1.js} +6 -39
  55. package/{index-bftkz6cp.js → index-ww4z397j.js} +198 -555
  56. package/{index-kwaq3n9n.js → index-xqjwd384.js} +4 -7
  57. package/{index-txr7dj2g.js → index-xqpzbab5.js} +106 -240
  58. package/{index-e4v6vhxq.js → index-xsjh6nzh.js} +178 -165
  59. package/{index-18zybgkc.js → index-ygn77a5w.js} +47 -99
  60. package/{index-9d6nvkz2.js → index-ztp3e73t.js} +79 -35
  61. package/index.js +23 -24
  62. package/{library.command-cqr175dy.js → library.command-1gx04jgn.js} +4 -5
  63. package/{localRegistry.command-49z55qqz.js → localRegistry.command-zndxj1q6.js} +21 -42
  64. package/{module.command-9p8mm3b0.js → module.command-3waddk95.js} +6 -7
  65. package/{package.command-wxyz909t.js → package.command-zrc7br1y.js} +5 -6
  66. package/package.json +2 -2
  67. package/{page.command-gsrsfqqs.js → page.command-vpjgardh.js} +4 -5
  68. package/{primitive.command-ns1mptje.js → primitive.command-sz84tk4t.js} +9 -20
  69. package/{prompter-1f8d9pcc.js → prompter-ptaa3nk0.js} +2 -2
  70. package/{quality.command-e76k2q9k.js → quality.command-g869hafs.js} +129 -363
  71. package/{recipeScanner-fpagtgc1.js → recipeScanner-4b69ybe0.js} +1 -1
  72. package/{repair.command-pkrsm3cw.js → repair.command-chsnjvvv.js} +11 -35
  73. package/{scalar.command-0cpt3fc8.js → scalar.command-2v21newx.js} +5 -6
  74. package/skills/akan-code/SKILL.md +12 -4
  75. package/{subspace.command-fktpwp0g.js → subspace.command-ckcbwfp1.js} +39 -58
  76. package/templates/appSample/srvkit/{AuthGuard.ts → guards.ts} +3 -3
  77. package/templates/appSample/srvkit/{SessionInternalArg.ts → internalArgs.ts} +3 -3
  78. package/templates/client.ts +7 -30
  79. package/templates/lib/__lib/lib.dictionary.ts +0 -2
  80. package/templates/lib/__lib/lib.document.ts +0 -2
  81. package/templates/lib/sig.ts +8 -30
  82. package/templates/server.ts +0 -2
  83. package/{tunnel.command-d5jy9gk8.js → tunnel.command-kd2yr4fx.js} +5 -6
  84. package/{typeChecker-3xgrzc2v.js → typeChecker-px49mezc.js} +1 -1
  85. package/typecheck.proc.js +1 -1
  86. package/workflow.command-44252cqv.js +79 -0
  87. package/{workspace.command-gv84eh1p.js → workspace.command-fqcztt5p.js} +72 -107
  88. package/TunnelShare-2j6s511d.js +0 -9
  89. package/cloud.script-fwx3vb34.js +0 -20
  90. package/context.command-dsnw0jby.js +0 -80
  91. package/context.runner-rs4rj15d.js +0 -22
  92. package/devTuiView-1q1mdgrv.js +0 -9
  93. package/getDirname-dr3jz1xg.js +0 -7
  94. package/index-6wwzs1bc.js +0 -7
  95. package/workflow.command-6tkcysmx.js +0 -105
@@ -1,17 +1,15 @@
1
1
  // @bun
2
2
  import {
3
3
  Workspace,
4
- runner,
5
4
  script,
6
5
  command
7
- } from "./index-qdgzsnhg.js";
8
- import"./index-q0y2jq9j.js";
6
+ } from "./index-w2zdcw3k.js";
7
+ import"./index-5b6tavv3.js";
9
8
  import {
10
9
  rootAllowedFiles,
11
10
  rootAllowedDirs,
12
11
  isAllowedLibFacetRootFile
13
- } from "./index-crmeqnmf.js";
14
- import"./index-6wwzs1bc.js";
12
+ } from "./index-9d9bgcx4.js";
15
13
 
16
14
  // pkgs/@akanjs/devkit/qualityScanner.ts
17
15
  import { createHash } from "crypto";
@@ -160,25 +158,11 @@ var STATIC_COMPONENT_MIN_MASS = 4;
160
158
  var MIXED_COMPONENT_MIN_MASS = 10;
161
159
  var MIXED_COMPONENT_MAX_TOUCHES = 2;
162
160
  var MODULE_SERVER_VIEW_MIN_CLIENT_MASS = 12;
161
+ var fileWarning = (rule, scope, file, message, line) => line === undefined ? { rule, scope, severity: "warning", file, message } : { rule, scope, severity: "warning", file, line, message };
163
162
 
164
163
  class SsrScanner {
165
164
  static #serverSafeCalls = new Set(["usePage", "getSelf", "useServer"]);
166
- static #clientGlobals = new Set([
167
- "window",
168
- "document",
169
- "navigator",
170
- "localStorage",
171
- "sessionStorage",
172
- "location",
173
- "history",
174
- "screen",
175
- "matchMedia",
176
- "IntersectionObserver",
177
- "ResizeObserver",
178
- "MutationObserver",
179
- "requestAnimationFrame",
180
- "WebSocket"
181
- ]);
165
+ static #clientGlobals = new Set("window document navigator localStorage sessionStorage location history screen matchMedia IntersectionObserver".split(" ").concat("ResizeObserver MutationObserver requestAnimationFrame WebSocket".split(" ")));
182
166
  static #clientRuntimeImports = new Set(["st", "fetch"]);
183
167
  scan(sourceFiles) {
184
168
  const componentFiles = sourceFiles.filter((sourceFile) => this.#isBalancedFile(sourceFile.file));
@@ -199,38 +183,17 @@ class SsrScanner {
199
183
  const components = this.#getComponents(sourceFile, vendorNames);
200
184
  const warnings = [];
201
185
  if (!hasVendorImport && !importsClientRuntime && this.#getTouches(sourceFile.sourceFile, sourceFile.sourceFile).length === 0 && !this.#isConventionClientFile(sourceFile.file)) {
202
- warnings.push({
203
- rule: "akan.ssr.unnecessary-use-client",
204
- scope: "ssr",
205
- severity: "warning",
206
- file: sourceFile.file,
207
- line: 1,
208
- message: `"use client" is declared but the file uses no client-only capability (hook, event handler, store, or browser API).`
209
- });
186
+ warnings.push(fileWarning("akan.ssr.unnecessary-use-client", "ssr", sourceFile.file, `"use client" is declared but the file uses no client-only capability (hook, event handler, store, or browser API).`, 1));
210
187
  }
211
188
  for (const component of components) {
212
189
  if (component.vendorTags)
213
190
  continue;
214
191
  if (component.touches.length === 0 && component.mass >= STATIC_COMPONENT_MIN_MASS) {
215
- warnings.push({
216
- rule: "akan.ssr.client-static-component",
217
- scope: "ssr",
218
- severity: "warning",
219
- file: sourceFile.file,
220
- line: component.line,
221
- message: `Client component "${component.name}" renders ${component.mass} JSX elements with no client-only capability. It is server-renderable markup sitting in the client bundle.`
222
- });
192
+ warnings.push(fileWarning("akan.ssr.client-static-component", "ssr", sourceFile.file, `Client component "${component.name}" renders ${component.mass} JSX elements with no client-only capability. It is server-renderable markup sitting in the client bundle.`, component.line));
223
193
  continue;
224
194
  }
225
195
  if (component.touches.length >= 1 && component.touches.length <= MIXED_COMPONENT_MAX_TOUCHES && component.mass >= MIXED_COMPONENT_MIN_MASS) {
226
- warnings.push({
227
- rule: "akan.ssr.client-static-markup",
228
- scope: "ssr",
229
- severity: "warning",
230
- file: sourceFile.file,
231
- line: component.line,
232
- message: `Client component "${component.name}" renders ${component.mass} JSX elements around only ${component.touches.length} client-only touch (${[...new Set(component.touches)].join(", ")}). Most of this subtree does not need the client bundle.`
233
- });
196
+ warnings.push(fileWarning("akan.ssr.client-static-markup", "ssr", sourceFile.file, `Client component "${component.name}" renders ${component.mass} JSX elements around only ${component.touches.length} client-only touch (${[...new Set(component.touches)].join(", ")}). Most of this subtree does not need the client bundle.`, component.line));
234
197
  }
235
198
  }
236
199
  warnings.push(...this.#getMountLoadWarnings(sourceFile));
@@ -250,32 +213,16 @@ class SsrScanner {
250
213
  entry.serverFiles += 1;
251
214
  modules.set(moduleDir, entry);
252
215
  }
253
- return [...modules].filter(([, entry]) => entry.serverFiles === 0 && entry.clientMass >= MODULE_SERVER_VIEW_MIN_CLIENT_MASS).map(([moduleDir, entry]) => ({
254
- rule: "akan.ssr.module-missing-server-view",
255
- scope: "ssr",
256
- severity: "warning",
257
- file: entry.line,
258
- message: `Module "${moduleDir}" renders ${entry.clientMass} JSX elements from client files only; it declares no Unit or View server component.`
259
- }));
216
+ return [...modules].filter(([, entry]) => entry.serverFiles === 0 && entry.clientMass >= MODULE_SERVER_VIEW_MIN_CLIENT_MASS).map(([moduleDir, entry]) => fileWarning("akan.ssr.module-missing-server-view", "ssr", entry.line, `Module "${moduleDir}" renders ${entry.clientMass} JSX elements from client files only; it declares no Unit or View server component.`));
260
217
  }
261
218
  #getMountLoadWarnings(sourceFile) {
262
219
  const warnings = [];
263
- const visit = (node) => {
264
- if (this.#isMountEffect(sourceFile.sourceFile, node)) {
265
- for (const load of this.#getLoadCalls(sourceFile.sourceFile, node)) {
266
- warnings.push({
267
- rule: "akan.ssr.client-mount-load",
268
- scope: "ssr",
269
- severity: "warning",
270
- file: sourceFile.file,
271
- line: this.#getLine(sourceFile.sourceFile, load.node),
272
- message: `Mount-only effect loads server data with ${load.callee}(). The route can fetch this before the first byte instead.`
273
- });
274
- }
275
- }
276
- ts2.forEachChild(node, visit);
277
- };
278
- ts2.forEachChild(sourceFile.sourceFile, visit);
220
+ SsrScanner.#walk(sourceFile.sourceFile, (node) => {
221
+ if (!this.#isMountEffect(sourceFile.sourceFile, node))
222
+ return;
223
+ for (const load of this.#getLoadCalls(sourceFile.sourceFile, node))
224
+ warnings.push(fileWarning("akan.ssr.client-mount-load", "ssr", sourceFile.file, `Mount-only effect loads server data with ${load.callee}(). The route can fetch this before the first byte instead.`, this.#getLine(sourceFile.sourceFile, load.node)));
225
+ });
279
226
  return warnings;
280
227
  }
281
228
  #isMountEffect(sourceFile, node) {
@@ -289,35 +236,23 @@ class SsrScanner {
289
236
  }
290
237
  #getLoadCalls(sourceFile, node) {
291
238
  const calls = [];
292
- const visit = (child) => {
293
- if (ts2.isCallExpression(child)) {
294
- const callee = child.expression.getText(sourceFile);
295
- if (/^fetch\.[a-z]/.test(callee) || /^st\.do\.(init|get|view|load|list|count|insight)[A-Z]/.test(callee))
296
- calls.push({ callee, node: child });
297
- }
298
- ts2.forEachChild(child, visit);
299
- };
300
- ts2.forEachChild(node, visit);
239
+ SsrScanner.#walk(node, (child) => {
240
+ if (!ts2.isCallExpression(child))
241
+ return;
242
+ const callee = child.expression.getText(sourceFile);
243
+ if (/^fetch\.[a-z]/.test(callee) || /^st\.do\.(init|get|view|load|list|count|insight)[A-Z]/.test(callee))
244
+ calls.push({ callee, node: child });
245
+ });
301
246
  return calls;
302
247
  }
303
248
  #getTemplateStateWarnings(sourceFile) {
304
249
  if (!sourceFile.file.endsWith(".Template.tsx"))
305
250
  return [];
306
251
  const warnings = [];
307
- const visit = (node) => {
308
- if (ts2.isCallExpression(node) && node.expression.getText(sourceFile.sourceFile) === "useState") {
309
- warnings.push({
310
- rule: "akan.ssr.template-client-state",
311
- scope: "ssr",
312
- severity: "warning",
313
- file: sourceFile.file,
314
- line: this.#getLine(sourceFile.sourceFile, node),
315
- message: "Template holds form state in useState. Templates are store-driven and carry no local state."
316
- });
317
- }
318
- ts2.forEachChild(node, visit);
319
- };
320
- ts2.forEachChild(sourceFile.sourceFile, visit);
252
+ SsrScanner.#walk(sourceFile.sourceFile, (node) => {
253
+ if (ts2.isCallExpression(node) && node.expression.getText(sourceFile.sourceFile) === "useState")
254
+ warnings.push(fileWarning("akan.ssr.template-client-state", "ssr", sourceFile.file, "Template holds form state in useState. Templates are store-driven and carry no local state.", this.#getLine(sourceFile.sourceFile, node)));
255
+ });
321
256
  return warnings;
322
257
  }
323
258
  #measureBalance(sourceFiles) {
@@ -372,7 +307,7 @@ class SsrScanner {
372
307
  }
373
308
  #getTouches(sourceFile, node) {
374
309
  const touches = [];
375
- const visit = (child) => {
310
+ SsrScanner.#walk(node, (child) => {
376
311
  if (ts2.isCallExpression(child)) {
377
312
  const callee = child.expression.getText(sourceFile);
378
313
  const bareName = callee.split(".").pop() ?? callee;
@@ -390,31 +325,32 @@ class SsrScanner {
390
325
  else if (SsrScanner.#clientGlobals.has(root))
391
326
  touches.push(root);
392
327
  }
393
- ts2.forEachChild(child, visit);
394
- };
395
- ts2.forEachChild(node, visit);
328
+ });
396
329
  return touches;
397
330
  }
398
331
  #getTagNames(sourceFile, node) {
399
332
  const tags = new Set;
400
- const visit = (child) => {
333
+ SsrScanner.#walk(node, (child) => {
401
334
  if (ts2.isJsxOpeningElement(child) || ts2.isJsxSelfClosingElement(child))
402
335
  tags.add(child.tagName.getText(sourceFile).split(".")[0]);
403
- ts2.forEachChild(child, visit);
404
- };
405
- ts2.forEachChild(node, visit);
336
+ });
406
337
  return tags;
407
338
  }
408
339
  #getMass(node) {
409
340
  let mass = 0;
410
- const visit = (child) => {
341
+ SsrScanner.#walk(node, (child) => {
411
342
  if (ts2.isJsxOpeningElement(child) || ts2.isJsxSelfClosingElement(child))
412
343
  mass += 1;
413
- ts2.forEachChild(child, visit);
414
- };
415
- ts2.forEachChild(node, visit);
344
+ });
416
345
  return mass;
417
346
  }
347
+ static #walk(node, visit) {
348
+ const walk = (child) => {
349
+ visit(child);
350
+ ts2.forEachChild(child, walk);
351
+ };
352
+ ts2.forEachChild(node, walk);
353
+ }
418
354
  #hasUseClient(sourceFile) {
419
355
  const first = sourceFile.statements[0];
420
356
  if (!first || !ts2.isExpressionStatement(first) || !ts2.isStringLiteral(first.expression))
@@ -688,24 +624,10 @@ class AkanQualityScanner {
688
624
  const warnings = [];
689
625
  const lineCount = sourceFile.content.split(/\r?\n/).length;
690
626
  const recommendedLineLimit = getRecommendedLineLimit(sourceFile.file);
691
- if (recommendedLineLimit && lineCount > recommendedLineLimit) {
692
- warnings.push({
693
- rule: "akan.file.recommended-max-lines",
694
- scope: "file",
695
- severity: "warning",
696
- file: sourceFile.file,
697
- message: `File has ${lineCount} lines. Recommended limit for this file type is ${recommendedLineLimit} lines.`
698
- });
699
- }
700
- if (lineCount > MAX_FILE_LINES) {
701
- warnings.push({
702
- rule: "akan.file.max-lines",
703
- scope: "file",
704
- severity: "warning",
705
- file: sourceFile.file,
706
- message: `File has ${lineCount} lines. Keep single files under ${MAX_FILE_LINES} lines.`
707
- });
708
- }
627
+ if (recommendedLineLimit && lineCount > recommendedLineLimit)
628
+ warnings.push(fileWarning("akan.file.recommended-max-lines", "file", sourceFile.file, `File has ${lineCount} lines. Recommended limit for this file type is ${recommendedLineLimit} lines.`));
629
+ if (lineCount > MAX_FILE_LINES)
630
+ warnings.push(fileWarning("akan.file.max-lines", "file", sourceFile.file, `File has ${lineCount} lines. Keep single files under ${MAX_FILE_LINES} lines.`));
709
631
  warnings.push(...getBangCommentWarnings(sourceFile));
710
632
  warnings.push(...getPlaceholderExportWarnings(sourceFile));
711
633
  warnings.push(...getDictionaryTextWarnings(sourceFile));
@@ -719,14 +641,7 @@ class AkanQualityScanner {
719
641
  continue;
720
642
  if (declaration.kind === "interface" && allowedInterfaceNames.has(declaration.name))
721
643
  continue;
722
- warnings.push({
723
- rule: "akan.file.class-export-global-declaration",
724
- scope: "file",
725
- severity: "warning",
726
- file: sourceFile.file,
727
- line: declaration.line,
728
- message: `Class export files should not declare top-level ${declaration.kind} "${declaration.name}". Move helpers to another file and import them.`
729
- });
644
+ warnings.push(fileWarning("akan.file.class-export-global-declaration", "file", sourceFile.file, `Class export files should not declare top-level ${declaration.kind} "${declaration.name}". Move helpers to another file and import them.`, declaration.line));
730
645
  }
731
646
  return warnings;
732
647
  }
@@ -748,28 +663,13 @@ class AkanQualityScanner {
748
663
  if (declaration.exported) {
749
664
  if (isAllowedComponentExport(declaration, isPage))
750
665
  continue;
751
- warnings.push({
752
- rule: "akan.file.component-export",
753
- scope: "file",
754
- severity: "warning",
755
- file: sourceFile.file,
756
- line: declaration.line,
757
- message: `Component file exports ${declaration.kind} "${declaration.name}", which is not a PascalCase component${isPage ? " or reserved route export" : ""}.`
758
- });
666
+ warnings.push(fileWarning("akan.file.component-export", "file", sourceFile.file, `Component file exports ${declaration.kind} "${declaration.name}", which is not a PascalCase component${isPage ? " or reserved route export" : ""}.`, declaration.line));
759
667
  continue;
760
668
  }
761
669
  if (isComponentValueKind(declaration.kind) && compoundComponentNames.has(declaration.name))
762
670
  continue;
763
- if (isRestrictedInternalKind(declaration.kind)) {
764
- warnings.push({
765
- rule: "akan.file.component-internal-declaration",
766
- scope: "file",
767
- severity: "warning",
768
- file: sourceFile.file,
769
- line: declaration.line,
770
- message: `Component file declares non-exported ${declaration.kind} "${declaration.name}". Only "interface <Component>Props" may stay internal.`
771
- });
772
- }
671
+ if (isRestrictedInternalKind(declaration.kind))
672
+ warnings.push(fileWarning("akan.file.component-internal-declaration", "file", sourceFile.file, `Component file declares non-exported ${declaration.kind} "${declaration.name}". Only "interface <Component>Props" may stay internal.`, declaration.line));
773
673
  }
774
674
  return warnings;
775
675
  }
@@ -778,33 +678,14 @@ class AkanQualityScanner {
778
678
  if (!suffix)
779
679
  return [];
780
680
  const modelName = toPascalCase(path2.basename(sourceFile.file, suffix));
781
- const warnings = [];
782
- for (const declaration of getTopLevelDeclarations(sourceFile)) {
783
- if (isAllowedConventionDeclaration(suffix, modelName, declaration))
784
- continue;
785
- warnings.push({
786
- rule: `akan.convention${suffix.replace(".ts", "")}`,
787
- scope: "convention",
788
- severity: "warning",
789
- file: sourceFile.file,
790
- line: declaration.line,
791
- message: `${path2.basename(sourceFile.file)} should not declare top-level ${declaration.kind} "${declaration.name}". Allowed declarations: ${getConventionDescription(suffix, modelName)}.`
792
- });
793
- }
794
- return warnings;
681
+ return getTopLevelDeclarations(sourceFile).filter((declaration) => !isAllowedConventionDeclaration(suffix, modelName, declaration)).map((declaration) => fileWarning(`akan.convention${suffix.replace(".ts", "")}`, "convention", sourceFile.file, `${path2.basename(sourceFile.file)} should not declare top-level ${declaration.kind} "${declaration.name}". Allowed declarations: ${getConventionDescription(suffix, modelName)}.`, declaration.line));
795
682
  }
796
683
  #scanAbstractQuality({ file, content }) {
797
684
  const lineCount = AbstractDoc.lineCountOf(content);
798
685
  if (lineCount <= AbstractDoc.maxLines)
799
686
  return [];
800
687
  return [
801
- {
802
- rule: "akan.file.abstract-max-lines",
803
- scope: "file",
804
- severity: "warning",
805
- file,
806
- message: `Abstract has ${lineCount} lines. Keep abstracts under ${AbstractDoc.maxLines} lines and compact them periodically.`
807
- }
688
+ fileWarning("akan.file.abstract-max-lines", "file", file, `Abstract has ${lineCount} lines. Keep abstracts under ${AbstractDoc.maxLines} lines and compact them periodically.`)
808
689
  ];
809
690
  }
810
691
  #scanLayoutQuality(sourceFile) {
@@ -814,26 +695,12 @@ class AkanQualityScanner {
814
695
  const { type, name, isDir } = rootEntry;
815
696
  const allowed = isDir ? rootAllowedDirs[type].has(name) : rootAllowedFiles[type].has(name);
816
697
  const kind = isDir ? "folder" : "file";
817
- if (!allowed) {
818
- warnings.push({
819
- rule: `akan.layout.${type}-root-${kind}`,
820
- scope: "layout",
821
- severity: "warning",
822
- file: sourceFile.file,
823
- message: `Unexpected ${type} root ${kind} "${name}". Keep ${type} code in conventional ${type} folders.`
824
- });
825
- }
698
+ if (!allowed)
699
+ warnings.push(fileWarning(`akan.layout.${type}-root-${kind}`, "layout", sourceFile.file, `Unexpected ${type} root ${kind} "${name}". Keep ${type} code in conventional ${type} folders.`));
826
700
  }
827
701
  const libFacetFile = getLibFacetRootFile(sourceFile.file);
828
- if (libFacetFile && !isAllowedLibFacetRootFile(libFacetFile)) {
829
- warnings.push({
830
- rule: "akan.layout.lib-facet-file",
831
- scope: "layout",
832
- severity: "warning",
833
- file: sourceFile.file,
834
- message: `Unexpected lib facet root file "${libFacetFile}". Keep direct lib/ root files limited to generated support facets.`
835
- });
836
- }
702
+ if (libFacetFile && !isAllowedLibFacetRootFile(libFacetFile))
703
+ warnings.push(fileWarning("akan.layout.lib-facet-file", "layout", sourceFile.file, `Unexpected lib facet root file "${libFacetFile}". Keep direct lib/ root files limited to generated support facets.`));
837
704
  const moduleUiWarning = getModuleUiWarning(sourceFile.file);
838
705
  if (moduleUiWarning)
839
706
  warnings.push(moduleUiWarning);
@@ -900,67 +767,48 @@ function formatQualityLocation(file, line) {
900
767
  function getExportedFunctionLikes(sourceFile) {
901
768
  const declarations = [];
902
769
  const nameExempt = isPageRouteFile(sourceFile.file) || isUiComponentFile(sourceFile.file);
770
+ const add = (name, kind, node, body, isEnumClass = false) => declarations.push({
771
+ name,
772
+ kind,
773
+ file: sourceFile.file,
774
+ line: getLine(sourceFile.sourceFile, node),
775
+ bodyFingerprint: getBodyFingerprint(sourceFile.sourceFile, body),
776
+ duplicateNameExempt: nameExempt || isConventionDuplicateNameExempt(sourceFile.file, isEnumClass)
777
+ });
903
778
  for (const statement of sourceFile.sourceFile.statements) {
904
- if (ts3.isFunctionDeclaration(statement) && statement.name && isExported(statement)) {
905
- declarations.push({
906
- name: statement.name.text,
907
- kind: "function",
908
- file: sourceFile.file,
909
- line: getLine(sourceFile.sourceFile, statement),
910
- bodyFingerprint: getBodyFingerprint(sourceFile.sourceFile, statement.body),
911
- duplicateNameExempt: nameExempt || isConventionDuplicateNameExempt(sourceFile.file, false)
912
- });
913
- }
914
- if (ts3.isClassDeclaration(statement) && statement.name && isExported(statement)) {
915
- declarations.push({
916
- name: statement.name.text,
917
- kind: "class",
918
- file: sourceFile.file,
919
- line: getLine(sourceFile.sourceFile, statement),
920
- bodyFingerprint: getBodyFingerprint(sourceFile.sourceFile, statement),
921
- duplicateNameExempt: nameExempt || isConventionDuplicateNameExempt(sourceFile.file, isEnumClassStatement(sourceFile.sourceFile, statement))
922
- });
923
- }
779
+ if (ts3.isFunctionDeclaration(statement) && statement.name && isExported(statement))
780
+ add(statement.name.text, "function", statement, statement.body);
781
+ if (ts3.isClassDeclaration(statement) && statement.name && isExported(statement))
782
+ add(statement.name.text, "class", statement, statement, isEnumClassStatement(sourceFile.sourceFile, statement));
924
783
  if (ts3.isVariableStatement(statement) && isExported(statement)) {
925
784
  for (const declaration of statement.declarationList.declarations) {
926
785
  if (!ts3.isIdentifier(declaration.name) || !isFunctionLikeInitializer(declaration.initializer))
927
786
  continue;
928
- declarations.push({
929
- name: declaration.name.text,
930
- kind: "function-variable",
931
- file: sourceFile.file,
932
- line: getLine(sourceFile.sourceFile, declaration),
933
- bodyFingerprint: getBodyFingerprint(sourceFile.sourceFile, declaration.initializer),
934
- duplicateNameExempt: nameExempt || isConventionDuplicateNameExempt(sourceFile.file, false)
935
- });
787
+ add(declaration.name.text, "function-variable", declaration, declaration.initializer);
936
788
  }
937
789
  }
938
790
  }
939
791
  return declarations;
940
792
  }
941
- function isPageRouteFile(file) {
793
+ function sysSegments(file) {
942
794
  const segments = file.split("/");
943
- return (segments[0] === "apps" || segments[0] === "libs") && segments[2] === "page";
795
+ return segments[0] === "apps" || segments[0] === "libs" ? segments : null;
796
+ }
797
+ function isPageRouteFile(file) {
798
+ return sysSegments(file)?.[2] === "page";
944
799
  }
945
800
  function isUiComponentFile(file) {
946
- const segments = file.split("/");
947
- return (segments[0] === "apps" || segments[0] === "libs") && segments[2] === "ui";
801
+ return sysSegments(file)?.[2] === "ui";
948
802
  }
949
803
  function isConventionDuplicateNameExempt(file, isEnumClass) {
950
- if (!isInLibModule(file))
804
+ if (!sysSegments(file)?.includes("lib"))
951
805
  return false;
952
- if (file.endsWith(".tsx"))
953
- return true;
954
- if (file.endsWith(".document.ts") || file.endsWith(".service.ts") || file.endsWith(".signal.ts") || file.endsWith(".store.ts"))
806
+ if (file.endsWith(".tsx") || /\.(document|service|signal|store)\.ts$/.test(file))
955
807
  return true;
956
808
  if (file.endsWith(".constant.ts"))
957
809
  return !isEnumClass;
958
810
  return false;
959
811
  }
960
- function isInLibModule(file) {
961
- const segments = file.split("/");
962
- return (segments[0] === "apps" || segments[0] === "libs") && segments.includes("lib");
963
- }
964
812
  function isEnumClassStatement(sourceFile, statement) {
965
813
  if (!ts3.isClassDeclaration(statement))
966
814
  return false;
@@ -1056,14 +904,7 @@ function isDefaultExportStatement(statement) {
1056
904
  function getBangCommentWarnings(sourceFile) {
1057
905
  if (!isClientReachableFile(sourceFile.file))
1058
906
  return [];
1059
- return findPatternLines(sourceFile.content, /(?:^|[^\s/][ \t]+)\/[*/]!/).map((line) => ({
1060
- rule: "akan.file.bang-comment-in-client",
1061
- scope: "file",
1062
- severity: "warning",
1063
- file: sourceFile.file,
1064
- line,
1065
- message: "A `//!` or `/*!` marker survives minification and ships to the browser."
1066
- }));
907
+ return findPatternLines(sourceFile.content, /(?:^|[^\s/][ \t]+)\/[*/]!/).map((line) => fileWarning("akan.file.bang-comment-in-client", "file", sourceFile.file, "A `//!` or `/*!` marker survives minification and ships to the browser.", line));
1067
908
  }
1068
909
  function isClientReachableFile(file) {
1069
910
  if (/\.(test|spec)\.tsx?$/.test(file))
@@ -1082,61 +923,24 @@ function isClientReachableFile(file) {
1082
923
  function getPlaceholderExportWarnings(sourceFile) {
1083
924
  if (!sourceFile.file.endsWith("/index.ts") && !sourceFile.file.endsWith("/index.tsx"))
1084
925
  return [];
1085
- return getTopLevelDeclarations(sourceFile).filter((declaration) => declaration.exported && PLACEHOLDER_EXPORT_NAMES.has(declaration.name)).map((declaration) => ({
1086
- rule: "akan.file.placeholder-export",
1087
- scope: "file",
1088
- severity: "warning",
1089
- file: sourceFile.file,
1090
- line: declaration.line,
1091
- message: `Generated or barrel index file should not export placeholder "${declaration.name}".`
1092
- }));
926
+ return getTopLevelDeclarations(sourceFile).filter((declaration) => declaration.exported && PLACEHOLDER_EXPORT_NAMES.has(declaration.name)).map((declaration) => fileWarning("akan.file.placeholder-export", "file", sourceFile.file, `Generated or barrel index file should not export placeholder "${declaration.name}".`, declaration.line));
1093
927
  }
1094
928
  function getDictionaryTextWarnings(sourceFile) {
1095
929
  if (!sourceFile.file.endsWith(".dictionary.ts"))
1096
930
  return [];
1097
931
  const stalePatterns = [{ pattern: /\b[A-Z][A-Za-z0-9]* description\b/, label: "scaffold description text" }];
1098
- return stalePatterns.flatMap(({ pattern, label }) => findPatternLines(sourceFile.content, pattern).map((line) => ({
1099
- rule: "akan.file.dictionary-stale-text",
1100
- scope: "file",
1101
- severity: "warning",
1102
- file: sourceFile.file,
1103
- line,
1104
- message: `Dictionary text appears to contain ${label}.`
1105
- })));
932
+ return stalePatterns.flatMap(({ pattern, label }) => findPatternLines(sourceFile.content, pattern).map((line) => fileWarning("akan.file.dictionary-stale-text", "file", sourceFile.file, `Dictionary text appears to contain ${label}.`, line)));
1106
933
  }
1107
934
  function getGlobalMutationWarnings(sourceFile) {
1108
935
  const warnings = [];
1109
936
  for (const statement of sourceFile.sourceFile.statements) {
1110
- if (ts3.isModuleDeclaration(statement) && statement.name.getText(sourceFile.sourceFile) === "global") {
1111
- warnings.push({
1112
- rule: "akan.file.global-declaration",
1113
- scope: "file",
1114
- severity: "warning",
1115
- file: sourceFile.file,
1116
- line: getLine(sourceFile.sourceFile, statement),
1117
- message: "Global declarations require an explicit low-level integration allowlist."
1118
- });
1119
- }
1120
- if (ts3.isInterfaceDeclaration(statement) && statement.name.text === "Window") {
1121
- warnings.push({
1122
- rule: "akan.file.window-augmentation",
1123
- scope: "file",
1124
- severity: "warning",
1125
- file: sourceFile.file,
1126
- line: getLine(sourceFile.sourceFile, statement),
1127
- message: "Window augmentation should be isolated to approved browser integration files."
1128
- });
1129
- }
1130
- if (ts3.isExpressionStatement(statement) && statement.expression.getText(sourceFile.sourceFile).includes(".prototype.")) {
1131
- warnings.push({
1132
- rule: "akan.file.prototype-mutation",
1133
- scope: "file",
1134
- severity: "warning",
1135
- file: sourceFile.file,
1136
- line: getLine(sourceFile.sourceFile, statement),
1137
- message: "Prototype mutation should be avoided or isolated to approved low-level integration files."
1138
- });
1139
- }
937
+ const warn = (rule, message) => warnings.push(fileWarning(rule, "file", sourceFile.file, message, getLine(sourceFile.sourceFile, statement)));
938
+ if (ts3.isModuleDeclaration(statement) && statement.name.getText(sourceFile.sourceFile) === "global")
939
+ warn("akan.file.global-declaration", "Global declarations require an explicit low-level integration allowlist.");
940
+ if (ts3.isInterfaceDeclaration(statement) && statement.name.text === "Window")
941
+ warn("akan.file.window-augmentation", "Window augmentation should be isolated to approved browser integration files.");
942
+ if (ts3.isExpressionStatement(statement) && statement.expression.getText(sourceFile.sourceFile).includes(".prototype."))
943
+ warn("akan.file.prototype-mutation", "Prototype mutation should be avoided or isolated to approved low-level integration files.");
1140
944
  }
1141
945
  return warnings;
1142
946
  }
@@ -1145,73 +949,59 @@ function getTopLevelDeclarations(sourceFile) {
1145
949
  }
1146
950
  function getTopLevelDeclaration(sourceFile, statement) {
1147
951
  const line = getLine(sourceFile, statement);
1148
- if (ts3.isClassDeclaration(statement) && statement.name) {
1149
- return [{ name: statement.name.text, kind: "class", line, exported: isExported(statement), node: statement }];
1150
- }
1151
- if (ts3.isFunctionDeclaration(statement) && statement.name) {
1152
- return [{ name: statement.name.text, kind: "function", line, exported: isExported(statement), node: statement }];
1153
- }
1154
- if (ts3.isInterfaceDeclaration(statement)) {
1155
- return [{ name: statement.name.text, kind: "interface", line, exported: isExported(statement), node: statement }];
1156
- }
1157
- if (ts3.isTypeAliasDeclaration(statement)) {
1158
- return [{ name: statement.name.text, kind: "type", line, exported: isExported(statement), node: statement }];
1159
- }
1160
- if (ts3.isEnumDeclaration(statement)) {
1161
- return [{ name: statement.name.text, kind: "enum", line, exported: isExported(statement), node: statement }];
1162
- }
1163
- if (ts3.isVariableStatement(statement)) {
1164
- return statement.declarationList.declarations.filter((declaration) => ts3.isIdentifier(declaration.name)).map((declaration) => ({
1165
- name: declaration.name.text,
1166
- kind: "variable",
1167
- line: getLine(sourceFile, declaration),
1168
- exported: isExported(statement),
1169
- node: statement
1170
- }));
1171
- }
1172
- if (ts3.isExportDeclaration(statement)) {
952
+ const declared = (name, kind, at = line) => ({
953
+ name,
954
+ kind,
955
+ line: at,
956
+ exported: isExported(statement),
957
+ node: statement
958
+ });
959
+ if (ts3.isClassDeclaration(statement) && statement.name)
960
+ return [declared(statement.name.text, "class")];
961
+ if (ts3.isFunctionDeclaration(statement) && statement.name)
962
+ return [declared(statement.name.text, "function")];
963
+ if (ts3.isInterfaceDeclaration(statement))
964
+ return [declared(statement.name.text, "interface")];
965
+ if (ts3.isTypeAliasDeclaration(statement))
966
+ return [declared(statement.name.text, "type")];
967
+ if (ts3.isEnumDeclaration(statement))
968
+ return [declared(statement.name.text, "enum")];
969
+ if (ts3.isVariableStatement(statement))
970
+ return statement.declarationList.declarations.filter((declaration) => ts3.isIdentifier(declaration.name)).map((declaration) => declared(declaration.name.text, "variable", getLine(sourceFile, declaration)));
971
+ if (ts3.isExportDeclaration(statement))
1173
972
  return [{ name: "export declaration", kind: "export", line, exported: true, node: statement }];
1174
- }
1175
973
  return [];
1176
974
  }
1177
- function isAllowedConventionDeclaration(suffix, modelName, declaration) {
1178
- if (suffix === ".dictionary.ts")
1179
- return isExportedConst(declaration) && declaration.name === "dictionary";
975
+ function getConventionClassNames(suffix, model) {
1180
976
  if (suffix === ".constant.ts")
1181
- return isAllowedConstantDeclaration(modelName, declaration);
977
+ return [`${model}Input`, `${model}Object`, model, `Light${model}`, `${model}Insight`];
1182
978
  if (suffix === ".document.ts")
1183
- return declaration.kind === "class" && [`${modelName}Filter`, modelName, `${modelName}Model`].includes(declaration.name);
979
+ return [`${model}Filter`, model, `${model}Model`];
1184
980
  if (suffix === ".service.ts")
1185
- return declaration.kind === "class" && declaration.name === `${modelName}Service`;
981
+ return [`${model}Service`];
1186
982
  if (suffix === ".signal.ts")
1187
- return declaration.kind === "class" && [`${modelName}Internal`, `${modelName}Slice`, `${modelName}Endpoint`].includes(declaration.name);
983
+ return [`${model}Internal`, `${model}Slice`, `${model}Endpoint`];
1188
984
  if (suffix === ".store.ts")
1189
- return declaration.kind === "class" && declaration.name === `${modelName}Store`;
1190
- return false;
985
+ return [`${model}Store`];
986
+ return [];
1191
987
  }
1192
- function isAllowedConstantDeclaration(modelName, declaration) {
988
+ function isAllowedConventionDeclaration(suffix, modelName, declaration) {
989
+ if (suffix === ".dictionary.ts")
990
+ return isExportedConst(declaration) && declaration.name === "dictionary";
1193
991
  if (declaration.kind !== "class")
1194
992
  return false;
1195
- if ([`${modelName}Input`, `${modelName}Object`, modelName, `Light${modelName}`, `${modelName}Insight`].includes(declaration.name))
993
+ if (getConventionClassNames(suffix, modelName).includes(declaration.name))
1196
994
  return true;
1197
- if (!ts3.isClassDeclaration(declaration.node))
1198
- return false;
1199
- const heritageClause = declaration.node.heritageClauses?.find((clause) => clause.token === ts3.SyntaxKind.ExtendsKeyword);
1200
- const expression = heritageClause?.types[0]?.expression;
1201
- return !!expression && expression.getText().startsWith("enumOf(");
995
+ return suffix === ".constant.ts" && isEnumClassStatement(declaration.node.getSourceFile(), declaration.node);
1202
996
  }
1203
997
  function getConventionDescription(suffix, modelName) {
1204
998
  if (suffix === ".dictionary.ts")
1205
999
  return "export const dictionary";
1206
- if (suffix === ".constant.ts")
1207
- return `${modelName}Input, ${modelName}Object, ${modelName}, Light${modelName}, ${modelName}Insight, or enumOf classes`;
1208
- if (suffix === ".document.ts")
1209
- return `${modelName}Filter, ${modelName}, or ${modelName}Model`;
1210
- if (suffix === ".service.ts")
1211
- return `${modelName}Service`;
1212
- if (suffix === ".signal.ts")
1213
- return `${modelName}Internal, ${modelName}Slice, or ${modelName}Endpoint`;
1214
- return `${modelName}Store`;
1000
+ const names = [
1001
+ ...getConventionClassNames(suffix, modelName),
1002
+ ...suffix === ".constant.ts" ? ["enumOf classes"] : []
1003
+ ];
1004
+ return names.length > 1 ? `${names.slice(0, -1).join(", ")}, or ${names.at(-1)}` : names[0];
1215
1005
  }
1216
1006
  var sysRootTypes = { apps: "app", libs: "lib" };
1217
1007
  function getSysRootEntry(file) {
@@ -1243,13 +1033,7 @@ function getModuleUiWarning(file) {
1243
1033
  const allowedFileNames = new Set(allowedSuffixes.map((suffix) => `${pascalName}.${suffix}.tsx`));
1244
1034
  if (allowedFileNames.has(fileName) || fileName.endsWith(".test.tsx") || fileName.endsWith(".spec.tsx"))
1245
1035
  return null;
1246
- return {
1247
- rule: "akan.layout.module-ui-file",
1248
- scope: "layout",
1249
- severity: "warning",
1250
- file,
1251
- message: `Unexpected ${kind} module UI filename "${fileName}". Expected one of: ${[...allowedFileNames].join(", ")}.`
1252
- };
1036
+ return fileWarning("akan.layout.module-ui-file", "layout", file, `Unexpected ${kind} module UI filename "${fileName}". Expected one of: ${[...allowedFileNames].join(", ")}.`);
1253
1037
  }
1254
1038
  function getModuleInfo(file) {
1255
1039
  const segments = file.split("/");
@@ -1265,10 +1049,8 @@ function getModuleInfo(file) {
1265
1049
  if (segments.length !== libIndex + 3)
1266
1050
  return null;
1267
1051
  const fileName = segments[libIndex + 2];
1268
- if (moduleName.startsWith("__")) {
1269
- const scalarName = moduleName === "__scalar" ? segments[libIndex + 2] : moduleName;
1270
- return { moduleName: scalarName, fileName, kind: "scalar" };
1271
- }
1052
+ if (moduleName.startsWith("__"))
1053
+ return { moduleName, fileName, kind: "scalar" };
1272
1054
  if (moduleName.startsWith("_"))
1273
1055
  return { moduleName, fileName, kind: "service" };
1274
1056
  return { moduleName, fileName, kind: "database" };
@@ -1277,7 +1059,7 @@ function isExportedConst(declaration) {
1277
1059
  return declaration.exported && ts3.isVariableStatement(declaration.node) && (declaration.node.declarationList.flags & ts3.NodeFlags.Const) !== 0;
1278
1060
  }
1279
1061
  function isExported(node) {
1280
- return !!ts3.getCombinedModifierFlags(node) && !!(ts3.getCombinedModifierFlags(node) & ts3.ModifierFlags.Export);
1062
+ return !!(ts3.getCombinedModifierFlags(node) & ts3.ModifierFlags.Export);
1281
1063
  }
1282
1064
  function isFunctionLikeInitializer(node) {
1283
1065
  return !!node && (ts3.isArrowFunction(node) || ts3.isFunctionExpression(node));
@@ -1340,24 +1122,16 @@ function compareWarnings(a, b) {
1340
1122
  // pkgs/@akanjs/cli/quality/quality.script.ts
1341
1123
  import { Logger } from "akanjs/common";
1342
1124
 
1343
- // pkgs/@akanjs/cli/quality/quality.runner.ts
1344
- class QualityRunner extends runner("quality") {
1345
- async scan(workspace) {
1346
- return await new AkanQualityScanner().scan(workspace.workspaceRoot);
1347
- }
1348
- }
1349
-
1350
- // pkgs/@akanjs/cli/quality/quality.script.ts
1351
- class QualityScript extends script("quality", [QualityRunner]) {
1125
+ class QualityScript extends script("quality") {
1352
1126
  async scan(workspace, format = "text") {
1353
1127
  const spinner = workspace.spinning("Scanning Akan code quality...");
1354
- const result = await this.qualityRunner.scan(workspace);
1128
+ const result = await new AkanQualityScanner().scan(workspace.workspaceRoot);
1355
1129
  spinner.succeed(`Quality scan completed (${result.scannedFiles} files, ${result.warnings.length} warnings)`);
1356
1130
  Logger.rawLog(format === "json" ? JSON.stringify(result, null, 2) : formatQualityScanResult(result));
1357
1131
  }
1358
1132
  async ssr(workspace, format = "text") {
1359
1133
  const spinner = workspace.spinning("Measuring Akan server/client render balance...");
1360
- const scanned = await this.qualityRunner.scan(workspace);
1134
+ const scanned = await new AkanQualityScanner().scan(workspace.workspaceRoot);
1361
1135
  const result = { ...scanned, warnings: scanned.warnings.filter((warning) => warning.scope === "ssr") };
1362
1136
  spinner.succeed(`SSR scan completed (${result.scannedFiles} files, ${result.warnings.length} warnings)`);
1363
1137
  Logger.rawLog(format === "json" ? JSON.stringify(result, null, 2) : formatSsrScanResult(result));
@@ -1366,15 +1140,7 @@ class QualityScript extends script("quality", [QualityRunner]) {
1366
1140
 
1367
1141
  // pkgs/@akanjs/cli/quality/quality.command.ts
1368
1142
  class QualityCommand extends command("quality", [QualityScript], ({ public: target }) => ({
1369
- quality: target({ desc: "Scan apps and libs for Akan code quality warnings" }).arg("action", String, {
1370
- desc: "quality action",
1371
- default: "scan",
1372
- enum: ["scan", "ssr"]
1373
- }).option("format", String, {
1374
- desc: "output format",
1375
- default: "text",
1376
- enum: ["text", "json"]
1377
- }).with(Workspace).exec(async function(action, format, workspace) {
1143
+ quality: target({ desc: "Scan apps and libs for Akan code quality warnings" }).arg("action", String, { desc: "quality action", default: "scan", enum: ["scan", "ssr"] }).option("format", String, { desc: "output format", default: "text", enum: ["text", "json"] }).with(Workspace).exec(async function(action, format, workspace) {
1378
1144
  if (action === "scan")
1379
1145
  await this.qualityScript.scan(workspace, format);
1380
1146
  else if (action === "ssr")