@akanjs/devkit 3.0.0-beta.9 → 3.0.0

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 (341) hide show
  1. package/DEV_RUNTIME_KNOBS.md +44 -26
  2. package/README.ko.md +21 -7
  3. package/README.md +17 -8
  4. package/abstractDoc.test.ts +6 -15
  5. package/abstractDoc.ts +6 -8
  6. package/agentsIndex.test.ts +4 -7
  7. package/agentsIndex.ts +6 -22
  8. package/akanApp/BackendImportGraph.test.ts +8 -22
  9. package/akanApp/BackendImportGraph.ts +1 -20
  10. package/akanApp/akanApp.host.test.ts +56 -3
  11. package/akanApp/akanApp.host.ts +218 -278
  12. package/akanApp/devBootLatch.test.ts +39 -0
  13. package/akanApp/devBootLatch.ts +39 -0
  14. package/akanApp/devHostPolicy.test.ts +81 -35
  15. package/akanApp/devHostPolicy.ts +41 -102
  16. package/akanConfig/akanBin.test.ts +62 -0
  17. package/akanConfig/akanBin.ts +78 -0
  18. package/akanConfig/akanConfig.test.ts +439 -141
  19. package/akanConfig/akanConfig.ts +359 -275
  20. package/akanConfig/index.ts +1 -0
  21. package/akanConfig/types.ts +22 -8
  22. package/akanContext.ts +110 -160
  23. package/akanMcpContract.ts +28 -73
  24. package/appSelectionMemory.ts +1 -4
  25. package/applicationBuildReporter.ts +37 -5
  26. package/applicationBuildRunner.test.ts +41 -5
  27. package/applicationBuildRunner.ts +71 -63
  28. package/applicationTestPreload.ts +3 -12
  29. package/artifact/implicitRootLayout.test.ts +32 -30
  30. package/artifact/implicitRootLayout.ts +58 -106
  31. package/artifact/routeSeedIndex.test.ts +7 -6
  32. package/artifact/routeSeedIndex.ts +11 -44
  33. package/biome.base.json +35 -2
  34. package/biomeBase.ts +2 -5
  35. package/cloud/cloudApi.test.ts +202 -0
  36. package/cloud/cloudApi.ts +77 -39
  37. package/cloud/configLock.test.ts +64 -0
  38. package/cloud/configLock.ts +96 -0
  39. package/cloud/constants.ts +26 -5
  40. package/cloud/globalConfig.ts +71 -32
  41. package/codeAgent/agent/AkanCodeServices.ts +97 -0
  42. package/codeAgent/agent/AkanContextFiles.ts +94 -0
  43. package/codeAgent/agent/CodeAgent.ts +551 -0
  44. package/codeAgent/agent/CodeAgentAsks.ts +66 -0
  45. package/codeAgent/agent/CodeAgentEventMapper.ts +225 -0
  46. package/codeAgent/agent/CodeAgentGate.ts +93 -0
  47. package/codeAgent/agent/CodeAgentProxy.ts +82 -0
  48. package/codeAgent/agent/CodeAgentRpcHost.ts +103 -0
  49. package/codeAgent/agent/CodeAgentRpcListener.ts +109 -0
  50. package/codeAgent/agent/CodeAgentStreamPrinter.ts +86 -0
  51. package/codeAgent/agent/CodeAgentSuspended.ts +85 -0
  52. package/codeAgent/agent/CodeAgentUi.ts +47 -0
  53. package/codeAgent/agent/CodeMailbox.ts +148 -0
  54. package/codeAgent/agent/CodeSessionFork.ts +56 -0
  55. package/codeAgent/agent/CodeSessionIndex.ts +78 -0
  56. package/codeAgent/agent/SubagentPool.ts +214 -0
  57. package/codeAgent/agent/akanCodeModel.ts +47 -0
  58. package/codeAgent/agent/akanCodePaths.ts +31 -0
  59. package/codeAgent/agent/akanContextFiles.test.ts +114 -0
  60. package/codeAgent/agent/akanSystemPrompt.ts +46 -0
  61. package/codeAgent/agent/codeAgent.test.ts +178 -0
  62. package/codeAgent/agent/codeAgentContract.test.ts +523 -0
  63. package/codeAgent/agent/codeAgentProxy.test.ts +74 -0
  64. package/codeAgent/agent/codeAgentRpcListener.test.ts +113 -0
  65. package/codeAgent/agent/codeAgentSuspended.test.ts +47 -0
  66. package/codeAgent/agent/codeMailbox.test.ts +76 -0
  67. package/codeAgent/agent/codeSessionFork.test.ts +58 -0
  68. package/codeAgent/agent/codeSessionIndex.test.ts +77 -0
  69. package/codeAgent/agent/consoleToStderr.ts +8 -0
  70. package/codeAgent/agent/jsonLines.ts +29 -0
  71. package/codeAgent/agent/stringArg.ts +5 -0
  72. package/codeAgent/feedback/DevLogFeedback.ts +82 -0
  73. package/codeAgent/feedback/PreviewChrome.ts +82 -0
  74. package/codeAgent/feedback/PreviewFeedback.ts +46 -0
  75. package/codeAgent/feedback/PreviewView.ts +95 -0
  76. package/codeAgent/feedback/TurnFeedback.ts +98 -0
  77. package/codeAgent/feedback/VerifyFeedback.ts +40 -0
  78. package/codeAgent/feedback/previewChrome.test.ts +73 -0
  79. package/codeAgent/index.ts +34 -0
  80. package/codeAgent/skills/akan-code/SKILL.md +130 -0
  81. package/codeAgent/skills/akan-debug/SKILL.md +42 -0
  82. package/codeAgent/skills/akan-deps/SKILL.md +50 -0
  83. package/codeAgent/skills/akan-err/SKILL.md +39 -0
  84. package/codeAgent/skills/akan-module/SKILL.md +45 -0
  85. package/codeAgent/skills/akan-scaffold/SKILL.md +76 -0
  86. package/codeAgent/skills/akan-scalar/SKILL.md +52 -0
  87. package/codeAgent/skills/akan-service/SKILL.md +34 -0
  88. package/codeAgent/skills/akan-store/SKILL.md +67 -0
  89. package/codeAgent/skills/akan-ui/SKILL.md +63 -0
  90. package/codeAgent/skills/akan-validate/SKILL.md +44 -0
  91. package/codeAgent/tools/AkanCli.ts +50 -0
  92. package/codeAgent/tools/AkanCodePlugins.ts +136 -0
  93. package/codeAgent/tools/AkanEditScope.ts +48 -0
  94. package/codeAgent/tools/AkanEnvKeys.ts +50 -0
  95. package/codeAgent/tools/AkanToolPack.ts +92 -0
  96. package/codeAgent/tools/AkanVerifier.ts +48 -0
  97. package/codeAgent/tools/AskToolPack.ts +99 -0
  98. package/codeAgent/tools/MailToolPack.ts +76 -0
  99. package/codeAgent/tools/McpClient.ts +187 -0
  100. package/codeAgent/tools/McpOAuth.ts +233 -0
  101. package/codeAgent/tools/McpOAuthLoopback.ts +64 -0
  102. package/codeAgent/tools/McpServerConfig.ts +129 -0
  103. package/codeAgent/tools/McpSignIn.ts +158 -0
  104. package/codeAgent/tools/McpTokenStore.ts +81 -0
  105. package/codeAgent/tools/McpToolPack.ts +199 -0
  106. package/codeAgent/tools/SessionToolPack.ts +91 -0
  107. package/codeAgent/tools/WebToolPack.ts +147 -0
  108. package/codeAgent/tools/akanCodePlugins.test.ts +677 -0
  109. package/codeAgent/tools/askToolPack.test.ts +116 -0
  110. package/codeAgent/tools/mcpOAuth.test.ts +612 -0
  111. package/codeAgent/tools/mcpServerConfig.test.ts +212 -0
  112. package/codegenLock.test.ts +4 -13
  113. package/codegenLock.ts +3 -17
  114. package/commandDecorators/argMeta.ts +1 -5
  115. package/commandDecorators/camelToKebabCase.ts +1 -0
  116. package/commandDecorators/command.ts +46 -102
  117. package/commandDecorators/commandBuilder.ts +30 -61
  118. package/commandDecorators/commandDecorators.test.ts +2 -10
  119. package/commandDecorators/dependencyBuilder.ts +10 -1
  120. package/commandDecorators/helpFormatter.ts +68 -145
  121. package/commandDecorators/index.ts +0 -2
  122. package/commandDecorators/targetMeta.ts +9 -7
  123. package/createTunnel.ts +7 -14
  124. package/csrE2e/csrE2eHarness.fixture.ts +505 -0
  125. package/csrE2e/csrFastRefresh.e2e.test.ts +233 -0
  126. package/csrE2e/csrPageStack.e2e.test.ts +252 -0
  127. package/csrE2e/csrPatchFirst.e2e.test.ts +87 -0
  128. package/csrE2e/csrRootLayout.e2e.test.ts +134 -0
  129. package/csrE2e/devHmrLatency.e2e.test.ts +391 -0
  130. package/csrE2e/registryVendor.e2e.test.ts +221 -0
  131. package/csrE2e/ssrRegistry.e2e.test.ts +644 -0
  132. package/dependencyScanner.test.ts +4 -16
  133. package/dependencyScanner.ts +31 -80
  134. package/devkitUtils.test.ts +48 -20
  135. package/executors.linkFallback.test.ts +103 -0
  136. package/executors.test.ts +176 -106
  137. package/executors.ts +212 -498
  138. package/fileEditor.ts +9 -37
  139. package/{getDirname.test.ts → fileSys.test.ts} +5 -3
  140. package/fileSys.ts +22 -9
  141. package/formSetterScanner.ts +3 -16
  142. package/frontendBuild/allRoutesBuilder.ts +7 -21
  143. package/frontendBuild/autoImportSync.test.ts +27 -38
  144. package/frontendBuild/autoImportSync.ts +31 -65
  145. package/frontendBuild/buildRouteClient.test.ts +123 -37
  146. package/frontendBuild/bundleDefine.test.ts +12 -0
  147. package/frontendBuild/bundleDefine.ts +16 -0
  148. package/frontendBuild/clientBuildTypes.ts +5 -36
  149. package/frontendBuild/clientEntriesBundler.ts +9 -40
  150. package/frontendBuild/clientEntryDiscovery.test.ts +112 -0
  151. package/frontendBuild/clientEntryDiscovery.ts +122 -110
  152. package/frontendBuild/csrArtifactBuilder.ts +9 -31
  153. package/frontendBuild/csrDevArtifactWriter.test.ts +151 -0
  154. package/frontendBuild/csrDevArtifactWriter.ts +307 -0
  155. package/frontendBuild/csrDevBundler.test.ts +66 -0
  156. package/frontendBuild/csrDevBundler.ts +95 -0
  157. package/frontendBuild/csrDevFactoryCheck.test.ts +33 -0
  158. package/frontendBuild/csrDevFactoryCheck.ts +39 -0
  159. package/frontendBuild/csrDevModuleCompiler.test.ts +293 -0
  160. package/frontendBuild/csrDevModuleCompiler.ts +422 -0
  161. package/frontendBuild/csrDevPatcher.test.ts +572 -0
  162. package/frontendBuild/csrDevPatcher.ts +548 -0
  163. package/frontendBuild/csrDevPaths.ts +145 -0
  164. package/frontendBuild/csrDevResolver.test.ts +239 -0
  165. package/frontendBuild/csrDevResolver.ts +363 -0
  166. package/frontendBuild/csrDevTypes.ts +61 -0
  167. package/frontendBuild/csrEntryFiles.ts +24 -0
  168. package/frontendBuild/cssCandidateCache.test.ts +0 -6
  169. package/frontendBuild/cssCandidateCache.ts +6 -41
  170. package/frontendBuild/cssCompiler.ts +31 -82
  171. package/frontendBuild/cssImportResolver.ts +6 -19
  172. package/frontendBuild/devChangePlanner.ts +7 -10
  173. package/frontendBuild/devGeneratedIndexSync.test.ts +12 -27
  174. package/frontendBuild/devGeneratedIndexSync.ts +1 -4
  175. package/frontendBuild/devRegistryBundler.ts +192 -0
  176. package/frontendBuild/fontOptimizer.test.ts +6 -14
  177. package/frontendBuild/fontOptimizer.ts +15 -72
  178. package/frontendBuild/fontPruner.test.ts +5 -10
  179. package/frontendBuild/fontPruner.ts +5 -20
  180. package/frontendBuild/frontendBuild.test.ts +57 -70
  181. package/frontendBuild/hmrChangeClassifier.ts +2 -2
  182. package/frontendBuild/hmrWatcher.test.ts +59 -32
  183. package/frontendBuild/hmrWatcher.ts +31 -74
  184. package/frontendBuild/index.ts +4 -0
  185. package/frontendBuild/pagesBundleBuilder.ts +28 -30
  186. package/frontendBuild/pagesEntrySourceGenerator.ts +56 -6
  187. package/frontendBuild/precompressArtifacts.ts +5 -18
  188. package/frontendBuild/registryMetadataFingerprint.test.ts +63 -0
  189. package/frontendBuild/registryMetadataFingerprint.ts +28 -0
  190. package/frontendBuild/routeClientBuilder.ts +100 -62
  191. package/frontendBuild/routesManifestArtifactSerializer.ts +5 -36
  192. package/frontendBuild/serverGraphFile.test.ts +77 -0
  193. package/frontendBuild/serverGraphFile.ts +93 -0
  194. package/frontendBuild/sourceMtimeIndex.test.ts +41 -97
  195. package/frontendBuild/sourceMtimeIndex.ts +19 -130
  196. package/frontendBuild/ssrBaseArtifactBuilder.ts +19 -13
  197. package/frontendBuild/ssrDevBundler.ts +85 -0
  198. package/frontendBuild/styleContract.ts +1 -8
  199. package/frontendBuild/themeValidator.ts +4 -26
  200. package/frontendBuild/watchRootResolver.test.ts +23 -42
  201. package/frontendBuild/watchRootResolver.ts +5 -27
  202. package/getRelatedCnsts.ts +12 -92
  203. package/incrementalBuilder/buildBatch.proc.ts +147 -63
  204. package/incrementalBuilder/buildBatchProtocol.ts +13 -38
  205. package/incrementalBuilder/buildBatchRunner.test.ts +65 -0
  206. package/incrementalBuilder/buildBatchRunner.ts +26 -38
  207. package/incrementalBuilder/builderChannel.test.ts +24 -49
  208. package/incrementalBuilder/builderChannel.ts +6 -37
  209. package/incrementalBuilder/builderRequestRouter.test.ts +0 -8
  210. package/incrementalBuilder/builderRequestRouter.ts +5 -24
  211. package/incrementalBuilder/builderWorkQueue.test.ts +94 -0
  212. package/incrementalBuilder/builderWorkQueue.ts +126 -0
  213. package/incrementalBuilder/devWatchBatch.test.ts +21 -24
  214. package/incrementalBuilder/incrementalBuilder.host.test.ts +53 -56
  215. package/incrementalBuilder/incrementalBuilder.host.ts +68 -81
  216. package/incrementalBuilder/incrementalBuilder.proc.test.ts +270 -0
  217. package/incrementalBuilder/incrementalBuilder.proc.ts +596 -208
  218. package/index.ts +5 -19
  219. package/integration/devResourceProbe.ts +50 -56
  220. package/integration/devStability.integration.test.ts +105 -199
  221. package/integration/devStabilityHarness.test.ts +13 -28
  222. package/integration/devStabilityHarness.ts +65 -330
  223. package/integration/ssrMemoryProbe.ts +41 -167
  224. package/libSource.test.ts +4 -15
  225. package/libSource.ts +19 -31
  226. package/lint/__fixtures__/no-document-cookie/bad.tsx +4 -0
  227. package/lint/__fixtures__/no-document-cookie/good.tsx +7 -0
  228. package/lint/__fixtures__/no-web-only-api-outside-webkit/bad.tsx +7 -0
  229. package/lint/__fixtures__/no-web-only-api-outside-webkit/good.tsx +11 -0
  230. package/lint/__fixtures__/no-web-storage/bad.tsx +6 -0
  231. package/lint/__fixtures__/no-web-storage/good.tsx +7 -0
  232. package/lint/gritRules.test.ts +8 -24
  233. package/lint/no-deep-internal-import.grit +2 -2
  234. package/lint/no-document-cookie.grit +29 -0
  235. package/lint/no-redeclare-predefined-endpoint.grit +2 -1
  236. package/lint/no-web-only-api-outside-webkit.grit +44 -0
  237. package/lint/no-web-storage.grit +30 -0
  238. package/linter.test.ts +1 -2
  239. package/linter.ts +26 -116
  240. package/mobile/desktopBin.test.ts +182 -0
  241. package/mobile/desktopBin.ts +144 -0
  242. package/mobile/desktopServerStage.test.ts +252 -0
  243. package/mobile/desktopServerStage.ts +178 -0
  244. package/mobile/index.ts +9 -0
  245. package/mobile/mobileTarget.test.ts +57 -8
  246. package/mobile/mobileTarget.ts +45 -26
  247. package/mobile/nativeApi.test.ts +30 -0
  248. package/mobile/nativeApi.ts +40 -0
  249. package/mobile/nativeApp.test.ts +184 -0
  250. package/mobile/nativeApp.ts +454 -0
  251. package/mobile/nativeAppLine.test.ts +69 -0
  252. package/mobile/nativeAppLine.ts +71 -0
  253. package/mobile/nativeConfig.test.ts +438 -0
  254. package/mobile/nativeConfig.ts +294 -0
  255. package/mobile/nativePluginFolders.test.ts +81 -0
  256. package/mobile/nativePluginFolders.ts +60 -0
  257. package/mobile/nativeWebDir.test.ts +80 -0
  258. package/mobile/nativeWebDir.ts +66 -0
  259. package/mobile/placeholderAppId.test.ts +13 -0
  260. package/mobile/placeholderAppId.ts +16 -0
  261. package/mobile/usageDescriptions.test.ts +27 -0
  262. package/mobile/usageDescriptions.ts +24 -0
  263. package/package.json +11 -11
  264. package/packageExports.test.ts +3 -18
  265. package/packageExportsMap.ts +3 -19
  266. package/piDependencyPins.test.ts +46 -0
  267. package/platformTest/BunTestReport.test.ts +60 -0
  268. package/platformTest/BunTestReport.ts +65 -0
  269. package/platformTest/IdleWatchedProcess.test.ts +56 -0
  270. package/platformTest/IdleWatchedProcess.ts +73 -0
  271. package/platformTest/LinuxDockerTarget.ts +105 -0
  272. package/platformTest/PlatformTestRun.test.ts +64 -0
  273. package/platformTest/PlatformTestRun.ts +141 -0
  274. package/platformTest/PlatformTestTarget.ts +142 -0
  275. package/platformTest/TestSnapshot.test.ts +72 -0
  276. package/platformTest/TestSnapshot.ts +103 -0
  277. package/platformTest/WindowsSshTarget.ts +151 -0
  278. package/prompter.ts +3 -13
  279. package/qualityScanner.test.ts +9 -26
  280. package/qualityScanner.ts +200 -286
  281. package/recipeScanner.test.ts +7 -12
  282. package/recipeScanner.ts +9 -38
  283. package/repoIdentity.ts +2 -9
  284. package/routeSourceValidator.test.ts +0 -1
  285. package/routeSourceValidator.ts +7 -42
  286. package/scanInfo.ts +91 -225
  287. package/selectModel.ts +1 -2
  288. package/semver.ts +3 -5
  289. package/slicePlanner.test.ts +6 -24
  290. package/slicePlanner.ts +11 -32
  291. package/spinner.test.ts +3 -8
  292. package/spinner.ts +4 -16
  293. package/sshTunnel.ts +1 -6
  294. package/ssrScanner.test.ts +1 -6
  295. package/ssrScanner.ts +103 -113
  296. package/stripAnsi.ts +3 -0
  297. package/subspace.test.ts +9 -25
  298. package/subspace.ts +48 -115
  299. package/subspaceConfig.ts +3 -11
  300. package/testHelpers.ts +194 -0
  301. package/transforms/asyncDefaultExportDetector.ts +3 -11
  302. package/transforms/barrelAnalyzer.ts +43 -135
  303. package/transforms/barrelImportsPlugin.ts +38 -175
  304. package/transforms/externalizeFrameworkPlugin.test.ts +39 -118
  305. package/transforms/externalizeFrameworkPlugin.ts +13 -87
  306. package/transforms/moduleSyntax.ts +32 -0
  307. package/transforms/rscUseClientTransform.ts +42 -20
  308. package/transforms/serverEnvPlugin.test.ts +103 -0
  309. package/transforms/serverEnvPlugin.ts +41 -0
  310. package/transforms/transforms.test.ts +166 -44
  311. package/transforms/tsconfigPackageResolver.test.ts +6 -24
  312. package/transforms/useClientBundlePlugin.ts +6 -23
  313. package/typeChecker.ts +7 -63
  314. package/types.ts +2 -0
  315. package/uploadRelease.ts +10 -25
  316. package/workflow/artifacts.ts +18 -0
  317. package/workflow/executor.test.ts +1 -12
  318. package/workflow/executor.ts +101 -110
  319. package/workflow/index.ts +0 -1
  320. package/workflow/moduleIndex.test.ts +4 -15
  321. package/workflow/moduleIndex.ts +9 -49
  322. package/workflow/plan.ts +18 -25
  323. package/workflow/render.ts +80 -96
  324. package/workflow/rolloutGate.test.ts +1 -1
  325. package/workflow/source.ts +52 -69
  326. package/workflow/sourceAst.ts +50 -0
  327. package/workspaceLayout.test.ts +18 -3
  328. package/workspaceLayout.ts +22 -23
  329. package/abstractCompactor.ts +0 -112
  330. package/aiEditor.test.ts +0 -60
  331. package/aiEditor.ts +0 -387
  332. package/applicationReleasePackager.ts +0 -209
  333. package/capacitorApp.test.ts +0 -372
  334. package/capacitorApp.ts +0 -1648
  335. package/commandDecorators/commandMeta.ts +0 -7
  336. package/commandDecorators/types.ts +0 -10
  337. package/getDirname.ts +0 -4
  338. package/guideline.ts +0 -19
  339. package/local/optimize-resource/ipcprobe/child.ts +0 -1
  340. package/local/optimize-resource/ipcprobe/parent.ts +0 -11
  341. package/workflow/primitive.ts +0 -59
