@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,12 +1,11 @@
1
1
  import { describe, expect, test } from "bun:test";
2
- import { mkdtemp, writeFile } from "node:fs/promises";
3
- import { tmpdir } from "node:os";
2
+ import { writeFile } from "node:fs/promises";
4
3
  import path from "node:path";
5
4
  import { collectRecipeSources, findInlineRecipeDuplicates, type RecipeInfo, scanRecipes } from "./recipeScanner";
5
+ import { tempDirs } from "./testHelpers";
6
6
 
7
7
  const byName = (recipes: RecipeInfo[], name: string) => recipes.find((recipe) => recipe.name === name);
8
8
 
9
- // Mirrors pkgs/akanjs/ui/recipe/ (framework) — two recipes in one source, variant + size surfaces.
10
9
  // The scanner is per-source, so a folder of one-recipe files and a legacy multi-recipe file both parse.
11
10
  const FRAMEWORK = `
12
11
  import { recipe, tv } from "./recipeFactory";
@@ -24,7 +23,6 @@ export type ButtonVariants = NonNullable<Parameters<typeof buttonRecipe>[0]>;
24
23
  export const badgeRecipe = recipe(tv({ base: "rounded-full", variants: { variant: { default: "bg-muted", info: "bg-info" } } }));
25
24
  `;
26
25
 
