@akanjs/devkit 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 (233) hide show
  1. package/abstractDoc.test.ts +6 -15
  2. package/abstractDoc.ts +1 -4
  3. package/agentsIndex.test.ts +4 -7
  4. package/agentsIndex.ts +6 -22
  5. package/akanApp/BackendImportGraph.test.ts +8 -22
  6. package/akanApp/BackendImportGraph.ts +1 -20
  7. package/akanApp/akanApp.host.test.ts +1 -3
  8. package/akanApp/akanApp.host.ts +64 -250
  9. package/akanApp/devHostPolicy.test.ts +10 -34
  10. package/akanApp/devHostPolicy.ts +12 -98
  11. package/akanConfig/akanConfig.test.ts +30 -39
  12. package/akanConfig/akanConfig.ts +40 -136
  13. package/akanContext.ts +106 -159
  14. package/akanMcpContract.ts +28 -73
  15. package/appSelectionMemory.ts +1 -4
  16. package/applicationBuildReporter.ts +37 -5
  17. package/applicationBuildRunner.ts +25 -59
  18. package/applicationReleasePackager.ts +24 -44
  19. package/applicationTestPreload.ts +3 -12
  20. package/artifact/implicitRootLayout.test.ts +3 -19
  21. package/artifact/implicitRootLayout.ts +34 -91
  22. package/artifact/routeSeedIndex.ts +11 -44
  23. package/biomeBase.ts +2 -5
  24. package/capacitorApp.test.ts +4 -14
  25. package/capacitorApp.ts +86 -235
  26. package/cloud/cloudApi.test.ts +202 -0
  27. package/cloud/cloudApi.ts +33 -57
  28. package/cloud/configLock.test.ts +64 -0
  29. package/cloud/configLock.ts +96 -0
  30. package/cloud/constants.ts +3 -2
  31. package/cloud/globalConfig.ts +64 -31
  32. package/codeAgent/agent/AkanCodeServices.ts +15 -57
  33. package/codeAgent/agent/AkanContextFiles.ts +5 -28
  34. package/codeAgent/agent/CodeAgent.ts +33 -108
  35. package/codeAgent/agent/CodeAgentAsks.ts +7 -30
  36. package/codeAgent/agent/CodeAgentEventMapper.ts +9 -52
  37. package/codeAgent/agent/CodeAgentGate.ts +8 -34
  38. package/codeAgent/agent/CodeAgentProxy.ts +4 -13
  39. package/codeAgent/agent/CodeAgentRpcHost.ts +3 -16
  40. package/codeAgent/agent/CodeAgentRpcListener.ts +2 -10
  41. package/codeAgent/agent/CodeAgentStreamPrinter.ts +4 -15
  42. package/codeAgent/agent/CodeAgentSuspended.ts +1 -8
  43. package/codeAgent/agent/CodeAgentUi.ts +2 -7
  44. package/codeAgent/agent/CodeMailbox.ts +7 -34
  45. package/codeAgent/agent/CodeSessionFork.ts +4 -19
  46. package/codeAgent/agent/CodeSessionIndex.ts +5 -31
  47. package/codeAgent/agent/SubagentPool.ts +48 -58
  48. package/codeAgent/agent/akanCodeModel.ts +5 -39
  49. package/codeAgent/agent/akanCodePaths.ts +4 -24
  50. package/codeAgent/agent/akanContextFiles.test.ts +0 -2
  51. package/codeAgent/agent/akanSystemPrompt.ts +1 -9
  52. package/codeAgent/agent/codeAgent.test.ts +178 -0
  53. package/codeAgent/agent/codeAgentContract.test.ts +5 -28
  54. package/codeAgent/agent/codeMailbox.test.ts +0 -4
  55. package/codeAgent/agent/codeSessionFork.test.ts +0 -2
  56. package/codeAgent/agent/codeSessionIndex.test.ts +9 -4
  57. package/codeAgent/agent/consoleToStderr.ts +2 -12
  58. package/codeAgent/agent/jsonLines.ts +29 -0
  59. package/codeAgent/agent/stringArg.ts +5 -0
  60. package/codeAgent/feedback/DevLogFeedback.ts +3 -18
  61. package/codeAgent/feedback/PreviewChrome.ts +5 -29
  62. package/codeAgent/feedback/PreviewFeedback.ts +2 -9
  63. package/codeAgent/feedback/PreviewView.ts +3 -15
  64. package/codeAgent/feedback/TurnFeedback.ts +2 -17
  65. package/codeAgent/feedback/VerifyFeedback.ts +1 -7
  66. package/codeAgent/feedback/previewChrome.test.ts +1 -1
  67. package/codeAgent/index.ts +2 -10
  68. package/codeAgent/skills/akan-code/SKILL.md +12 -4
  69. package/codeAgent/tools/AkanCli.ts +4 -16
  70. package/codeAgent/tools/AkanCodePlugins.ts +21 -43
  71. package/codeAgent/tools/AkanEditScope.ts +2 -12
  72. package/codeAgent/tools/AkanEnvKeys.ts +3 -11
  73. package/codeAgent/tools/AkanToolPack.ts +6 -28
  74. package/codeAgent/tools/AkanVerifier.ts +1 -8
  75. package/codeAgent/tools/AskToolPack.ts +3 -15
  76. package/codeAgent/tools/MailToolPack.ts +0 -9
  77. package/codeAgent/tools/McpClient.ts +3 -24
  78. package/codeAgent/tools/McpOAuth.ts +6 -41
  79. package/codeAgent/tools/McpOAuthLoopback.ts +2 -15
  80. package/codeAgent/tools/McpServerConfig.ts +7 -26
  81. package/codeAgent/tools/McpSignIn.ts +46 -49
  82. package/codeAgent/tools/McpTokenStore.ts +6 -17
  83. package/codeAgent/tools/McpToolPack.ts +107 -35
  84. package/codeAgent/tools/SessionToolPack.ts +1 -11
  85. package/codeAgent/tools/WebToolPack.ts +1 -7
  86. package/codeAgent/tools/akanCodePlugins.test.ts +231 -63
  87. package/codeAgent/tools/askToolPack.test.ts +0 -3
  88. package/codeAgent/tools/mcpOAuth.test.ts +315 -27
  89. package/codeAgent/tools/mcpServerConfig.test.ts +21 -14
  90. package/codegenLock.test.ts +4 -13
  91. package/codegenLock.ts +3 -17
  92. package/commandDecorators/argMeta.ts +1 -5
  93. package/commandDecorators/camelToKebabCase.ts +1 -0
  94. package/commandDecorators/command.ts +44 -102
  95. package/commandDecorators/commandBuilder.ts +30 -61
  96. package/commandDecorators/commandDecorators.test.ts +1 -9
  97. package/commandDecorators/dependencyBuilder.ts +10 -1
  98. package/commandDecorators/helpFormatter.ts +66 -144
  99. package/commandDecorators/index.ts +0 -2
  100. package/commandDecorators/targetMeta.ts +9 -7
  101. package/createTunnel.ts +7 -14
  102. package/dependencyScanner.test.ts +4 -16
  103. package/dependencyScanner.ts +27 -78
  104. package/devkitUtils.test.ts +47 -19
  105. package/executors.test.ts +114 -60
  106. package/executors.ts +165 -484
  107. package/fileEditor.ts +9 -37
  108. package/{getDirname.test.ts → fileSys.test.ts} +1 -1
  109. package/fileSys.ts +6 -8
  110. package/formSetterScanner.ts +3 -16
  111. package/frontendBuild/allRoutesBuilder.ts +7 -21
  112. package/frontendBuild/autoImportSync.test.ts +14 -38
  113. package/frontendBuild/autoImportSync.ts +15 -67
  114. package/frontendBuild/buildRouteClient.test.ts +10 -36
  115. package/frontendBuild/bundleDefine.ts +12 -0
  116. package/frontendBuild/clientBuildTypes.ts +5 -36
  117. package/frontendBuild/clientEntriesBundler.ts +9 -40
  118. package/frontendBuild/clientEntryDiscovery.ts +6 -41
  119. package/frontendBuild/csrArtifactBuilder.ts +6 -25
  120. package/frontendBuild/cssCandidateCache.test.ts +0 -6
  121. package/frontendBuild/cssCandidateCache.ts +6 -41
  122. package/frontendBuild/cssCompiler.ts +29 -78
  123. package/frontendBuild/cssImportResolver.ts +5 -18
  124. package/frontendBuild/devChangePlanner.ts +7 -10
  125. package/frontendBuild/devGeneratedIndexSync.test.ts +12 -27
  126. package/frontendBuild/devGeneratedIndexSync.ts +1 -4
  127. package/frontendBuild/fontOptimizer.test.ts +5 -13
  128. package/frontendBuild/fontOptimizer.ts +15 -72
  129. package/frontendBuild/fontPruner.test.ts +5 -10
  130. package/frontendBuild/fontPruner.ts +5 -20
  131. package/frontendBuild/frontendBuild.test.ts +27 -52
  132. package/frontendBuild/hmrChangeClassifier.ts +2 -2
  133. package/frontendBuild/hmrWatcher.test.ts +7 -31
  134. package/frontendBuild/hmrWatcher.ts +14 -71
  135. package/frontendBuild/pagesBundleBuilder.ts +4 -29
  136. package/frontendBuild/precompressArtifacts.ts +5 -18
  137. package/frontendBuild/routeClientBuilder.ts +7 -47
  138. package/frontendBuild/routesManifestArtifactSerializer.ts +5 -36
  139. package/frontendBuild/sourceMtimeIndex.test.ts +37 -94
  140. package/frontendBuild/sourceMtimeIndex.ts +19 -130
  141. package/frontendBuild/ssrBaseArtifactBuilder.ts +2 -8
  142. package/frontendBuild/styleContract.ts +1 -8
  143. package/frontendBuild/themeValidator.ts +4 -26
  144. package/frontendBuild/watchRootResolver.test.ts +23 -42
  145. package/frontendBuild/watchRootResolver.ts +5 -27
  146. package/getRelatedCnsts.ts +12 -92
  147. package/incrementalBuilder/buildBatch.proc.ts +22 -57
  148. package/incrementalBuilder/buildBatchProtocol.ts +8 -36
  149. package/incrementalBuilder/buildBatchRunner.test.ts +34 -0
  150. package/incrementalBuilder/buildBatchRunner.ts +10 -31
  151. package/incrementalBuilder/builderChannel.test.ts +24 -49
  152. package/incrementalBuilder/builderChannel.ts +6 -37
  153. package/incrementalBuilder/builderRequestRouter.test.ts +0 -8
  154. package/incrementalBuilder/builderRequestRouter.ts +5 -24
  155. package/incrementalBuilder/devWatchBatch.test.ts +14 -18
  156. package/incrementalBuilder/incrementalBuilder.host.test.ts +18 -55
  157. package/incrementalBuilder/incrementalBuilder.host.ts +18 -76
  158. package/incrementalBuilder/incrementalBuilder.proc.test.ts +81 -0
  159. package/incrementalBuilder/incrementalBuilder.proc.ts +48 -161
  160. package/index.ts +3 -15
  161. package/integration/devResourceProbe.ts +50 -56
  162. package/integration/devStability.integration.test.ts +47 -194
  163. package/integration/devStabilityHarness.test.ts +9 -29
  164. package/integration/devStabilityHarness.ts +65 -330
  165. package/integration/ssrMemoryProbe.ts +41 -167
  166. package/libSource.test.ts +4 -15
  167. package/libSource.ts +19 -31
  168. package/lint/gritRules.test.ts +6 -23
  169. package/linter.test.ts +1 -2
  170. package/linter.ts +22 -115
  171. package/mobile/mobileTarget.ts +14 -13
  172. package/package.json +2 -2
  173. package/packageExports.test.ts +3 -18
  174. package/packageExportsMap.ts +3 -19
  175. package/piDependencyPins.test.ts +3 -14
  176. package/platformTest/IdleWatchedProcess.test.ts +5 -13
  177. package/platformTest/IdleWatchedProcess.ts +1 -5
  178. package/platformTest/LinuxDockerTarget.ts +20 -35
  179. package/platformTest/PlatformTestRun.test.ts +5 -9
  180. package/platformTest/PlatformTestTarget.ts +24 -2
  181. package/platformTest/TestSnapshot.test.ts +6 -14
  182. package/platformTest/TestSnapshot.ts +1 -5
  183. package/platformTest/WindowsSshTarget.ts +6 -22
  184. package/prompter.ts +3 -6
  185. package/qualityScanner.test.ts +6 -24
  186. package/qualityScanner.ts +196 -284
  187. package/recipeScanner.test.ts +7 -12
  188. package/recipeScanner.ts +9 -38
  189. package/repoIdentity.ts +2 -9
  190. package/routeSourceValidator.ts +7 -30
  191. package/scanInfo.ts +82 -214
  192. package/selectModel.ts +1 -2
  193. package/semver.ts +3 -5
  194. package/slicePlanner.test.ts +6 -24
  195. package/slicePlanner.ts +11 -32
  196. package/spinner.test.ts +3 -8
  197. package/spinner.ts +4 -16
  198. package/sshTunnel.ts +1 -6
  199. package/ssrScanner.test.ts +1 -6
  200. package/ssrScanner.ts +103 -113
  201. package/subspace.test.ts +6 -25
  202. package/subspace.ts +31 -109
  203. package/subspaceConfig.ts +3 -11
  204. package/testHelpers.ts +49 -34
  205. package/transforms/asyncDefaultExportDetector.ts +3 -11
  206. package/transforms/barrelAnalyzer.ts +43 -135
  207. package/transforms/barrelImportsPlugin.ts +38 -175
  208. package/transforms/externalizeFrameworkPlugin.test.ts +39 -118
  209. package/transforms/externalizeFrameworkPlugin.ts +13 -87
  210. package/transforms/moduleSyntax.ts +32 -0
  211. package/transforms/rscUseClientTransform.ts +35 -20
  212. package/transforms/transforms.test.ts +153 -44
  213. package/transforms/tsconfigPackageResolver.test.ts +6 -24
  214. package/transforms/useClientBundlePlugin.ts +2 -22
  215. package/typeChecker.ts +7 -63
  216. package/uploadRelease.ts +10 -25
  217. package/workflow/artifacts.ts +18 -0
  218. package/workflow/executor.test.ts +1 -12
  219. package/workflow/executor.ts +85 -146
  220. package/workflow/index.ts +0 -1
  221. package/workflow/moduleIndex.test.ts +4 -15
  222. package/workflow/moduleIndex.ts +9 -49
  223. package/workflow/plan.ts +18 -25
  224. package/workflow/render.ts +80 -96
  225. package/workflow/source.ts +33 -56
  226. package/workflow/sourceAst.ts +50 -0
  227. package/workspaceLayout.ts +4 -19
  228. package/commandDecorators/commandMeta.ts +0 -7
  229. package/commandDecorators/types.ts +0 -10
  230. package/getDirname.ts +0 -4
  231. package/local/optimize-resource/ipcprobe/child.ts +0 -1
  232. package/local/optimize-resource/ipcprobe/parent.ts +0 -11
  233. package/workflow/primitive.ts +0 -59