@@ -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);
@@ -24,7 +24,7 @@ const workspacePackageManifestPaths = async () => {
24
24
  for (const workspace of workspaces) {
25
25
  const glob = new Bun.Glob(`${workspace}/package.json`);
26
26
  for await (const relativePath of glob.scan({ cwd: workspaceRoot, onlyFiles: true })) {
27
- paths.add(relativePath);
27
+ paths.add(relativePath.split(path.sep).join("/"));
28
28
  }
29
29
  }
30
30
  return [...paths].sort();
@@ -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: "예산",
@@ -160,21 +189,27 @@ export const normalizeFieldType = (typeName: string) => {
160
189
  return normalizedTypes[typeName.toLowerCase()] ?? typeName;
161
190
  };
162
191
 
163
- export const ensureBaseTypeImport = (content: string, typeName: string) => {
164
- if (typeName !== "Int" && typeName !== "Float") return content;
165
- const source = sourceFileFor("constant.ts", content);
166
- const baseImport = findNamedImport(source, "akanjs/base");
167
- if (baseImport) {
168
- if (baseImport.names.includes(typeName)) return content;
169
- const nextNames = [...baseImport.names, typeName].sort();
192
+ export const ensureNamedImport = (content: string, moduleSpecifier: string, name: string) => {
193
+ const source = sourceFileFor("source.ts", content);
194
+ const namedImport = findNamedImport(source, moduleSpecifier);
195
+ if (namedImport) {
196
+ if (namedImport.names.includes(name)) return content;
197
+ const nextNames = [...namedImport.names, name].sort();
170
198
  return spliceText(
171
199
  content,
172
- baseImport.namedBindingsStart,
173
- baseImport.namedBindingsEnd,
200
+ namedImport.namedBindingsStart,
201
+ namedImport.namedBindingsEnd,
174
202
  `{ ${nextNames.join(", ")} }`,
175
203
  );
176
204
  }
177
- return `import { ${typeName} } from "akanjs/base";\n${content}`;
205
+ return `import { ${name} } from "${moduleSpecifier}";\n${content}`;
206
+ };
207
+
208
+ export const ensureBaseImport = (content: string, name: string) => ensureNamedImport(content, "akanjs/base", name);
209
+
210
+ export const ensureBaseTypeImport = (content: string, typeName: string) => {
211
+ if (typeName !== "Int" && typeName !== "Float") return content;
212
+ return ensureBaseImport(content, typeName);
178
213
  };
179
214
 
180
215
  export type FieldDefaultValue = string | number | boolean | null;
@@ -200,12 +235,12 @@ const booleanDefault = (rawDefault: FieldDefaultValue): string | null => {
200
235
  };
201
236
 
202
237
  const dateDefault = (rawDefault: FieldDefaultValue): string | null => {
203
- if (typeof rawDefault === "number" && Number.isFinite(rawDefault)) return `new Date(${rawDefault})`;
238
+ if (typeof rawDefault === "number" && Number.isFinite(rawDefault)) return `() => dayjs(${rawDefault})`;
204
239
  if (typeof rawDefault !== "string") return null;
205
240
  const trimmed = rawDefault.trim();
206
241
  if (!trimmed) return null;
207
- if (trimmed === "now") return "new Date()";
208
- if (!Number.isNaN(Date.parse(trimmed))) return `new Date(${JSON.stringify(trimmed)})`;
242
+ if (trimmed === "now") return "() => dayjs()";
243
+ if (!Number.isNaN(Date.parse(trimmed))) return `() => dayjs(${JSON.stringify(trimmed)})`;
209
244
  return null;
210
245
  };
211
246
 
@@ -373,9 +408,6 @@ export interface AkanDictionaryStructure {
373
408
  fields: string[];
374
409
  }
375
410
 
376
- const sourceFileFor = (fileName: string, content: string, scriptKind = ts.ScriptKind.TS) =>
377
- ts.createSourceFile(fileName, content, ts.ScriptTarget.Latest, true, scriptKind);
378
-
379
411
  const hasParseDiagnostics = (source: ts.SourceFile) =>
380
412
  ((source as ts.SourceFile & { parseDiagnostics?: readonly ts.Diagnostic[] }).parseDiagnostics ?? []).length > 0;
381
413
 
@@ -392,52 +424,6 @@ const lineEndAt = (content: string, position: number) => {
392
424
  const lineIndentAt = (content: string, position: number) =>
393
425
  /^[ \t]*/.exec(content.slice(lineStartAt(content, position)))?.[0] ?? "";
394
426
 
395
- const nodeName = (node: ts.PropertyName | ts.BindingName | undefined) => {
396
- if (!node) return null;
397
- if (ts.isIdentifier(node) || ts.isStringLiteral(node) || ts.isNumericLiteral(node)) return node.text;
398
- return null;
399
- };
400
-
401
- const propertyName = (node: ts.ObjectLiteralElementLike) =>
402
- ts.isPropertyAssignment(node) || ts.isShorthandPropertyAssignment(node) || ts.isMethodDeclaration(node)
403
- ? nodeName(node.name)
404
- : null;
405
-
406
- const expressionName = (expression: ts.Expression): string | null => {
407
- if (ts.isIdentifier(expression)) return expression.text;
408
- if (ts.isPropertyAccessExpression(expression)) return expression.name.text;
409
- if (ts.isCallExpression(expression)) return expressionName(expression.expression);
410
- if (ts.isAsExpression(expression)) return expressionName(expression.expression);
411
- return null;
412
- };
413
-
414
- const firstObjectReturnedByArrow = (node: ts.Node): ts.ObjectLiteralExpression | null => {
415
- if (!ts.isArrowFunction(node) && !ts.isFunctionExpression(node)) return null;
416
- if (ts.isObjectLiteralExpression(node.body)) return node.body;
417
- if (ts.isParenthesizedExpression(node.body) && ts.isObjectLiteralExpression(node.body.expression)) {
418
- return node.body.expression;
419
- }
420
- if (!ts.isBlock(node.body)) return null;
421
- for (const statement of node.body.statements) {
422
- if (ts.isReturnStatement(statement) && statement.expression && ts.isObjectLiteralExpression(statement.expression)) {
423
- return statement.expression;
424
- }
425
- }
426
- return null;
427
- };
428
-
429
- const isViaCall = (expression: ts.Expression) =>
430
- ts.isCallExpression(expression) && expressionName(expression.expression) === "via";
431
-
432
- const heritageCall = (node: ts.ClassDeclaration) => {
433
- const heritage = node.heritageClauses?.flatMap((clause) => [...clause.types]) ?? [];
434
- const expression = heritage.find((clause) => isViaCall(clause.expression))?.expression;
435
- return expression && ts.isCallExpression(expression) ? expression : null;
436
- };
437
-
438
- const callExpressionName = (node: ts.CallExpression) =>
439
- ts.isPropertyAccessExpression(node.expression) ? node.expression.name.text : expressionName(node.expression);
440
-
441
427
  const locatedObject = (source: ts.SourceFile, objectLiteral: ts.ObjectLiteralExpression): ObjectInsertionLocator => ({
442
428
  objectStart: objectLiteral.getStart(source),
443
429
  objectEnd: objectLiteral.getEnd(),
@@ -863,8 +849,6 @@ export const parseValues = (value: string | null) =>
863
849
  .map((item) => item.trim())
864
850
  .filter(Boolean) ?? [];
865
851
 
866
- // ---- Service / signal insertion (add-mutation, add-slice) ----
867
-
868
852
  export const hasSourceParseErrors = (content: string, fileName = "source.ts") =>
869
853
  hasParseDiagnostics(sourceFileFor(fileName, content));
870
854
 
@@ -982,8 +966,7 @@ export const insertSignalFactoryEntry = (
982
966
  if (!located) return null;
983
967
  const locator = locatedObject(source, located.object);
984
968
  if (locator.fields.some((field) => field.name === entryName)) return content;
985
- // Compute the param edit first (its offsets precede the object), but apply it last so the
986
- // 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.
987
970
  const paramEdit = factoryParamEdit(content, source, located.arrow, param);
988
971
  if (paramEdit === null) return null;
989
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
+ };
@@ -6,6 +6,7 @@ import {
6
6
  isScannedRootEntry,
7
7
  libRootAllowedDirs,
8
8
  libRootAllowedFiles,
9
+ rootEntryHintOf,
9
10
  } from "./workspaceLayout";
10
11
 
11
12
  describe("app root layout allowlist", () => {
@@ -15,16 +16,30 @@ describe("app root layout allowlist", () => {
15
16
  });
16
17
 
17
18
  test("admits every documented app root folder", () => {
18
- for (const dirname of ["mobile", "plugin", "secrets", "srvkit", "webkit"]) {
19
+ for (const dirname of [".akan", "plugin", "script", "secrets", "srvkit", "webkit"]) {
19
20
  expect(appRootAllowedDirs.has(dirname)).toBe(true);
20
21
  }
21
22
  });
22
23
 
24
+ test("admits the native plugins an app owns, and a lib too", () => {
25
+ expect(appRootAllowedDirs.has("native")).toBe(true);
26
+ expect(libRootAllowedDirs.has("native")).toBe(true);
27
+ });
28
+
23
29
  test("rejects an app root entry no facet owns", () => {
24
30
  expect(appRootAllowedFiles.has("helper.ts")).toBe(false);
25
31
  expect(appRootAllowedDirs.has("base")).toBe(false);
26
32
  });
27
33
 
34
+ test("names what a Capacitor app kept in its root as a leftover, not an unknown entry", () => {
35
+ for (const name of ["android", "ios", "mobile", "capacitor.config.ts", "capacitor.config.json"]) {
36
+ expect(appRootAllowedFiles.has(name) || appRootAllowedDirs.has(name)).toBe(false);
37
+ expect(rootEntryHintOf("app", name)).toContain(".akan/native/<target>");
38
+ }
39
+ expect(rootEntryHintOf("app", "base")).toBeNull();
40
+ expect(rootEntryHintOf("lib", "ios")).toBeNull();
41
+ });
42
+
28
43
  test("skips dotfile artifacts the sync glob never sees, but keeps .akan", () => {
29
44
  expect(isScannedRootEntry("app", ".DS_Store")).toBe(false);
30
45
  expect(isScannedRootEntry("app", ".akan")).toBe(true);
@@ -48,10 +63,10 @@ describe("lib root layout allowlist", () => {
48
63
  });
49
64
 
50
65
  test("rejects the app-only run and mobile entries", () => {
51
- for (const filename of ["main.ts", "capacitor.config.ts", "akan.app.json"]) {
66
+ for (const filename of ["main.ts", "akan.app.json"]) {
52
67
  expect(libRootAllowedFiles.has(filename)).toBe(false);
53
68
  }
54
- for (const dirname of [".akan", "android", "ios", "mobile", "script", "secrets"]) {
69
+ for (const dirname of [".akan", "script", "secrets"]) {
55
70
  expect(libRootAllowedDirs.has(dirname)).toBe(false);
56
71
  }
57
72
  });