@akanjs/devkit 3.0.0-beta.2 → 3.0.0-beta.21

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 (289) hide show
  1. package/README.ko.md +2 -2
  2. package/README.md +3 -2
  3. package/abstractDoc.test.ts +6 -15
  4. package/abstractDoc.ts +6 -8
  5. package/agentsIndex.test.ts +4 -7
  6. package/agentsIndex.ts +6 -22
  7. package/akanApp/BackendImportGraph.test.ts +8 -22
  8. package/akanApp/BackendImportGraph.ts +1 -20
  9. package/akanApp/akanApp.host.test.ts +1 -3
  10. package/akanApp/akanApp.host.ts +82 -254
  11. package/akanApp/devHostPolicy.test.ts +10 -34
  12. package/akanApp/devHostPolicy.ts +12 -98
  13. package/akanConfig/akanConfig.test.ts +142 -106
  14. package/akanConfig/akanConfig.ts +147 -191
  15. package/akanConfig/types.ts +4 -3
  16. package/akanContext.ts +110 -160
  17. package/akanMcpContract.ts +28 -73
  18. package/appSelectionMemory.ts +1 -4
  19. package/applicationBuildReporter.ts +37 -5
  20. package/applicationBuildRunner.test.ts +5 -3
  21. package/applicationBuildRunner.ts +54 -61
  22. package/applicationTestPreload.ts +3 -12
  23. package/artifact/implicitRootLayout.test.ts +32 -30
  24. package/artifact/implicitRootLayout.ts +58 -106
  25. package/artifact/routeSeedIndex.test.ts +7 -6
  26. package/artifact/routeSeedIndex.ts +11 -44
  27. package/biome.base.json +32 -1
  28. package/biomeBase.ts +2 -5
  29. package/cloud/cloudApi.test.ts +202 -0
  30. package/cloud/cloudApi.ts +77 -39
  31. package/cloud/configLock.test.ts +64 -0
  32. package/cloud/configLock.ts +96 -0
  33. package/cloud/constants.ts +26 -5
  34. package/cloud/globalConfig.ts +70 -32
  35. package/codeAgent/agent/AkanCodeServices.ts +97 -0
  36. package/codeAgent/agent/AkanContextFiles.ts +94 -0
  37. package/codeAgent/agent/CodeAgent.ts +551 -0
  38. package/codeAgent/agent/CodeAgentAsks.ts +66 -0
  39. package/codeAgent/agent/CodeAgentEventMapper.ts +225 -0
  40. package/codeAgent/agent/CodeAgentGate.ts +93 -0
  41. package/codeAgent/agent/CodeAgentProxy.ts +82 -0
  42. package/codeAgent/agent/CodeAgentRpcHost.ts +103 -0
  43. package/codeAgent/agent/CodeAgentRpcListener.ts +109 -0
  44. package/codeAgent/agent/CodeAgentStreamPrinter.ts +86 -0
  45. package/codeAgent/agent/CodeAgentSuspended.ts +85 -0
  46. package/codeAgent/agent/CodeAgentUi.ts +47 -0
  47. package/codeAgent/agent/CodeMailbox.ts +148 -0
  48. package/codeAgent/agent/CodeSessionFork.ts +56 -0
  49. package/codeAgent/agent/CodeSessionIndex.ts +78 -0
  50. package/codeAgent/agent/SubagentPool.ts +214 -0
  51. package/codeAgent/agent/akanCodeModel.ts +47 -0
  52. package/codeAgent/agent/akanCodePaths.ts +31 -0
  53. package/codeAgent/agent/akanContextFiles.test.ts +114 -0
  54. package/codeAgent/agent/akanSystemPrompt.ts +46 -0
  55. package/codeAgent/agent/codeAgent.test.ts +178 -0
  56. package/codeAgent/agent/codeAgentContract.test.ts +523 -0
  57. package/codeAgent/agent/codeAgentProxy.test.ts +74 -0
  58. package/codeAgent/agent/codeAgentRpcListener.test.ts +113 -0
  59. package/codeAgent/agent/codeAgentSuspended.test.ts +47 -0
  60. package/codeAgent/agent/codeMailbox.test.ts +76 -0
  61. package/codeAgent/agent/codeSessionFork.test.ts +58 -0
  62. package/codeAgent/agent/codeSessionIndex.test.ts +77 -0
  63. package/codeAgent/agent/consoleToStderr.ts +8 -0
  64. package/codeAgent/agent/jsonLines.ts +29 -0
  65. package/codeAgent/agent/stringArg.ts +5 -0
  66. package/codeAgent/feedback/DevLogFeedback.ts +82 -0
  67. package/codeAgent/feedback/PreviewChrome.ts +82 -0
  68. package/codeAgent/feedback/PreviewFeedback.ts +46 -0
  69. package/codeAgent/feedback/PreviewView.ts +95 -0
  70. package/codeAgent/feedback/TurnFeedback.ts +98 -0
  71. package/codeAgent/feedback/VerifyFeedback.ts +40 -0
  72. package/codeAgent/feedback/previewChrome.test.ts +73 -0
  73. package/codeAgent/index.ts +34 -0
  74. package/codeAgent/skills/akan-code/SKILL.md +130 -0
  75. package/codeAgent/skills/akan-debug/SKILL.md +42 -0
  76. package/codeAgent/skills/akan-deps/SKILL.md +50 -0
  77. package/codeAgent/skills/akan-err/SKILL.md +39 -0
  78. package/codeAgent/skills/akan-module/SKILL.md +45 -0
  79. package/codeAgent/skills/akan-scaffold/SKILL.md +76 -0
  80. package/codeAgent/skills/akan-scalar/SKILL.md +52 -0
  81. package/codeAgent/skills/akan-service/SKILL.md +34 -0
  82. package/codeAgent/skills/akan-store/SKILL.md +67 -0
  83. package/codeAgent/skills/akan-ui/SKILL.md +63 -0
  84. package/codeAgent/skills/akan-validate/SKILL.md +44 -0
  85. package/codeAgent/tools/AkanCli.ts +50 -0
  86. package/codeAgent/tools/AkanCodePlugins.ts +136 -0
  87. package/codeAgent/tools/AkanEditScope.ts +48 -0
  88. package/codeAgent/tools/AkanEnvKeys.ts +50 -0
  89. package/codeAgent/tools/AkanToolPack.ts +92 -0
  90. package/codeAgent/tools/AkanVerifier.ts +48 -0
  91. package/codeAgent/tools/AskToolPack.ts +99 -0
  92. package/codeAgent/tools/MailToolPack.ts +76 -0
  93. package/codeAgent/tools/McpClient.ts +187 -0
  94. package/codeAgent/tools/McpOAuth.ts +233 -0
  95. package/codeAgent/tools/McpOAuthLoopback.ts +64 -0
  96. package/codeAgent/tools/McpServerConfig.ts +129 -0
  97. package/codeAgent/tools/McpSignIn.ts +158 -0
  98. package/codeAgent/tools/McpTokenStore.ts +81 -0
  99. package/codeAgent/tools/McpToolPack.ts +199 -0
  100. package/codeAgent/tools/SessionToolPack.ts +91 -0
  101. package/codeAgent/tools/WebToolPack.ts +147 -0
  102. package/codeAgent/tools/akanCodePlugins.test.ts +677 -0
  103. package/codeAgent/tools/askToolPack.test.ts +116 -0
  104. package/codeAgent/tools/mcpOAuth.test.ts +612 -0
  105. package/codeAgent/tools/mcpServerConfig.test.ts +212 -0
  106. package/codegenLock.test.ts +4 -13
  107. package/codegenLock.ts +3 -17
  108. package/commandDecorators/argMeta.ts +1 -5
  109. package/commandDecorators/camelToKebabCase.ts +1 -0
  110. package/commandDecorators/command.ts +46 -102
  111. package/commandDecorators/commandBuilder.ts +30 -61
  112. package/commandDecorators/commandDecorators.test.ts +2 -10
  113. package/commandDecorators/dependencyBuilder.ts +10 -1
  114. package/commandDecorators/helpFormatter.ts +68 -145
  115. package/commandDecorators/index.ts +0 -2
  116. package/commandDecorators/targetMeta.ts +9 -7
  117. package/createTunnel.ts +7 -14
  118. package/dependencyScanner.test.ts +4 -16
  119. package/dependencyScanner.ts +31 -80
  120. package/devkitUtils.test.ts +48 -20
  121. package/executors.linkFallback.test.ts +103 -0
  122. package/executors.test.ts +132 -106
  123. package/executors.ts +195 -496
  124. package/fileEditor.ts +9 -37
  125. package/{getDirname.test.ts → fileSys.test.ts} +5 -3
  126. package/fileSys.ts +6 -8
  127. package/formSetterScanner.ts +3 -16
  128. package/frontendBuild/allRoutesBuilder.ts +7 -21
  129. package/frontendBuild/autoImportSync.test.ts +27 -38
  130. package/frontendBuild/autoImportSync.ts +31 -65
  131. package/frontendBuild/buildRouteClient.test.ts +62 -37
  132. package/frontendBuild/bundleDefine.ts +12 -0
  133. package/frontendBuild/clientBuildTypes.ts +5 -36
  134. package/frontendBuild/clientEntriesBundler.ts +9 -40
  135. package/frontendBuild/clientEntryDiscovery.ts +6 -41
  136. package/frontendBuild/csrArtifactBuilder.ts +6 -25
  137. package/frontendBuild/cssCandidateCache.test.ts +0 -6
  138. package/frontendBuild/cssCandidateCache.ts +6 -41
  139. package/frontendBuild/cssCompiler.ts +29 -78
  140. package/frontendBuild/cssImportResolver.ts +6 -19
  141. package/frontendBuild/devChangePlanner.ts +7 -10
  142. package/frontendBuild/devGeneratedIndexSync.test.ts +12 -27
  143. package/frontendBuild/devGeneratedIndexSync.ts +1 -4
  144. package/frontendBuild/fontOptimizer.test.ts +6 -14
  145. package/frontendBuild/fontOptimizer.ts +15 -72
  146. package/frontendBuild/fontPruner.test.ts +5 -10
  147. package/frontendBuild/fontPruner.ts +5 -20
  148. package/frontendBuild/frontendBuild.test.ts +46 -69
  149. package/frontendBuild/hmrChangeClassifier.ts +2 -2
  150. package/frontendBuild/hmrWatcher.test.ts +8 -32
  151. package/frontendBuild/hmrWatcher.ts +14 -71
  152. package/frontendBuild/pagesBundleBuilder.ts +4 -29
  153. package/frontendBuild/precompressArtifacts.ts +5 -18
  154. package/frontendBuild/routeClientBuilder.ts +23 -46
  155. package/frontendBuild/routesManifestArtifactSerializer.ts +5 -36
  156. package/frontendBuild/sourceMtimeIndex.test.ts +41 -97
  157. package/frontendBuild/sourceMtimeIndex.ts +19 -130
  158. package/frontendBuild/ssrBaseArtifactBuilder.ts +2 -8
  159. package/frontendBuild/styleContract.ts +1 -8
  160. package/frontendBuild/themeValidator.ts +4 -26
  161. package/frontendBuild/watchRootResolver.test.ts +23 -42
  162. package/frontendBuild/watchRootResolver.ts +5 -27
  163. package/getRelatedCnsts.ts +12 -92
  164. package/incrementalBuilder/buildBatch.proc.ts +29 -58
  165. package/incrementalBuilder/buildBatchProtocol.ts +8 -36
  166. package/incrementalBuilder/buildBatchRunner.test.ts +34 -0
  167. package/incrementalBuilder/buildBatchRunner.ts +10 -31
  168. package/incrementalBuilder/builderChannel.test.ts +24 -49
  169. package/incrementalBuilder/builderChannel.ts +6 -37
  170. package/incrementalBuilder/builderRequestRouter.test.ts +0 -8
  171. package/incrementalBuilder/builderRequestRouter.ts +5 -24
  172. package/incrementalBuilder/devWatchBatch.test.ts +21 -24
  173. package/incrementalBuilder/incrementalBuilder.host.test.ts +18 -55
  174. package/incrementalBuilder/incrementalBuilder.host.ts +18 -76
  175. package/incrementalBuilder/incrementalBuilder.proc.test.ts +81 -0
  176. package/incrementalBuilder/incrementalBuilder.proc.ts +49 -161
  177. package/index.ts +5 -19
  178. package/integration/devResourceProbe.ts +50 -56
  179. package/integration/devStability.integration.test.ts +47 -194
  180. package/integration/devStabilityHarness.test.ts +13 -28
  181. package/integration/devStabilityHarness.ts +65 -330
  182. package/integration/ssrMemoryProbe.ts +41 -167
  183. package/libSource.test.ts +4 -15
  184. package/libSource.ts +19 -31
  185. package/lint/__fixtures__/no-document-cookie/bad.tsx +4 -0
  186. package/lint/__fixtures__/no-document-cookie/good.tsx +7 -0
  187. package/lint/__fixtures__/no-web-only-api-outside-webkit/bad.tsx +7 -0
  188. package/lint/__fixtures__/no-web-only-api-outside-webkit/good.tsx +11 -0
  189. package/lint/__fixtures__/no-web-storage/bad.tsx +6 -0
  190. package/lint/__fixtures__/no-web-storage/good.tsx +7 -0
  191. package/lint/gritRules.test.ts +8 -24
  192. package/lint/no-deep-internal-import.grit +2 -2
  193. package/lint/no-document-cookie.grit +29 -0
  194. package/lint/no-redeclare-predefined-endpoint.grit +2 -1
  195. package/lint/no-web-only-api-outside-webkit.grit +44 -0
  196. package/lint/no-web-storage.grit +30 -0
  197. package/linter.test.ts +1 -2
  198. package/linter.ts +26 -116
  199. package/mobile/index.ts +6 -0
  200. package/mobile/mobileTarget.ts +16 -13
  201. package/mobile/nativeApi.test.ts +30 -0
  202. package/mobile/nativeApi.ts +40 -0
  203. package/mobile/nativeApp.test.ts +85 -0
  204. package/mobile/nativeApp.ts +210 -0
  205. package/mobile/nativeConfig.test.ts +199 -0
  206. package/mobile/nativeConfig.ts +184 -0
  207. package/mobile/nativeWebDir.test.ts +80 -0
  208. package/mobile/nativeWebDir.ts +62 -0
  209. package/mobile/placeholderAppId.test.ts +13 -0
  210. package/mobile/placeholderAppId.ts +16 -0
  211. package/mobile/usageDescriptions.test.ts +27 -0
  212. package/mobile/usageDescriptions.ts +24 -0
  213. package/package.json +10 -10
  214. package/packageExports.test.ts +3 -18
  215. package/packageExportsMap.ts +3 -19
  216. package/piDependencyPins.test.ts +46 -0
  217. package/platformTest/BunTestReport.test.ts +60 -0
  218. package/platformTest/BunTestReport.ts +65 -0
  219. package/platformTest/IdleWatchedProcess.test.ts +56 -0
  220. package/platformTest/IdleWatchedProcess.ts +73 -0
  221. package/platformTest/LinuxDockerTarget.ts +105 -0
  222. package/platformTest/PlatformTestRun.test.ts +64 -0
  223. package/platformTest/PlatformTestRun.ts +141 -0
  224. package/platformTest/PlatformTestTarget.ts +142 -0
  225. package/platformTest/TestSnapshot.test.ts +72 -0
  226. package/platformTest/TestSnapshot.ts +103 -0
  227. package/platformTest/WindowsSshTarget.ts +151 -0
  228. package/prompter.ts +3 -13
  229. package/qualityScanner.test.ts +9 -26
  230. package/qualityScanner.ts +200 -286
  231. package/recipeScanner.test.ts +7 -12
  232. package/recipeScanner.ts +9 -38
  233. package/repoIdentity.ts +2 -9
  234. package/routeSourceValidator.test.ts +0 -1
  235. package/routeSourceValidator.ts +7 -42
  236. package/scanInfo.ts +91 -225
  237. package/selectModel.ts +1 -2
  238. package/semver.ts +3 -5
  239. package/slicePlanner.test.ts +6 -24
  240. package/slicePlanner.ts +11 -32
  241. package/spinner.test.ts +3 -8
  242. package/spinner.ts +4 -16
  243. package/sshTunnel.ts +1 -6
  244. package/ssrScanner.test.ts +1 -6
  245. package/ssrScanner.ts +103 -113
  246. package/stripAnsi.ts +3 -0
  247. package/subspace.test.ts +30 -25
  248. package/subspace.ts +81 -115
  249. package/subspaceConfig.ts +11 -9
  250. package/testHelpers.ts +194 -0
  251. package/transforms/asyncDefaultExportDetector.ts +3 -11
  252. package/transforms/barrelAnalyzer.ts +43 -135
  253. package/transforms/barrelImportsPlugin.ts +38 -175
  254. package/transforms/externalizeFrameworkPlugin.test.ts +39 -118
  255. package/transforms/externalizeFrameworkPlugin.ts +13 -87
  256. package/transforms/moduleSyntax.ts +32 -0
  257. package/transforms/rscUseClientTransform.ts +35 -20
  258. package/transforms/transforms.test.ts +153 -44
  259. package/transforms/tsconfigPackageResolver.test.ts +6 -24
  260. package/transforms/useClientBundlePlugin.ts +2 -22
  261. package/typeChecker.ts +7 -63
  262. package/types.ts +1 -0
  263. package/uploadRelease.ts +10 -25
  264. package/workflow/artifacts.ts +18 -0
  265. package/workflow/executor.test.ts +1 -12
  266. package/workflow/executor.ts +101 -110
  267. package/workflow/index.ts +0 -1
  268. package/workflow/moduleIndex.test.ts +4 -15
  269. package/workflow/moduleIndex.ts +9 -49
  270. package/workflow/plan.ts +18 -25
  271. package/workflow/render.ts +80 -96
  272. package/workflow/rolloutGate.test.ts +1 -1
  273. package/workflow/source.ts +52 -69
  274. package/workflow/sourceAst.ts +50 -0
  275. package/workspaceLayout.test.ts +13 -3
  276. package/workspaceLayout.ts +20 -23
  277. package/abstractCompactor.ts +0 -112
  278. package/aiEditor.test.ts +0 -60
  279. package/aiEditor.ts +0 -387
  280. package/applicationReleasePackager.ts +0 -209
  281. package/capacitorApp.test.ts +0 -372
  282. package/capacitorApp.ts +0 -1648
  283. package/commandDecorators/commandMeta.ts +0 -7
  284. package/commandDecorators/types.ts +0 -10
  285. package/getDirname.ts +0 -4
  286. package/guideline.ts +0 -19
  287. package/local/optimize-resource/ipcprobe/child.ts +0 -1
  288. package/local/optimize-resource/ipcprobe/parent.ts +0 -11
  289. package/workflow/primitive.ts +0 -59