@@ -4,6 +4,15 @@ import ts from "typescript";
4
4
  import type { AkanModuleContext } from "../akanContext";
5
5
  import type { Workspace } from "../commandDecorators";
6
6
  import { moduleComponentName, moduleSourcePaths } from "./source";
7
+ import {
8
+ callExpressionName,
9
+ expressionName,
10
+ firstObjectReturnedByArrow,
11
+ heritageCall,
12
+ nodeName,
13
+ propertyName,
14
+ sourceFileFor,
15
+ } from "./sourceAst";
7
16
  import type {
8
17
  AkanConstantIndex,
9
18
  AkanDictionaryIndex,
@@ -52,9 +61,6 @@ const fileExists = async (filePath: string) => {
52
61
  }
53
62
  };
54
63
 
55
- const sourceFileFor = (filePath: string, content: string) =>
56
- ts.createSourceFile(filePath, content, ts.ScriptTarget.Latest, true, ts.ScriptKind.TS);
57
-
58
64
  const parseDiagnosticsFor = (source: ts.SourceFile, filePath: string): WorkflowDiagnostic[] => {
59
65
  const parseDiagnostics =
60
66
  (source as ts.SourceFile & { parseDiagnostics?: readonly ts.DiagnosticWithLocation[] }).parseDiagnostics ?? [];
@@ -83,25 +89,6 @@ const spanFor = (source: ts.SourceFile, file: string, node: ts.Node): AkanSource
83
89
  };