27
- // Mirrors apps/minimal/ui/Recipe/ shapes — base-only (no variants) + single-variant, with per-export JSDoc.
28
26
  const APP = `
29
27
  import { recipe, tv } from "akanjs/ui";
30
28
  /** 전체 화면 배경/전경. 페이지 루트 컨테이너. */
@@ -97,12 +95,7 @@ describe("scanRecipes", () => {
97
95
  });
98
96
  });
99
97
 
100
- // Recipes moved from a flat `ui/Recipe.ts` to a `ui/Recipe/` folder. Three consumers (the AGENTS.md recipe
101
- // index, the recipeGate lint, the MCP module context) go through collectRecipeSources, and every one of them
102
- // degrades silently — empty list, no error — if it stops finding sources. These tests are that alarm.
103
- // The advisory exists to catch a look being re-authored inline. Requiring every base token only matched a
104
- // verbatim copy of the whole base — the one shape that never occurs in practice — so it reported nothing on
105
- // the near-copies it was built for, and silently, being advisory. These tests pin the ratio behaviour.
98
+ // Requiring every base token matched only a verbatim copy, which never occurs; these tests pin the ratio instead.
106
99
  describe("findInlineRecipeDuplicates", () => {
107
100
  // 8 tokens → ceil(8 * 0.7) = 6 must be reproduced.
108
101
  const EIGHT = `export const cardRecipe = recipe(tv({ base: "flex rounded-box border border-border bg-card p-4 text-card-foreground shadow-sm" }));`;
@@ -135,9 +128,11 @@ describe("findInlineRecipeDuplicates", () => {
135
128
  });
136
129
  });
137
130
 
131
+ // The AGENTS.md index, the recipeGate lint and the MCP module context all degrade silently if this finds nothing.
138
132
  describe("collectRecipeSources", () => {
133
+ const makeTempRoot = tempDirs("akan-recipe-");
139
134
  const seed = async (files: Record<string, string>) => {
140
- const root = await mkdtemp(path.join(tmpdir(), "akan-recipe-"));
135
+ const root = await makeTempRoot();
141
136
  for (const [rel, content] of Object.entries(files)) {
142
137
  const abs = path.join(root, rel);
143
138
  await Bun.write(abs, content);
@@ -176,7 +171,7 @@ describe("collectRecipeSources", () => {
176
171
  });
177
172
 
178
173
  test("returns nothing when neither shape exists, without throwing", async () => {
179
- const root = await mkdtemp(path.join(tmpdir(), "akan-recipe-"));
174
+ const root = await makeTempRoot();
180
175
  await writeFile(path.join(root, "placeholder"), "");
181
176
  expect(await collectRecipeSources(path.join(root, "ui"), "@apps/x/ui")).toEqual([]);
182
177
  });
package/recipeScanner.ts CHANGED
@@ -1,11 +1,7 @@
1
1
  import { readdir } from "node:fs/promises";
2
2
  import ts from "typescript";
3
3
 
4
- /**
5
- * A single Akan UI recipe discovered by scanning source. `variants` maps each variant key to its allowed option
6
- * names (e.g. `{ variant: ["primary", "ghost"], size: ["sm", "md"] }`); a base-only recipe has `variants: {}`.
7
- * `importFrom` is the module a consumer imports the recipe from (e.g. `@apps/minimal/ui`).
8
- */
4
+ /** `variants` maps each key to its option names (`{}` for a base-only recipe); `importFrom` is the consumer's module. */
9
5
  export interface RecipeInfo {
10
6
  name: string;
11
7
  importFrom: string;
@@ -22,13 +18,8 @@ export interface RecipeSource {
22
18
  importFrom: string;
23
19
  }
24
20
 
25
- /**
26
- * Collects every recipe source under a `ui` folder. Recipes live one-per-file in a `Recipe/` folder
27
- * (`recipe/` for the framework), so this reads the whole folder; the flat `Recipe.ts` is still read for
28
- * apps that have not moved yet. Every consumer of `scanRecipes` must go through here — three call sites
29
- * (AGENTS.md recipe index, `recipeGate` lint, MCP module context) hardcoded the flat path before, and each
30
- * one fails silently (empty list, no error) when the file is absent.
31
- */
21
+ // One recipe per file under `Recipe/` (`recipe/` for the framework), plus the legacy flat `Recipe.ts`. Every
22
+ // `scanRecipes` consumer goes through here: a hardcoded path fails silently (an empty list) when the file is absent.
32
23
  export const collectRecipeSources = async (
33
24
  uiDirPath: string,
34
25
  importFrom: string,
@@ -51,12 +42,8 @@ export const collectRecipeSources = async (
51
42
  return [flat, ...fromDir].filter((source): source is RecipeSource => !!source);
52
43
  };
53
44
 
54
- /**
55
- * Statically finds every `export const <name> = recipe(tv({ ... }))` across the given sources and extracts its
56
- * variant surface + leading JSDoc one-liner. Detection is by the `recipe(tv(...))` call shape (not by name suffix,
57
- * since base-only recipes like `appScreen` omit the `Recipe` suffix). Being AST-based, it never matches recipe
58
- * definitions that appear only inside string/template literals (e.g. code examples in docs pages).
59
- */
45
+ // Detected by the `recipe(tv(...))` call shape, not the name suffix (base-only recipes like `appScreen` omit it);
46
+ // AST-based, so code examples inside string literals never match.
60
47
  export const scanRecipes = (sources: RecipeSource[]): RecipeInfo[] => {
61
48
  const recipes: RecipeInfo[] = [];
62
49
  for (const source of sources) {
@@ -73,12 +60,13 @@ export const scanRecipes = (sources: RecipeSource[]): RecipeInfo[] => {
73
60
  if (!ts.isIdentifier(declaration.name) || !declaration.initializer) continue;
74
61
  const parsed = parseRecipeCall(declaration.initializer);
75
62
  if (!parsed) continue;
63
+ const doc = getLeadingDoc(source.content, statement);
76
64
  recipes.push({
77
65
  name: declaration.name.text,
78
66
  importFrom: source.importFrom,
79
67
  variants: parsed.variants,
80
68
  ...(parsed.defaultVariants ? { defaultVariants: parsed.defaultVariants } : {}),
81
- ...(getLeadingDoc(source.content, statement) ? { doc: getLeadingDoc(source.content, statement) } : {}),
69
+ ...(doc ? { doc } : {}),
82
70
  ...(parsed.base ? { base: parsed.base } : {}),
83
71
  });
84
72
  }
@@ -90,7 +78,6 @@ export const scanRecipes = (sources: RecipeSource[]): RecipeInfo[] => {
90
78
  const isExported = (statement: ts.VariableStatement): boolean =>
91
79
  statement.modifiers?.some((modifier) => modifier.kind === ts.SyntaxKind.ExportKeyword) ?? false;
92
80
 
93
- /** Matches `recipe( tv( <ObjectLiteral> ) )` and returns the variant surface, or null for anything else. */
94
81
  const parseRecipeCall = (
95
82
  initializer: ts.Expression,
96
83
  ): { variants: Record<string, string[]>; defaultVariants?: Record<string, string>; base?: string } | null => {
@@ -148,27 +135,12 @@ export interface RecipeDuplicate {
148
135
  className: string;
149
136
  }
150
137
 
151
- /**
152
- * Fraction of a recipe's base tokens an inline className must reproduce to count as a duplicate.
153
- *
154
- * Requiring *every* token (the original rule) only caught a verbatim copy of the whole base, which is the one
155
- * form of duplication that essentially never happens: someone re-authoring a look reproduces the gist, not all
156
- * eight tokens. So the check passed on exactly the near-duplicates it existed to find, and silently — it is an
157
- * advisory, so nothing went red. A ratio catches those; false positives are cheap here for the same reason.
158
- */
138
+ // A re-authored look reproduces the gist, not every token, so a ratio (not all tokens) catches near-duplicates.
159
139
  const DUPLICATE_TOKEN_RATIO = 0.7;
160
140
 
161
- /** Minimum base tokens for a recipe to be worth fingerprinting at all. */
162
141
  const MIN_FINGERPRINT_TOKENS = 3;
163
142
 
164
- /**
165
- * SSOT advisory: finds JSX `className` string values that hand-rewrite a recipe's base fingerprint instead of
166
- * consuming the recipe. Only recipes whose base has 3+ distinctive tokens are checked — shorter fingerprints
167
- * (`grid gap-3` …) are generic utilities and would flood the report with false positives. A className counts as
168
- * a duplicate once it reproduces {@link DUPLICATE_TOKEN_RATIO} of those tokens, so a near-copy that drops or
169
- * swaps one still reports. AST-scoped to real `className` attributes, so class strings inside doc-example
170
- * template literals never match.
171
- */
143
+ // Shorter fingerprints (`grid gap-3`) are generic utilities that would flood the report with false positives.
172
144
  export const findInlineRecipeDuplicates = (
173
145
  recipes: RecipeInfo[],
174
146
  files: { path: string; content: string }[],
@@ -219,7 +191,6 @@ const stringValuesIn = (node: ts.Node): string[] => {
219
191
  return values;
220
192
  };
221
193
 
222
- /** The first non-empty line of the JSDoc/line comment immediately preceding the statement, markers stripped. */
223
194
  const getLeadingDoc = (fullText: string, node: ts.Node): string | undefined => {
224
195
  const ranges = ts.getLeadingCommentRanges(fullText, node.getFullStart());
225
196
  if (!ranges?.length) return undefined;
package/repoIdentity.ts CHANGED
@@ -24,15 +24,8 @@ const readRemoteName = (workspaceRoot: string): string | null => {
24
24
  }
25
25
  };
26
26
 
27
- /**
28
- * The repository's own name.
29
- *
30
- * Deriving it from the working directory made every generated file that names the repo — the AGENTS.md title, its
31
- * `- Repo:` line — depend on what each person happened to call the folder they cloned into, so one commit rendered
32
- * a different guide per developer and the diff never settled. The origin remote is the one identity every clone
33
- * shares. `AKAN_PUBLIC_REPO_NAME` is deliberately not consulted: that is a deployment namespace (queue prefixes,
34
- * cache keys, secret paths) which a monorepo hosting several products legitimately points somewhere else.
35
- */
27
+ // The origin remote, not the clone's folder name, so generated files agree across clones. `AKAN_PUBLIC_REPO_NAME` is a
28
+ // deployment namespace (queue prefixes, cache keys) and deliberately not consulted.
36
29
  export const resolveRepoName = (workspaceRoot: string): string => {
37
30
  const cached = resolved.get(workspaceRoot);
38
31
  if (cached) return cached;
@@ -15,7 +15,6 @@ describe("RouteSourceValidator", () => {
15
15
  "export const reconnect = true;",
16
16
  "export const wsConnect = true;",
17
17
  "export const layoutStyle = {};",
18
- 'export const gaTrackingId = "G-1";',
19
18
  ].join("\n");
20
19
 
21
20
  expect(() => validate(source, "layout", true)).not.toThrow();
@@ -24,15 +24,8 @@ interface ChainStage {
24
24
 
25
25
  const chainRoots = new Set(["page", "layout", "rootLayout"]);
26
26
 
27
- /**
28
- * Static enforcement of the `page/` route conventions, split out of `executors.ts` so that importing
29
- * an executor does not pull `typescript` (+65MB resident) into the module graph. Both validators need
30
- * a real AST — modifier inspection, `export *` rejection, statement-kind walking and call-expression
31
- * identity — so `Bun.Transpiler.scan()`, which only reports export names, cannot replace them.
32
- *
33
- * Import it dynamically (`await import("./routeSourceValidator")`) so long-lived processes that never
34
- * validate a route source stay lean.
35
- */
27
+ // Import dynamically: it pulls in `typescript` (+65MB resident). `Bun.Transpiler.scan()` reports only export names,
28
+ // too little for these checks.
36
29
  export class RouteSourceValidator {
37
30
  static validateRouteSourceExports(
38
31
  source: string,
@@ -86,18 +79,6 @@ export class RouteSourceValidator {
86
79
  if (exported.has("head") && exported.has("generateHead")) {
87
80
  throw new Error(`[route-convention] head and generateHead cannot both be exported in ${filePath}`);
88
81
  }
89
- if (
90
- !options.rootLayout &&
91
- (exported.has("head") || exported.has("generateHead")) &&
92
- (exported.has("metadata") || exported.has("generateMetadata"))
93
- ) {
94
- throw new Error(
95
- `[route-convention] head/generateHead and metadata/generateMetadata cannot both be exported in ${filePath}`,
96
- );
97
- }
98
- if (exported.has("metadata") && exported.has("generateMetadata")) {
99
- throw new Error(`[route-convention] metadata and generateMetadata cannot both be exported in ${filePath}`);
100
- }
101
82
  const chain = RouteSourceValidator.#readChain(sourceFile, filePath);
102
83
  if (!chain) return { devOnly: RouteSourceValidator.#readDevOnly(sourceFile, filePath) };
103
84
  const named = [...exported].filter((name) => name !== "default");
@@ -116,12 +97,7 @@ export class RouteSourceValidator {
116
97
  };
117
98
  }
118
99
 
119
- /**
120
- * The default export as a `page()` / `layout()` / `rootLayout()` chain, or null when the module is the legacy
121
- * shape. Read off the source for the same reason `devOnly` is: the build enumerates routes without importing
122
- * them, and `akan sync` should name a `[projectId]` folder whose page declares no `.param("projectId")` before
123
- * the first request does.
124
- */
100
+ // Null for the legacy shape. Read off the source because the build enumerates routes without importing them.
125
101
  static #readChain(sourceFile: ts.SourceFile, filePath: string) {
126
102
  for (const statement of sourceFile.statements) {
127
103
  if (!ts.isExportAssignment(statement) || statement.isExportEquals) continue;
@@ -185,12 +161,8 @@ export class RouteSourceValidator {
185
161
  );
186
162
  }
187
163
 
188
- /**
189
- * `devOnly` decides whether the route exists in the production build at all, so it is read from the
190
- * source rather than from an evaluated module — the build never imports route files to enumerate them.
191
- * That is why only a literal is accepted: anything the parser cannot settle would otherwise ship a
192
- * route the author believed was excluded.
193
- */
164
+ // Only a literal is accepted: the build reads it without importing the route, and a value the parser cannot settle
165
+ // would ship a route its author believed excluded.
194
166
  static #readDevOnly(sourceFile: ts.SourceFile, filePath: string): boolean {
195
167
  for (const statement of sourceFile.statements) {
196
168
  if (!ts.isVariableStatement(statement)) continue;
@@ -234,13 +206,7 @@ export class RouteSourceValidator {
234
206
  return current;
235
207
  }
236
208
 
237
- /**
238
- * Statically enforces that a `_overrides.tsx` route file is a logic-free activation manifest: a plain module
239
- * (no `"use client"` — the framework generates the client wrapper) that only imports components and binds them
240
- * to slots through a single `export default override({ Modal: BrandModal })`. It must not declare components
241
- * inline or run logic — that keeps the override contract a thin binding layer rather than a second place to
242
- * author UI. Slot names and value types are validated at compile time by `override`.
243
- */
209
+ // A logic-free manifest: imports plus one `export default override({ ... })`; `override` types the slots.
244
210
  static validateOverridesSourceExports(source: string, filePath: string) {
245
211
  const sourceFile = ts.createSourceFile(filePath, source, ts.ScriptTarget.Latest, true, ts.ScriptKind.TSX);
246
212
  const fail = (message: string): never => {
@@ -248,9 +214,8 @@ export class RouteSourceValidator {
248
214
  };
249
215
  let defaultOverride: ts.ExportAssignment | null = null;
250
216
  for (const statement of sourceFile.statements) {
251
- // A "use client" directive is unnecessary (the framework wraps the manifest) but harmless if present.
217
+ //? A "use client" directive is unnecessary (the framework wraps the manifest) but harmless.
252
218
  if (ts.isExpressionStatement(statement) && ts.isStringLiteral(statement.expression)) continue;
253
- // The manifest imports the app components it binds; imports and type-only decls carry no runtime logic.
254
219
  if (ts.isImportDeclaration(statement)) continue;
255
220
  if (ts.isInterfaceDeclaration(statement) || ts.isTypeAliasDeclaration(statement)) continue;
256
221
  if (ts.isExportAssignment(statement) && !statement.isExportEquals) {