@@ -0,0 +1,151 @@
1
+ import { existsSync } from "node:fs";
2
+ import os from "node:os";
3
+ import path from "node:path";
4
+ import type { PlatformGatePolicy, WindowsTestTargetConfig } from "../cloud/constants";
5
+ import { IdleWatchedProcess } from "./IdleWatchedProcess";
6
+ import { type PlatformRunContext, PlatformTestTarget } from "./PlatformTestTarget";
7
+
8
+ export class WindowsSshTarget extends PlatformTestTarget {
9
+ static readonly utmctlPath = "/Applications/UTM.app/Contents/MacOS/utmctl";
10
+ static readonly defaultWorkRoot = "C:\\akan-test";
11
+ //* arm64 has neither a sharp@0.32 prebuilt nor a Chrome for puppeteer's postinstall; x64 users get both.
12
+ static readonly defaultTolerateScriptFailures = ["sharp", "puppeteer"];
13
+ static readonly #runDirPattern = "^\\d{8}-\\d{6}$";
14
+ static readonly #keptRuns = 2;
15
+
16
+ readonly platform = "windows";
17
+ readonly policy: PlatformGatePolicy;
18
+ protected override readonly tolerateScriptFailures: string[];
19
+ readonly #config: WindowsTestTargetConfig;
20
+ #host: string;
21
+ #runDir: string | null = null;
22
+
23
+ constructor(config: WindowsTestTargetConfig) {
24
+ super();
25
+ this.#config = config;
26
+ this.#host = process.env.AKAN_TEST_WINDOWS_HOST ?? config.host;
27
+ this.policy = config.policy ?? "warn";
28
+ this.tolerateScriptFailures = config.tolerateScriptFailures ?? WindowsSshTarget.defaultTolerateScriptFailures;
29
+ }
30
+
31
+ get #workRoot() {
32
+ return this.#config.workRoot ?? WindowsSshTarget.defaultWorkRoot;
33
+ }
34
+
35
+ protected async prepare(context: PlatformRunContext) {
36
+ await this.#connect();
37
+ const bunVersion = (await this.#ssh("bun --version")).stdout.trim();
38
+ if (bunVersion !== Bun.version)
39
+ throw new Error(
40
+ `the Windows host runs Bun ${bunVersion || "(none)"}, this host ${Bun.version} — align them first`,
41
+ );
42
+ if ((await this.#ssh("git --version")).exitCode !== 0) throw new Error("git is not installed on the Windows host");
43
+ const runDir = `${this.#workRoot}\\${context.runId}`;
44
+ const mkdir = await this.#ssh(`New-Item -ItemType Directory -Force -Path '${runDir}\\w' | Out-Null`);
45
+ if (mkdir.exitCode !== 0) throw new Error(`could not create ${runDir}: ${mkdir.stderr.trim()}`);
46
+ this.#runDir = runDir;
47
+ for (const [local, remoteName] of [
48
+ [context.snapshot.tarPath, "src.tar"],
49
+ [context.envPath, "env"],
50
+ ] as const) {
51
+ const copied = await WindowsSshTarget.spawnText([
52
+ ...this.#scpBase(),
53
+ local,
54
+ this.#remoteScpPath(`${runDir}\\${remoteName}`),
55
+ ]);
56
+ if (copied.exitCode !== 0) throw new Error(`scp ${remoteName} failed: ${copied.stderr.trim()}`);
57
+ }
58
+ const extract = await this.exec(
59
+ "tar -xf ..\\src.tar && copy /Y ..\\env .env >nul && git init -q",
60
+ path.join(context.logDir, this.platform, "extract.log"),
61
+ PlatformTestTarget.installIdleMs,
62
+ );
63
+ if (extract.exitCode !== 0) throw new Error("extracting the snapshot on the Windows host failed");
64
+ }
65
+
66
+ protected async exec(command: string, logPath: string, idleMs: number) {
67
+ const runDir = this.#runDir;
68
+ if (!runDir) throw new Error("the Windows run directory was not prepared");
69
+ let remotePid: string | null = null;
70
+ const script = [
71
+ "$ProgressPreference = 'SilentlyContinue'",
72
+ "[Console]::OutputEncoding = [Text.Encoding]::UTF8",
73
+ "'PID=' + $PID",
74
+ ...Object.entries(PlatformTestTarget.gitIdentityEnv).map(([key, value]) => `$env:${key} = '${value}'`),
75
+ `Set-Location -LiteralPath '${runDir}\\w'`,
76
+ `cmd /c "${command} 2>&1"`,
77
+ "exit $LASTEXITCODE",
78
+ ].join("; ");
79
+ return await new IdleWatchedProcess([...this.#sshBase(), script], {
80
+ logPath,
81
+ idleMs,
82
+ onOutput: (text) => {
83
+ remotePid ??= /PID=(\d+)/.exec(text)?.[1] ?? null;
84
+ },
85
+ onIdle: async () => {
86
+ if (remotePid) await this.#ssh(`taskkill /F /T /PID ${remotePid} | Out-Null`);
87
+ },
88
+ }).run();
89
+ }
90
+
91
+ protected async cleanup() {
92
+ this.#runDir = null;
93
+ await this.#ssh(
94
+ [
95
+ `Get-ChildItem -LiteralPath '${this.#workRoot}' -Directory`,
96
+ `Where-Object { $_.Name -match '${WindowsSshTarget.#runDirPattern}' }`,
97
+ "Sort-Object Name -Descending",
98
+ `Select-Object -Skip ${WindowsSshTarget.#keptRuns}`,
99
+ "ForEach-Object { cmd /c rmdir /s /q $_.FullName }",
100
+ ].join(" | "),
101
+ );
102
+ }
103
+
104
+ async #connect() {
105
+ if ((await this.#ssh("exit 0")).exitCode === 0) return;
106
+ const vm = this.#config.utmVm;
107
+ if (!vm || !existsSync(WindowsSshTarget.utmctlPath))
108
+ throw new Error(`cannot reach ${this.#config.user}@${this.#host} over ssh`);
109
+ const status = (await WindowsSshTarget.spawnText([WindowsSshTarget.utmctlPath, "status", vm])).stdout.trim();
110
+ if (status !== "started") await WindowsSshTarget.spawnText([WindowsSshTarget.utmctlPath, "start", vm]);
111
+ for (let attempt = 0; attempt < 24; attempt++) {
112
+ const addresses = (await WindowsSshTarget.spawnText([WindowsSshTarget.utmctlPath, "ip-address", vm])).stdout;
113
+ const ipv4 = addresses.split(/\s+/).find((address) => /^\d+\.\d+\.\d+\.\d+$/.test(address));
114
+ if (ipv4) this.#host = ipv4;
115
+ if ((await this.#ssh("exit 0")).exitCode === 0) return;
116
+ await Bun.sleep(5_000);
117
+ }
118
+ throw new Error(`UTM VM "${vm}" did not answer ssh at ${this.#host}`);
119
+ }
120
+
121
+ #sshOptions() {
122
+ const expand = (file: string) => (file.startsWith("~/") ? path.join(os.homedir(), file.slice(2)) : file);
123
+ return [
124
+ "-i",
125
+ expand(this.#config.identityFile),
126
+ ...(this.#config.knownHostsFile ? ["-o", `UserKnownHostsFile=${expand(this.#config.knownHostsFile)}`] : []),
127
+ "-o",
128
+ "BatchMode=yes",
129
+ "-o",
130
+ "ConnectTimeout=8",
131
+ "-o",
132
+ "ServerAliveInterval=30",
133
+ ];
134
+ }
135
+
136
+ #sshBase() {
137
+ return ["ssh", ...this.#sshOptions(), `${this.#config.user}@${this.#host}`];
138
+ }
139
+
140
+ #scpBase() {
141
+ return ["scp", "-q", ...this.#sshOptions()];
142
+ }
143
+
144
+ #remoteScpPath(windowsPath: string) {
145
+ return `${this.#config.user}@${this.#host}:/${windowsPath.replaceAll("\\", "/")}`;
146
+ }
147
+
148
+ async #ssh(script: string) {
149
+ return await WindowsSshTarget.spawnText([...this.#sshBase(), script]);
150
+ }
151
+ }
package/prompter.ts CHANGED
@@ -1,8 +1,7 @@
1
1
  import fsPromise from "node:fs/promises";
2
2
  import { input, select } from "@inquirer/prompts";
3
3
 
4
- import { getDirname } from "./getDirname";
5
- import type { GuideGenerateJson } from "./guideline";
4
+ import { getDirname } from "./fileSys";
6
5
 
7
6
  interface FileUpdateRequestProps {
8
7
  context: string;
@@ -34,17 +33,8 @@ export class Prompter {
34
33
  const guidelineRoot = await Prompter.#getGuidelineRoot();
35
34
  return (await fsPromise.readdir(guidelineRoot)).filter((name) => !name.startsWith("_")).sort();
36
35
  }
37
- static async getGuideJson(guideName: string): Promise<GuideGenerateJson> {
38
- const guidelineRoot = await Prompter.#getGuidelineRoot();
39
- const filePath = `${guidelineRoot}/${guideName}/${guideName}.generate.json`;
40
- const guideJson = await fsPromise.readFile(filePath, "utf-8");
41
- return JSON.parse(guideJson) as GuideGenerateJson;
42
- }
43
- /**
44
- * The name is checked against the directory listing rather than interpolated straight into a path:
45
- * it arrives from an MCP tool argument, and an unknown one has to name the valid set instead of
46
- * surfacing an ENOENT that carries the host's own paths.
47
- */
36
+ // Checked against the listing, not interpolated into a path: the name is an MCP tool argument, and an unknown one
37
+ // must not surface an ENOENT carrying the host's paths.
48
38
  static async getInstruction(guideName: string): Promise<string> {
49
39
  const guideNames = await Prompter.listGuidelines();
50
40
  if (!guideNames.includes(guideName))
@@ -1,32 +1,23 @@
1
- import { afterEach, describe, expect, test } from "bun:test";
2
- import { mkdir, mkdtemp, rm, writeFile } from "node:fs/promises";
3
- import os from "node:os";
1
+ import { describe, expect, test } from "bun:test";
4
2
  import path from "node:path";
5
3
  import { AbstractDoc } from "./abstractDoc";
6
4
  import { AkanQualityScanner, type QualityScanResult } from "./qualityScanner";
5
+ import { tempDirs, writeText } from "./testHelpers";
7
6
 
8
- const tempRoots: string[] = [];
7
+ const makeTempRoot = tempDirs("akan-quality-scanner-");
9
8
 
10
9
  const makeWorkspace = async (files: Record<string, string>) => {
11
- const root = await mkdtemp(path.join(os.tmpdir(), "akan-quality-scanner-"));
12
- tempRoots.push(root);
13
- for (const [filePath, content] of Object.entries({ ".gitignore": "node_modules\n", ...files })) {
14
- const absolutePath = path.join(root, filePath);
15
- await mkdir(path.dirname(absolutePath), { recursive: true });
16
- await writeFile(absolutePath, content);
17
- }
10
+ const root = await makeTempRoot();
11
+ for (const [filePath, content] of Object.entries({ ".gitignore": "node_modules\n", ...files }))
12
+ await writeText(path.join(root, filePath), content);
18
13
  return root;
19
14
  };
20
15
 
21
16
  const abstractOf = (lineNum: number) =>
22
17
  ["# post Abstract", ...Array.from({ length: lineNum - 1 }, (_, idx) => `- rule ${idx}`)].join("\n");
23
18
 
24
- afterEach(async () => {
25
- await Promise.all(tempRoots.splice(0).map((root) => rm(root, { recursive: true, force: true })));
26
- });
27
-
28
19
  describe("AkanQualityScanner abstract rule", () => {
29
- test("warns on an abstract over the line limit and points at akan compact", async () => {
20
+ test("warns on an abstract over the line limit and says how to trim it", async () => {
30
21
  const root = await makeWorkspace({
31
22
  "apps/demo/lib/post/post.abstract.md": abstractOf(AbstractDoc.maxLines + 1),
32
23
  "apps/demo/lib/post/post.constant.ts": "export class Post {}\n",
@@ -41,7 +32,8 @@ describe("AkanQualityScanner abstract rule", () => {
41
32
  expect(warnings).toHaveLength(1);
42
33
  expect(warnings[0]?.file).toBe("apps/demo/lib/post/post.abstract.md");
43
34
  expect(warnings[0]?.message).toContain(`${AbstractDoc.maxLines + 1} lines`);
44
- expect(warnings[0]?.fix).toContain("akan compact");
35
+ expect(warnings[0]?.fix).toContain("## Rules");
36
+ expect(warnings[0]?.fix).not.toContain("akan compact");
45
37
  });
46
38
  });
47
39
 
@@ -184,15 +176,6 @@ describe("AkanQualityScanner ssr rules", () => {
184
176
  });
185
177
  });
186
178
 
187
- const signalOf = (entries: string) =>
188
- [
189
- `import { endpoint, slice } from "akanjs/signal";`,
190
- `export class PostSlice extends slice(srv.post, { guards: {}, mcp: { get: true } }, (init) => ({`,
191
- entries,
192
- `})) {}`,
193
- "",
194
- ].join("\n");
195
-
196
179
  describe("AkanQualityScanner layout rules", () => {
197
180
  test("flags an unknown app root file but not a facet entrypoint", async () => {
198
181
  const root = await makeWorkspace({