84
90
  };
85
91
 
86
- const nodeName = (node: ts.PropertyName | ts.BindingName | undefined) => {
87
- if (!node) return null;
88
- if (ts.isIdentifier(node) || ts.isStringLiteral(node) || ts.isNumericLiteral(node)) return node.text;
89
- return null;
90
- };
91
-
92
- const propertyName = (node: ts.ObjectLiteralElementLike) =>
93
- ts.isPropertyAssignment(node) || ts.isShorthandPropertyAssignment(node) || ts.isMethodDeclaration(node)
94
- ? nodeName(node.name)
95
- : null;
96
-
97
- const expressionName = (expression: ts.Expression): string | null => {
98
- if (ts.isIdentifier(expression)) return expression.text;
99
- if (ts.isPropertyAccessExpression(expression)) return expression.name.text;
100
- if (ts.isCallExpression(expression)) return expressionName(expression.expression);
101
- if (ts.isAsExpression(expression)) return expressionName(expression.expression);
102
- return null;
103
- };
104
-
105
92
  const expressionSummary = (expression: ts.Expression): string => {
106
93
  if (ts.isAsExpression(expression)) return expressionSummary(expression.expression);
107
94
  if (ts.isIdentifier(expression) || ts.isPropertyAccessExpression(expression))
@@ -151,30 +138,6 @@ const fieldsFromObject = (
151
138
  })
152
139
  .filter((field): field is AkanFieldOutline => field !== null);
153
140
 
154
- const firstObjectReturnedByArrow = (node: ts.Node): ts.ObjectLiteralExpression | null => {
155
- if (!ts.isArrowFunction(node) && !ts.isFunctionExpression(node)) return null;
156
- if (ts.isObjectLiteralExpression(node.body)) return node.body;
157
- if (ts.isParenthesizedExpression(node.body) && ts.isObjectLiteralExpression(node.body.expression)) {
158
- return node.body.expression;
159
- }
160
- if (!ts.isBlock(node.body)) return null;
161
- for (const statement of node.body.statements) {
162
- if (ts.isReturnStatement(statement) && statement.expression && ts.isObjectLiteralExpression(statement.expression)) {
163
- return statement.expression;
164
- }
165
- }
166
- return null;
167
- };
168
-
169
- const isViaCall = (expression: ts.Expression) =>
170
- ts.isCallExpression(expression) && expressionName(expression.expression) === "via";
171
-
172
- const heritageCall = (node: ts.ClassDeclaration) => {
173
- const heritage = node.heritageClauses?.flatMap((clause) => [...clause.types]) ?? [];
174
- const expression = heritage.find((clause) => isViaCall(clause.expression))?.expression;
175
- return expression && ts.isCallExpression(expression) ? expression : null;
176
- };
177
-
178
141
  interface ParsedConstantIndex {
179
142
  index: AkanConstantIndex;
180
143
  diagnostics: WorkflowDiagnostic[];
@@ -297,9 +260,6 @@ const parseConstantIndex = (filePath: string, content: string, moduleClassName:
297
260
  };
298
261
  };
299
262
 
300
- const callExpressionName = (node: ts.CallExpression) =>
301
- ts.isPropertyAccessExpression(node.expression) ? node.expression.name.text : expressionName(node.expression);
302
-
303
263
  const parseDictionaryIndex = (filePath: string, content: string, moduleClassName: string): ParsedDictionaryIndex => {
304
264
  const source = sourceFileFor(filePath, content);
305
265
  const diagnostics = parseDiagnosticsFor(source, filePath);
package/workflow/plan.ts CHANGED
@@ -206,31 +206,24 @@ const createAddFieldRecommendations = (inputs: Record<string, WorkflowInputValue
206
206
  },
207
207
  ]
208
208
  : []),
209
- ...(!includeInLight
210
- ? [
211
- {
212
- code: "add-field-light-projection-choice",
213
- kind: "manual-action" as const,
214
- target: paths.constant,
215
- action: `Pass includeInLight=true when users should see ${field} in list/card data. Without it, the field can exist on ${capitalize(
216
- module,
217
- )}Input but stay absent from Light${capitalize(module)} projections.`,
218
- confidence: "medium" as const,
219
- message: `${module}.${field} is not selected for list/card projection data.`,
220
- },
221
- ]
222
- : []),
223
- ...(includeInLight
224
- ? [
225
- {
226
- code: "add-field-light-projection",
227
- kind: "placement" as const,
228
- target: paths.constant,
229
- confidence: "high" as const,
230
- message: `Add ${field} to Light${capitalize(module)} projection fields.`,
231
- },
232
- ]
233
- : []),
209
+ includeInLight
210
+ ? {
211
+ code: "add-field-light-projection",
212
+ kind: "placement",
213
+ target: paths.constant,
214
+ confidence: "high",
215
+ message: `Add ${field} to Light${capitalize(module)} projection fields.`,
216
+ }
217
+ : {
218
+ code: "add-field-light-projection-choice",
219
+ kind: "manual-action",
220
+ target: paths.constant,
221
+ action: `Pass includeInLight=true when users should see ${field} in list/card data. Without it, the field can exist on ${capitalize(
222
+ module,
223
+ )}Input but stay absent from Light${capitalize(module)} projections.`,
224
+ confidence: "medium",
225
+ message: `${module}.${field} is not selected for list/card projection data.`,
226
+ },
234
227
  ...(surfaces && !templateRequested
235
228
  ? [
236
229
  {
@@ -1,6 +1,9 @@
1
1
  import type {
2
+ PrimitiveFormat,
3
+ PrimitiveWriteReport,
2
4
  RepairReport,
3
5
  WorkflowApplyReport,
6
+ WorkflowDiagnostic,
4
7
  WorkflowFormat,
5
8
  WorkflowPlan,
6
9
  WorkflowRunArtifact,
@@ -9,6 +12,23 @@ import type {
9
12
  } from "./types";
10
13
  import { jsonText } from "./utils";
11
14
 
15
+ const listOr = <T>(items: readonly T[] | undefined, render: (item: T) => string, empty = "- none") =>
16
+ items?.length ? items.map(render) : [empty];
17
+
18
+ const renderDiagnostic = (diagnostic: WorkflowDiagnostic) =>
19
+ `- [${diagnostic.severity}] ${diagnostic.code}: ${diagnostic.message}`;
20
+
21
+ const renderCommand = (entry: { command: string; reason: string }) => `- \`${entry.command}\`: ${entry.reason}`;
22
+
23
+ const renderFile = (file: { action: string; path: string; reason: string }) =>
24
+ `- \`${file.action}\` ${file.path}: ${file.reason}`;
25
+
26
+ const renderRecommendation = (recommendation: WorkflowApplyReport["recommendations"][number]) => {
27
+ const target = recommendation.target ? ` ${recommendation.target}` : "";
28
+ const action = recommendation.action ? ` Action: ${recommendation.action}` : "";
29
+ return `- [${recommendation.kind}]${target} ${recommendation.message}${action}`;
30
+ };
31
+
12
32
  export const renderWorkflowList = (specs: readonly WorkflowSpec[]) =>
13
33
  [
14
34
  "# Akan Workflows",
@@ -41,7 +61,7 @@ export const renderWorkflowExplain = (spec: WorkflowSpec) =>
41
61
  ...spec.steps.map((step, index) => `${index + 1}. \`${step.id}\` (${step.tool}): ${step.description}`),
42
62
  "",
43
63
  "## Validation",
44
- ...spec.validation.map((validation) => `- \`${validation.command}\`: ${validation.reason}`),
64
+ ...spec.validation.map(renderCommand),
45
65
  "",
46
66
  ].join("\n");
47
67
 
@@ -71,43 +91,16 @@ export const renderWorkflowPlan = (plan: WorkflowPlan) =>
71
91
  }),
72
92
  "",
73
93
  "## Validation",
74
- ...plan.validation.map((validation) => `- \`${validation.command}\`: ${validation.reason}`),
94
+ ...plan.validation.map(renderCommand),
75
95
  "",
76
96
  "## Diagnostics",
77
- ...(plan.diagnostics.length
78
- ? plan.diagnostics.map((diagnostic) => `- [${diagnostic.severity}] ${diagnostic.code}: ${diagnostic.message}`)
79
- : ["- none"]),
97
+ ...listOr(plan.diagnostics, renderDiagnostic),
80
98
  "",
81
99
  "## Recommendations",
82
- ...(plan.recommendations.length
83
- ? plan.recommendations.map((recommendation) => `- [${recommendation.kind}] ${recommendation.message}`)
84
- : ["- none"]),
100
+ ...listOr(plan.recommendations, (recommendation) => `- [${recommendation.kind}] ${recommendation.message}`),
85
101
  "",
86
102
  ].join("\n");
87
103
 
88
- const renderRecommendation = (recommendation: WorkflowApplyReport["recommendations"][number]) => {
89
- const target = recommendation.target ? ` ${recommendation.target}` : "";
90
- const action = recommendation.action ? ` Action: ${recommendation.action}` : "";
91
- return `- [${recommendation.kind}]${target} ${recommendation.message}${action}`;
92
- };
93
-
94
- const renderDiagnostic = (diagnostic: WorkflowApplyReport["diagnostics"][number]) =>
95
- `- [${diagnostic.severity}] ${diagnostic.code}: ${diagnostic.message}`;
96
-
97
- const renderNextAction = (action: WorkflowApplyReport["nextActions"][number]) =>
98
- `- \`${action.command}\`: ${action.reason}`;
99
-
100
- const applySourceStatus = (report: WorkflowApplyReport) =>
101
- report.diagnostics.some(
102
- (diagnostic) =>
103
- diagnostic.severity === "error" &&
104
- (diagnostic.failureScope === "source-change" ||
105
- !diagnostic.failureScope ||
106
- diagnostic.failureScope === "unknown"),
107
- )
108
- ? "failed"
109
- : "passed";
110
-
111
104
  export const renderWorkflowApplyReport = (report: WorkflowApplyReport) => {
112
105
  const applySummary = {
113
106
  sourceFilesChanged: report.summary?.sourceFilesChanged ?? report.changedFiles,
@@ -140,7 +133,7 @@ export const renderWorkflowApplyReport = (report: WorkflowApplyReport) => {
140
133
  "",
141
134
  `- Mode: ${report.mode}`,
142
135
  `- Status: ${report.status}`,
143
- `- Source-change status: ${applySourceStatus(report)}`,
136
+ `- Source-change status: ${sourceBlockers.length ? "failed" : "passed"}`,
144
137
  `- Workspace status: ${validationBlockers.length ? "blocked" : "not blocked during apply"}`,
145
138
  `- Source files changed: ${applySummary.sourceFilesChanged.length}`,
146
139
  `- Generated files queued for sync: ${applySummary.generatedFilesSynced.length}`,
@@ -155,24 +148,16 @@ export const renderWorkflowApplyReport = (report: WorkflowApplyReport) => {
155
148
  ...(sourceBlockers.length ? sourceBlockers : ["- none"]),
156
149
  "",
157
150
  "## Automatically Modified",
158
- ...(applySummary.sourceFilesChanged.length
159
- ? applySummary.sourceFilesChanged.map((file) => `- \`${file.action}\` ${file.path}: ${file.reason}`)
160
- : ["- none"]),
151
+ ...listOr(applySummary.sourceFilesChanged, renderFile),
161
152
  "",
162
153
  "## Generated Sync",
163
- ...(applySummary.generatedFilesSynced.length
164
- ? applySummary.generatedFilesSynced.map((file) => `- \`${file.action}\` ${file.path}: ${file.reason}`)
165
- : ["- none"]),
154
+ ...listOr(applySummary.generatedFilesSynced, renderFile),
166
155
  "",
167
156
  "## Applied Commands",
168
- ...(report.appliedCommands.length
169
- ? report.appliedCommands.map((command) => `- \`${command.command}\`: ${command.reason}`)
170
- : ["- none"]),
157
+ ...listOr(report.appliedCommands, renderCommand),
171
158
  "",
172
159
  "## Recommended Validation Commands",
173
- ...(report.recommendedValidationCommands.length
174
- ? report.recommendedValidationCommands.map((command) => `- \`${command.command}\`: ${command.reason}`)
175
- : ["- none"]),
160
+ ...listOr(report.recommendedValidationCommands, renderCommand),
176
161
  "",
177
162
  "## User Review Required",
178
163
  ...(manualReviewItems.length ? manualReviewItems : ["- none"]),
@@ -185,13 +170,13 @@ export const renderWorkflowApplyReport = (report: WorkflowApplyReport) => {
185
170
  : ["- none"]),
186
171
  "",
187
172
  "## Diagnostics",
188
- ...(report.diagnostics.length ? report.diagnostics.map(renderDiagnostic) : ["- none"]),
173
+ ...listOr(report.diagnostics, renderDiagnostic),
189
174
  "",
190
175
  "## Recommendations",
191
- ...(report.recommendations.length ? report.recommendations.slice(0, 3).map(renderRecommendation) : ["- none"]),
176
+ ...listOr(report.recommendations.slice(0, 3), renderRecommendation),
192
177
  "",
193
178
  "## Next Actions",
194
- ...(report.nextActions.length ? report.nextActions.slice(0, 3).map(renderNextAction) : ["- none"]),
179
+ ...listOr(report.nextActions.slice(0, 3), renderCommand),
195
180
  "",
196
181
  ].join("\n");
197
182
  };
@@ -229,21 +214,16 @@ export const renderWorkflowValidationRunReport = (report: WorkflowValidationRunR
229
214
  `- Environment: ${report.summary.environment}`,
230
215
  "",
231
216
  "## Source Change Diagnostics",
232
- ...(report.workflowDiagnostics?.length
233
- ? report.workflowDiagnostics.map(
234
- (diagnostic) => `- [${diagnostic.severity}] ${diagnostic.code}: ${diagnostic.message}`,
235
- )
236
- : ["- none"]),
217
+ ...listOr(report.workflowDiagnostics, renderDiagnostic),
237
218
  "",
238
219
  "## Existing Workspace Blockers",
239
220
  `- Status: ${baselineSummary.status}`,
240
221
  `- Errors: ${baselineSummary.totalErrors}`,
241
222
  `- Warnings: ${baselineSummary.totalWarnings}`,
242
- ...(baselineSummary.byCode.length
243
- ? baselineSummary.byCode.map(
244
- (item) => `- [${item.severity}] ${item.code} (${item.count}x): ${item.sampleMessage}`,
245
- )
246
- : ["- none"]),
223
+ ...listOr(
224
+ baselineSummary.byCode,
225
+ (item) => `- [${item.severity}] ${item.code} (${item.count}x): ${item.sampleMessage}`,
226
+ ),
247
227
  ...(baselineSummary.detailsIncluded
248
228
  ? []
249
229
  : [
@@ -251,46 +231,30 @@ export const renderWorkflowValidationRunReport = (report: WorkflowValidationRunR
251
231
  ]),
252
232
  "",
253
233
  "## Existing Workspace Blocker Details",
254
- ...(report.baselineDiagnostics?.length
255
- ? report.baselineDiagnostics.map(
256
- (diagnostic) => `- [${diagnostic.severity}] ${diagnostic.code}: ${diagnostic.message}`,
257
- )
258
- : baselineSummary.detailsIncluded
259
- ? ["- none"]
260
- : ["- omitted"]),
234
+ ...listOr(report.baselineDiagnostics, renderDiagnostic, baselineSummary.detailsIncluded ? "- none" : "- omitted"),
261
235
  "",
262
236
  "## Known Blockers",
263
- ...(report.knownBlockers.length
264
- ? report.knownBlockers.map((blocker) => {
265
- const command = blocker.command ? ` \`${blocker.command}\`` : "";
266
- const count = blocker.count > 1 ? ` (${blocker.count}x)` : "";
267
- const known = blocker.known ? " known" : "";
268
- return `- [${blocker.failureScope}${known}]${command}${count}: ${blocker.message}`;
269
- })
270
- : ["- none"]),
237
+ ...listOr(report.knownBlockers, (blocker) => {
238
+ const command = blocker.command ? ` \`${blocker.command}\`` : "";
239
+ const count = blocker.count > 1 ? ` (${blocker.count}x)` : "";
240
+ const known = blocker.known ? " known" : "";
241
+ return `- [${blocker.failureScope}${known}]${command}${count}: ${blocker.message}`;
242
+ }),
271
243
  "",
272
244
  "## Commands",
273
- ...(report.commands.length
274
- ? report.commands.map((command) => {
275
- const scope = command.failureScope ? ` (${command.failureScope})` : "";
276
- return `- [${command.status}] \`${command.command}\`${scope}: ${command.reason}`;
277
- })
278
- : ["- none"]),
245
+ ...listOr(report.commands, (command) => {
246
+ const scope = command.failureScope ? ` (${command.failureScope})` : "";
247
+ return `- [${command.status}] \`${command.command}\`${scope}: ${command.reason}`;
248
+ }),
279
249
  "",
280
250
  "## Diagnostics",
281
- ...(report.diagnostics.length
282
- ? report.diagnostics.map((diagnostic) => `- [${diagnostic.severity}] ${diagnostic.code}: ${diagnostic.message}`)
283
- : ["- none"]),
251
+ ...listOr(report.diagnostics, renderDiagnostic),
284
252
  "",
285
253
  "## Repair Actions",
286
- ...(report.repairActions.length
287
- ? report.repairActions.map((action) => `- \`${action.command}\`: ${action.reason}`)
288
- : ["- none"]),
254
+ ...listOr(report.repairActions, renderCommand),
289
255
  "",
290
256
  "## Next Actions",
291
- ...(report.nextActions.length
292
- ? report.nextActions.map((action) => `- \`${action.command}\`: ${action.reason}`)
293
- : ["- none"]),
257
+ ...listOr(report.nextActions, renderCommand),
294
258
  "",
295
259
  ].join("\n");
296
260
  };
@@ -306,19 +270,13 @@ export const renderRepairReportMarkdown = (report: RepairReport) =>
306
270
  `- Target: ${report.target ?? "none"}`,
307
271
  "",
308
272
  "## Commands",
309
- ...(report.commands.length
310
- ? report.commands.map((command) => `- [${command.status}] \`${command.command}\`: ${command.reason}`)
311
- : ["- none"]),
273
+ ...listOr(report.commands, (command) => `- [${command.status}] \`${command.command}\`: ${command.reason}`),
312
274
  "",
313
275
  "## Diagnostics",
314
- ...(report.diagnostics.length
315
- ? report.diagnostics.map((diagnostic) => `- [${diagnostic.severity}] ${diagnostic.code}: ${diagnostic.message}`)
316
- : ["- none"]),
276
+ ...listOr(report.diagnostics, renderDiagnostic),
317
277
  "",
318
278
  "## Next Actions",
319
- ...(report.nextActions.length
320
- ? report.nextActions.map((action) => `- \`${action.command}\`: ${action.reason}`)
321
- : ["- none"]),
279
+ ...listOr(report.nextActions, renderCommand),
322
280
  "",
323
281
  ].join("\n");
324
282
 
@@ -333,3 +291,29 @@ export const renderWorkflowRunArtifact = (artifact: WorkflowRunArtifact, format:
333
291
  }
334
292
  return jsonText(artifact);
335
293
  };
294
+
295
+ export const renderPrimitiveWriteReport = (report: PrimitiveWriteReport) =>
296
+ [
297
+ `# Primitive Write: ${report.command}`,
298
+ "",
299
+ `- Status: ${report.status}`,
300
+ "",
301
+ "## Changed Files",
302
+ ...listOr(report.changedFiles, renderFile),
303
+ "",
304
+ "## Generated Files",
305
+ ...listOr(report.generatedFiles, renderFile),
306
+ "",
307
+ "## Validation Commands",
308
+ ...listOr(report.validationCommands, renderCommand),
309
+ "",
310
+ "## Diagnostics",
311
+ ...listOr(report.diagnostics, renderDiagnostic),
312
+ "",
313
+ "## Next Actions",
314
+ ...listOr(report.nextActions, renderCommand),
315
+ "",
316
+ ].join("\n");
317
+
318
+ export const renderPrimitiveReport = (report: PrimitiveWriteReport, format: PrimitiveFormat = "markdown") =>
319
+ format === "json" ? jsonText(report) : renderPrimitiveWriteReport(report);
@@ -1,13 +1,23 @@
1
+ import { lowerlize } from "akanjs/common";
1
2
  import ts from "typescript";
2
3
  import type { Sys } from "../commandDecorators";
3
4
  import { generatedFilePathsForTarget } from "./artifacts";
4
- import { createPrimitiveWriteReport } from "./primitive";
5
+ import {
6
+ callExpressionName,
7
+ expressionName,
8
+ firstObjectReturnedByArrow,
9
+ heritageCall,
10
+ nodeName,
11
+ propertyName,
12
+ sourceFileFor,
13
+ } from "./sourceAst";
5
14
  import type {
6
15
  PrimitiveChangedFile,
7
16
  PrimitiveFileMap,
8
17
  PrimitiveGeneratedFile,
9
18
  PrimitiveNextAction,
10
19
  PrimitiveValidationCommand,
20
+ PrimitiveWriteReport,
11
21
  WorkflowDiagnostic,
12
22
  } from "./types";
13
23
 
@@ -57,6 +67,27 @@ export const nextActionsForTarget = (target: string) =>
57
67
  { command: `akan lint ${target}`, reason: "Validate the target after generated files are refreshed." },
58
68
  ] satisfies PrimitiveNextAction[];
59
69
 
70
+ export const createPrimitiveWriteReport = ({
71
+ command,
72
+ status,
73
+ changedFiles = [],
74
+ generatedFiles = [],
75
+ validationCommands = [],
76
+ diagnostics = [],
77
+ nextActions = [],
78
+ }: Omit<PrimitiveWriteReport, "schemaVersion" | "status"> & {
79
+ status?: PrimitiveWriteReport["status"];
80
+ }): PrimitiveWriteReport => ({
81
+ schemaVersion: 1,
82
+ command,
83
+ status: status ?? (diagnostics.some((diagnostic) => diagnostic.severity === "error") ? "failed" : "passed"),
84
+ changedFiles,
85
+ generatedFiles,
86
+ validationCommands,
87
+ diagnostics,
88
+ nextActions,
89
+ });
90
+
60
91
  export const createPassedPrimitiveReport = ({
61
92
  command,
62
93
  changedFiles,
@@ -90,8 +121,6 @@ export const titleize = (value: string) =>
90
121
  .replace(/[-_]+/g, " ")
91
122
  .replace(/\b\w/g, (char) => char.toUpperCase());
92
123
 
93
- export const lowerlize = (value: string) => `${value.slice(0, 1).toLowerCase()}${value.slice(1)}`;
94
-
95
124
  const koLabels: Record<string, string> = {
96
125
  amount: "금액",
97
126
  budget: "예산",
@@ -379,9 +408,6 @@ export interface AkanDictionaryStructure {
379
408
  fields: string[];
380
409
  }
381
410
 
382
- const sourceFileFor = (fileName: string, content: string, scriptKind = ts.ScriptKind.TS) =>
383
- ts.createSourceFile(fileName, content, ts.ScriptTarget.Latest, true, scriptKind);
384
-
385
411
  const hasParseDiagnostics = (source: ts.SourceFile) =>
386
412
  ((source as ts.SourceFile & { parseDiagnostics?: readonly ts.Diagnostic[] }).parseDiagnostics ?? []).length > 0;
387
413
 
@@ -398,52 +424,6 @@ const lineEndAt = (content: string, position: number) => {
398
424
  const lineIndentAt = (content: string, position: number) =>
399
425
  /^[ \t]*/.exec(content.slice(lineStartAt(content, position)))?.[0] ?? "";
400
426
 
401
- const nodeName = (node: ts.PropertyName | ts.BindingName | undefined) => {
402
- if (!node) return null;
403
- if (ts.isIdentifier(node) || ts.isStringLiteral(node) || ts.isNumericLiteral(node)) return node.text;
404
- return null;
405
- };
406
-
407
- const propertyName = (node: ts.ObjectLiteralElementLike) =>
408
- ts.isPropertyAssignment(node) || ts.isShorthandPropertyAssignment(node) || ts.isMethodDeclaration(node)
409
- ? nodeName(node.name)
410
- : null;
411
-
412
- const expressionName = (expression: ts.Expression): string | null => {
413
- if (ts.isIdentifier(expression)) return expression.text;
414
- if (ts.isPropertyAccessExpression(expression)) return expression.name.text;
415
- if (ts.isCallExpression(expression)) return expressionName(expression.expression);
416
- if (ts.isAsExpression(expression)) return expressionName(expression.expression);
417
- return null;
418
- };
419
-
420
- const firstObjectReturnedByArrow = (node: ts.Node): ts.ObjectLiteralExpression | null => {
421
- if (!ts.isArrowFunction(node) && !ts.isFunctionExpression(node)) return null;
422
- if (ts.isObjectLiteralExpression(node.body)) return node.body;
423
- if (ts.isParenthesizedExpression(node.body) && ts.isObjectLiteralExpression(node.body.expression)) {
424
- return node.body.expression;
425
- }
426
- if (!ts.isBlock(node.body)) return null;
427
- for (const statement of node.body.statements) {
428
- if (ts.isReturnStatement(statement) && statement.expression && ts.isObjectLiteralExpression(statement.expression)) {
429
- return statement.expression;
430
- }
431
- }
432
- return null;
433
- };
434
-
435
- const isViaCall = (expression: ts.Expression) =>
436
- ts.isCallExpression(expression) && expressionName(expression.expression) === "via";
437
-
438
- const heritageCall = (node: ts.ClassDeclaration) => {
439
- const heritage = node.heritageClauses?.flatMap((clause) => [...clause.types]) ?? [];
440
- const expression = heritage.find((clause) => isViaCall(clause.expression))?.expression;
441
- return expression && ts.isCallExpression(expression) ? expression : null;
442
- };
443
-
444
- const callExpressionName = (node: ts.CallExpression) =>
445
- ts.isPropertyAccessExpression(node.expression) ? node.expression.name.text : expressionName(node.expression);
446
-
447
427
  const locatedObject = (source: ts.SourceFile, objectLiteral: ts.ObjectLiteralExpression): ObjectInsertionLocator => ({
448
428
  objectStart: objectLiteral.getStart(source),
449
429
  objectEnd: objectLiteral.getEnd(),
@@ -869,8 +849,6 @@ export const parseValues = (value: string | null) =>
869
849
  .map((item) => item.trim())
870
850
  .filter(Boolean) ?? [];
871
851
 
872
- // ---- Service / signal insertion (add-mutation, add-slice) ----
873
-
874
852
  export const hasSourceParseErrors = (content: string, fileName = "source.ts") =>
875
853
  hasParseDiagnostics(sourceFileFor(fileName, content));
876
854
 
@@ -988,8 +966,7 @@ export const insertSignalFactoryEntry = (
988
966
  if (!located) return null;
989
967
  const locator = locatedObject(source, located.object);
990
968
  if (locator.fields.some((field) => field.name === entryName)) return content;
991
- // Compute the param edit first (its offsets precede the object), but apply it last so the
992
- // object splice (at a higher offset) does not invalidate the param region positions.
969
+ // Apply the param edit last: it sits before the object, so applying it first would shift the object's offsets.
993
970
  const paramEdit = factoryParamEdit(content, source, located.arrow, param);
994
971
  if (paramEdit === null) return null;
995
972
  const withEntry = insertOrderedFieldLine(content, locator, entryName, entryLine, {
@@ -0,0 +1,50 @@
1
+ import ts from "typescript";
2
+
3
+ export const sourceFileFor = (fileName: string, content: string) =>
4
+ ts.createSourceFile(fileName, content, ts.ScriptTarget.Latest, true, ts.ScriptKind.TS);
5
+
6
+ export const nodeName = (node: ts.PropertyName | ts.BindingName | undefined) => {
7
+ if (!node) return null;
8
+ if (ts.isIdentifier(node) || ts.isStringLiteral(node) || ts.isNumericLiteral(node)) return node.text;
9
+ return null;
10
+ };
11
+
12
+ export const propertyName = (node: ts.ObjectLiteralElementLike) =>
13
+ ts.isPropertyAssignment(node) || ts.isShorthandPropertyAssignment(node) || ts.isMethodDeclaration(node)
14
+ ? nodeName(node.name)
15
+ : null;
16
+
17
+ export const expressionName = (expression: ts.Expression): string | null => {
18
+ if (ts.isIdentifier(expression)) return expression.text;
19
+ if (ts.isPropertyAccessExpression(expression)) return expression.name.text;
20
+ if (ts.isCallExpression(expression)) return expressionName(expression.expression);
21
+ if (ts.isAsExpression(expression)) return expressionName(expression.expression);
22
+ return null;
23
+ };
24
+
25
+ export const callExpressionName = (node: ts.CallExpression) =>
26
+ ts.isPropertyAccessExpression(node.expression) ? node.expression.name.text : expressionName(node.expression);
27
+
28
+ export const firstObjectReturnedByArrow = (node: ts.Node): ts.ObjectLiteralExpression | null => {
29
+ if (!ts.isArrowFunction(node) && !ts.isFunctionExpression(node)) return null;
30
+ if (ts.isObjectLiteralExpression(node.body)) return node.body;
31
+ if (ts.isParenthesizedExpression(node.body) && ts.isObjectLiteralExpression(node.body.expression)) {
32
+ return node.body.expression;
33
+ }
34
+ if (!ts.isBlock(node.body)) return null;
35
+ for (const statement of node.body.statements) {
36
+ if (ts.isReturnStatement(statement) && statement.expression && ts.isObjectLiteralExpression(statement.expression)) {
37
+ return statement.expression;
38
+ }
39
+ }
40
+ return null;
41
+ };
42
+
43
+ const isViaCall = (expression: ts.Expression) =>
44
+ ts.isCallExpression(expression) && expressionName(expression.expression) === "via";
45
+
46
+ export const heritageCall = (node: ts.ClassDeclaration) => {
47
+ const heritage = node.heritageClauses?.flatMap((clause) => [...clause.types]) ?? [];
48
+ const expression = heritage.find((clause) => isViaCall(clause.expression))?.expression;
49
+ return expression && ts.isCallExpression(expression) ? expression : null;
50
+ };
@@ -1,16 +1,9 @@
1
- /**
2
- * App/lib 루트 레이아웃 허용 목록 — 단일 소스.
3
- *
4
- * 같은 규칙을 scanInfo(`akan sync`, hard error) · akanContext(`akan doctor`, diagnostic) ·
5
- * qualityScanner(`akan quality scan`, warning) 세 곳이 각자 복사해 두면서 실제로 어긋났다
6
- * (스코프 AGENTS.md/CLAUDE.md 는 sync 만 허용, `plugin` 은 문서에만, `secrets` 는 doctor 만 거부).
7
- * 규칙을 추가할 때는 이 파일만 고치고, 루트 AGENTS.md 의 목록도 같이 갱신한다.
8
- */
1
+ // The one source of the app/lib root allowlists (sync, doctor and quality scan read it); mirror a change into the
2
+ // root AGENTS.md list.
9
3
 
10
4
  export type SysType = "app" | "lib";
11
5
 
12
6
  export const appRootAllowedFiles = new Set([
13
- // 스코프 에이전트 가이드 — scan(write) 이 유지하는 색인 + 마커 밖 hand-written 내용 (agentsIndex.ts)
14
7
  "AGENTS.md",
15
8
  "CLAUDE.md",
16
9
  "akan.app.json",
@@ -43,12 +36,7 @@ export const appRootAllowedDirs = new Set([
43
36
  "webkit",
44
37
  ]);
45
38
 
46
- /**
47
- * 라이브러리 루트는 앱 루트에서 실행/모바일 전용 항목(`main.ts`, `capacitor.config.ts`, `.akan`,
48
- * `android`, `ios`, `mobile`, `script`, `secrets`)을 뺀 집합이다 — 라이브러리는 부팅되지도, 패키징되지도
49
- * 않는다. `index.ts` 는 lib 에만 생성되고, `README.md` / `tsconfig.spec.json` 은 라이브러리가 패키지로
50
- * 배포되기 때문에 남는다. `public` / `private` 은 syncAssets 가 앱으로 심링크하므로 라이브러리도 가진다.
51
- */
39
+ // A library is never booted or packaged as an app, so the run/mobile entries are out; it is published, so README stays.
52
40
  export const libRootAllowedFiles = new Set([
53
41
  "AGENTS.md",
54
42
  "CLAUDE.md",
@@ -98,9 +86,6 @@ const libFacetRootTestPattern = /^[A-Za-z][A-Za-z0-9_-]*\.signal\.(test|spec)\.(
98
86
  export const isAllowedLibFacetRootFile = (filename: string) =>
99
87
  libFacetRootAllowedFiles.has(filename) || libFacetRootTestPattern.test(filename);
100
88
 
101
- /**
102
- * scanSync 는 루트를 `Bun.Glob("*")` 로 읽어 dotfile 을 아예 보지 못한다. 디렉터리를 직접 읽는
103
- * doctor 가 그 차이만큼 `.DS_Store` 같은 툴 산출물을 에러로 올리므로 같은 기준으로 걸러낸다.
104
- */
89
+ // scanSync reads roots through `Bun.Glob("*")`, which skips dotfiles, so doctor skips them the same way.
105
90
  export const isScannedRootEntry = (type: SysType, name: string) =>
106
91
  !name.startsWith(".") || rootAllowedDirs[type].has(name);
@@ -1,7 +0,0 @@
1
- import type { Cls } from "./types";
2
-
3
- export const Commands = () => {
4
- return (target: Cls) => {
5
- // not implemented yet
6
- };
7
- };