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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (233) hide show
  1. package/abstractDoc.test.ts +6 -15
  2. package/abstractDoc.ts +1 -4
  3. package/agentsIndex.test.ts +4 -7
  4. package/agentsIndex.ts +6 -22
  5. package/akanApp/BackendImportGraph.test.ts +8 -22
  6. package/akanApp/BackendImportGraph.ts +1 -20
  7. package/akanApp/akanApp.host.test.ts +1 -3
  8. package/akanApp/akanApp.host.ts +64 -250
  9. package/akanApp/devHostPolicy.test.ts +10 -34
  10. package/akanApp/devHostPolicy.ts +12 -98
  11. package/akanConfig/akanConfig.test.ts +30 -39
  12. package/akanConfig/akanConfig.ts +40 -136
  13. package/akanContext.ts +106 -159
  14. package/akanMcpContract.ts +28 -73
  15. package/appSelectionMemory.ts +1 -4
  16. package/applicationBuildReporter.ts +37 -5
  17. package/applicationBuildRunner.ts +25 -59
  18. package/applicationReleasePackager.ts +24 -44
  19. package/applicationTestPreload.ts +3 -12
  20. package/artifact/implicitRootLayout.test.ts +3 -19
  21. package/artifact/implicitRootLayout.ts +34 -91
  22. package/artifact/routeSeedIndex.ts +11 -44
  23. package/biomeBase.ts +2 -5
  24. package/capacitorApp.test.ts +4 -14
  25. package/capacitorApp.ts +86 -235
  26. package/cloud/cloudApi.test.ts +202 -0
  27. package/cloud/cloudApi.ts +33 -57
  28. package/cloud/configLock.test.ts +64 -0
  29. package/cloud/configLock.ts +96 -0
  30. package/cloud/constants.ts +3 -2
  31. package/cloud/globalConfig.ts +64 -31
  32. package/codeAgent/agent/AkanCodeServices.ts +15 -57
  33. package/codeAgent/agent/AkanContextFiles.ts +5 -28
  34. package/codeAgent/agent/CodeAgent.ts +33 -108
  35. package/codeAgent/agent/CodeAgentAsks.ts +7 -30
  36. package/codeAgent/agent/CodeAgentEventMapper.ts +9 -52
  37. package/codeAgent/agent/CodeAgentGate.ts +8 -34
  38. package/codeAgent/agent/CodeAgentProxy.ts +4 -13
  39. package/codeAgent/agent/CodeAgentRpcHost.ts +3 -16
  40. package/codeAgent/agent/CodeAgentRpcListener.ts +2 -10
  41. package/codeAgent/agent/CodeAgentStreamPrinter.ts +4 -15
  42. package/codeAgent/agent/CodeAgentSuspended.ts +1 -8
  43. package/codeAgent/agent/CodeAgentUi.ts +2 -7
  44. package/codeAgent/agent/CodeMailbox.ts +7 -34
  45. package/codeAgent/agent/CodeSessionFork.ts +4 -19
  46. package/codeAgent/agent/CodeSessionIndex.ts +5 -31
  47. package/codeAgent/agent/SubagentPool.ts +48 -58
  48. package/codeAgent/agent/akanCodeModel.ts +5 -39
  49. package/codeAgent/agent/akanCodePaths.ts +4 -24
  50. package/codeAgent/agent/akanContextFiles.test.ts +0 -2
  51. package/codeAgent/agent/akanSystemPrompt.ts +1 -9
  52. package/codeAgent/agent/codeAgent.test.ts +178 -0
  53. package/codeAgent/agent/codeAgentContract.test.ts +5 -28
  54. package/codeAgent/agent/codeMailbox.test.ts +0 -4
  55. package/codeAgent/agent/codeSessionFork.test.ts +0 -2
  56. package/codeAgent/agent/codeSessionIndex.test.ts +9 -4
  57. package/codeAgent/agent/consoleToStderr.ts +2 -12
  58. package/codeAgent/agent/jsonLines.ts +29 -0
  59. package/codeAgent/agent/stringArg.ts +5 -0
  60. package/codeAgent/feedback/DevLogFeedback.ts +3 -18
  61. package/codeAgent/feedback/PreviewChrome.ts +5 -29
  62. package/codeAgent/feedback/PreviewFeedback.ts +2 -9
  63. package/codeAgent/feedback/PreviewView.ts +3 -15
  64. package/codeAgent/feedback/TurnFeedback.ts +2 -17
  65. package/codeAgent/feedback/VerifyFeedback.ts +1 -7
  66. package/codeAgent/feedback/previewChrome.test.ts +1 -1
  67. package/codeAgent/index.ts +2 -10
  68. package/codeAgent/skills/akan-code/SKILL.md +12 -4
  69. package/codeAgent/tools/AkanCli.ts +4 -16
  70. package/codeAgent/tools/AkanCodePlugins.ts +21 -43
  71. package/codeAgent/tools/AkanEditScope.ts +2 -12
  72. package/codeAgent/tools/AkanEnvKeys.ts +3 -11
  73. package/codeAgent/tools/AkanToolPack.ts +6 -28
  74. package/codeAgent/tools/AkanVerifier.ts +1 -8
  75. package/codeAgent/tools/AskToolPack.ts +3 -15
  76. package/codeAgent/tools/MailToolPack.ts +0 -9
  77. package/codeAgent/tools/McpClient.ts +3 -24
  78. package/codeAgent/tools/McpOAuth.ts +6 -41
  79. package/codeAgent/tools/McpOAuthLoopback.ts +2 -15
  80. package/codeAgent/tools/McpServerConfig.ts +7 -26
  81. package/codeAgent/tools/McpSignIn.ts +46 -49
  82. package/codeAgent/tools/McpTokenStore.ts +6 -17
  83. package/codeAgent/tools/McpToolPack.ts +107 -35
  84. package/codeAgent/tools/SessionToolPack.ts +1 -11
  85. package/codeAgent/tools/WebToolPack.ts +1 -7
  86. package/codeAgent/tools/akanCodePlugins.test.ts +231 -63
  87. package/codeAgent/tools/askToolPack.test.ts +0 -3
  88. package/codeAgent/tools/mcpOAuth.test.ts +315 -27
  89. package/codeAgent/tools/mcpServerConfig.test.ts +21 -14
  90. package/codegenLock.test.ts +4 -13
  91. package/codegenLock.ts +3 -17
  92. package/commandDecorators/argMeta.ts +1 -5
  93. package/commandDecorators/camelToKebabCase.ts +1 -0
  94. package/commandDecorators/command.ts +44 -102
  95. package/commandDecorators/commandBuilder.ts +30 -61
  96. package/commandDecorators/commandDecorators.test.ts +1 -9
  97. package/commandDecorators/dependencyBuilder.ts +10 -1
  98. package/commandDecorators/helpFormatter.ts +66 -144
  99. package/commandDecorators/index.ts +0 -2
  100. package/commandDecorators/targetMeta.ts +9 -7
  101. package/createTunnel.ts +7 -14
  102. package/dependencyScanner.test.ts +4 -16
  103. package/dependencyScanner.ts +27 -78
  104. package/devkitUtils.test.ts +47 -19
  105. package/executors.test.ts +114 -60
  106. package/executors.ts +165 -484
  107. package/fileEditor.ts +9 -37
  108. package/{getDirname.test.ts → fileSys.test.ts} +1 -1
  109. package/fileSys.ts +6 -8
  110. package/formSetterScanner.ts +3 -16
  111. package/frontendBuild/allRoutesBuilder.ts +7 -21
  112. package/frontendBuild/autoImportSync.test.ts +14 -38
  113. package/frontendBuild/autoImportSync.ts +15 -67
  114. package/frontendBuild/buildRouteClient.test.ts +10 -36
  115. package/frontendBuild/bundleDefine.ts +12 -0
  116. package/frontendBuild/clientBuildTypes.ts +5 -36
  117. package/frontendBuild/clientEntriesBundler.ts +9 -40
  118. package/frontendBuild/clientEntryDiscovery.ts +6 -41
  119. package/frontendBuild/csrArtifactBuilder.ts +6 -25
  120. package/frontendBuild/cssCandidateCache.test.ts +0 -6
  121. package/frontendBuild/cssCandidateCache.ts +6 -41
  122. package/frontendBuild/cssCompiler.ts +29 -78
  123. package/frontendBuild/cssImportResolver.ts +5 -18
  124. package/frontendBuild/devChangePlanner.ts +7 -10
  125. package/frontendBuild/devGeneratedIndexSync.test.ts +12 -27
  126. package/frontendBuild/devGeneratedIndexSync.ts +1 -4
  127. package/frontendBuild/fontOptimizer.test.ts +5 -13
  128. package/frontendBuild/fontOptimizer.ts +15 -72
  129. package/frontendBuild/fontPruner.test.ts +5 -10
  130. package/frontendBuild/fontPruner.ts +5 -20
  131. package/frontendBuild/frontendBuild.test.ts +27 -52
  132. package/frontendBuild/hmrChangeClassifier.ts +2 -2
  133. package/frontendBuild/hmrWatcher.test.ts +7 -31
  134. package/frontendBuild/hmrWatcher.ts +14 -71
  135. package/frontendBuild/pagesBundleBuilder.ts +4 -29
  136. package/frontendBuild/precompressArtifacts.ts +5 -18
  137. package/frontendBuild/routeClientBuilder.ts +7 -47
  138. package/frontendBuild/routesManifestArtifactSerializer.ts +5 -36
  139. package/frontendBuild/sourceMtimeIndex.test.ts +37 -94
  140. package/frontendBuild/sourceMtimeIndex.ts +19 -130
  141. package/frontendBuild/ssrBaseArtifactBuilder.ts +2 -8
  142. package/frontendBuild/styleContract.ts +1 -8
  143. package/frontendBuild/themeValidator.ts +4 -26
  144. package/frontendBuild/watchRootResolver.test.ts +23 -42
  145. package/frontendBuild/watchRootResolver.ts +5 -27
  146. package/getRelatedCnsts.ts +12 -92
  147. package/incrementalBuilder/buildBatch.proc.ts +22 -57
  148. package/incrementalBuilder/buildBatchProtocol.ts +8 -36
  149. package/incrementalBuilder/buildBatchRunner.test.ts +34 -0
  150. package/incrementalBuilder/buildBatchRunner.ts +10 -31
  151. package/incrementalBuilder/builderChannel.test.ts +24 -49
  152. package/incrementalBuilder/builderChannel.ts +6 -37
  153. package/incrementalBuilder/builderRequestRouter.test.ts +0 -8
  154. package/incrementalBuilder/builderRequestRouter.ts +5 -24
  155. package/incrementalBuilder/devWatchBatch.test.ts +14 -18
  156. package/incrementalBuilder/incrementalBuilder.host.test.ts +18 -55
  157. package/incrementalBuilder/incrementalBuilder.host.ts +18 -76
  158. package/incrementalBuilder/incrementalBuilder.proc.test.ts +81 -0
  159. package/incrementalBuilder/incrementalBuilder.proc.ts +48 -161
  160. package/index.ts +3 -15
  161. package/integration/devResourceProbe.ts +50 -56
  162. package/integration/devStability.integration.test.ts +47 -194
  163. package/integration/devStabilityHarness.test.ts +9 -29
  164. package/integration/devStabilityHarness.ts +65 -330
  165. package/integration/ssrMemoryProbe.ts +41 -167
  166. package/libSource.test.ts +4 -15
  167. package/libSource.ts +19 -31
  168. package/lint/gritRules.test.ts +6 -23
  169. package/linter.test.ts +1 -2
  170. package/linter.ts +22 -115
  171. package/mobile/mobileTarget.ts +14 -13
  172. package/package.json +2 -2
  173. package/packageExports.test.ts +3 -18
  174. package/packageExportsMap.ts +3 -19
  175. package/piDependencyPins.test.ts +3 -14
  176. package/platformTest/IdleWatchedProcess.test.ts +5 -13
  177. package/platformTest/IdleWatchedProcess.ts +1 -5
  178. package/platformTest/LinuxDockerTarget.ts +20 -35
  179. package/platformTest/PlatformTestRun.test.ts +5 -9
  180. package/platformTest/PlatformTestTarget.ts +24 -2
  181. package/platformTest/TestSnapshot.test.ts +6 -14
  182. package/platformTest/TestSnapshot.ts +1 -5
  183. package/platformTest/WindowsSshTarget.ts +6 -22
  184. package/prompter.ts +3 -6
  185. package/qualityScanner.test.ts +6 -24
  186. package/qualityScanner.ts +196 -284
  187. package/recipeScanner.test.ts +7 -12
  188. package/recipeScanner.ts +9 -38
  189. package/repoIdentity.ts +2 -9
  190. package/routeSourceValidator.ts +7 -30
  191. package/scanInfo.ts +82 -214
  192. package/selectModel.ts +1 -2
  193. package/semver.ts +3 -5
  194. package/slicePlanner.test.ts +6 -24
  195. package/slicePlanner.ts +11 -32
  196. package/spinner.test.ts +3 -8
  197. package/spinner.ts +4 -16
  198. package/sshTunnel.ts +1 -6
  199. package/ssrScanner.test.ts +1 -6
  200. package/ssrScanner.ts +103 -113
  201. package/subspace.test.ts +6 -25
  202. package/subspace.ts +31 -109
  203. package/subspaceConfig.ts +3 -11
  204. package/testHelpers.ts +49 -34
  205. package/transforms/asyncDefaultExportDetector.ts +3 -11
  206. package/transforms/barrelAnalyzer.ts +43 -135
  207. package/transforms/barrelImportsPlugin.ts +38 -175
  208. package/transforms/externalizeFrameworkPlugin.test.ts +39 -118
  209. package/transforms/externalizeFrameworkPlugin.ts +13 -87
  210. package/transforms/moduleSyntax.ts +32 -0
  211. package/transforms/rscUseClientTransform.ts +35 -20
  212. package/transforms/transforms.test.ts +153 -44
  213. package/transforms/tsconfigPackageResolver.test.ts +6 -24
  214. package/transforms/useClientBundlePlugin.ts +2 -22
  215. package/typeChecker.ts +7 -63
  216. package/uploadRelease.ts +10 -25
  217. package/workflow/artifacts.ts +18 -0
  218. package/workflow/executor.test.ts +1 -12
  219. package/workflow/executor.ts +85 -146
  220. package/workflow/index.ts +0 -1
  221. package/workflow/moduleIndex.test.ts +4 -15
  222. package/workflow/moduleIndex.ts +9 -49
  223. package/workflow/plan.ts +18 -25
  224. package/workflow/render.ts +80 -96
  225. package/workflow/source.ts +33 -56
  226. package/workflow/sourceAst.ts +50 -0
  227. package/workspaceLayout.ts +4 -19
  228. package/commandDecorators/commandMeta.ts +0 -7
  229. package/commandDecorators/types.ts +0 -10
  230. package/getDirname.ts +0 -4
  231. package/local/optimize-resource/ipcprobe/child.ts +0 -1
  232. package/local/optimize-resource/ipcprobe/parent.ts +0 -11
  233. package/workflow/primitive.ts +0 -59
@@ -36,6 +36,15 @@ export interface PlatformRunContext {
36
36
  export abstract class PlatformTestTarget {
37
37
  static readonly installIdleMs = 5 * 60_000;
38
38
  static readonly testIdleMs = 2 * 60_000;
39
+ protected static readonly gitIdentityEnv = {
40
+ GIT_AUTHOR_NAME: "akan-test",
41
+ GIT_AUTHOR_EMAIL: "akan-test@localhost",
42
+ GIT_COMMITTER_NAME: "akan-test",
43
+ GIT_COMMITTER_EMAIL: "akan-test@localhost",
44
+ GIT_CONFIG_COUNT: "1",
45
+ GIT_CONFIG_KEY_0: "init.defaultBranch",
46
+ GIT_CONFIG_VALUE_0: "main",
47
+ };
39
48
 
40
49
  abstract readonly platform: RemoteTestPlatform;
41
50
  abstract readonly policy: PlatformGatePolicy;
@@ -103,8 +112,7 @@ export abstract class PlatformTestTarget {
103
112
  const tolerable =
104
113
  failedScripts.length > 0 && failedScripts.every((pkg) => this.tolerateScriptFailures.includes(pkg));
105
114
  if (!tolerable) throw new Error(`bun install failed — see ${logPath}`);
106
- //* A failed lifecycle script makes bun skip linking the workspace root's `.bin`, so every tool the suites
107
- //* spawn from there disappears; a second pass without scripts links them.
115
+ //* A failed lifecycle script makes bun skip linking the root `.bin`; a second pass without scripts links it.
108
116
  envWarnings.push(`install script failed for ${failedScripts.join(", ")} — tolerated on ${this.platform}`);
109
117
  const relink = await this.exec(
110
118
  "bun install --frozen-lockfile --ignore-scripts",
@@ -117,4 +125,18 @@ export abstract class PlatformTestTarget {
117
125
  #logPath(context: PlatformRunContext, name: string) {
118
126
  return path.join(context.logDir, this.platform, `${name}.log`);
119
127
  }
128
+
129
+ protected static async spawnText(command: string[], stdin?: string) {
130
+ const proc = Bun.spawn(command, {
131
+ stdin: stdin === undefined ? "ignore" : new Response(stdin),
132
+ stdout: "pipe",
133
+ stderr: "pipe",
134
+ });
135
+ const [stdout, stderr, exitCode] = await Promise.all([
136
+ new Response(proc.stdout).text(),
137
+ new Response(proc.stderr).text(),
138
+ proc.exited,
139
+ ]);
140
+ return { stdout, stderr, exitCode };
141
+ }
120
142
  }
@@ -1,10 +1,10 @@
1
- import { afterAll, describe, expect, test } from "bun:test";
2
- import { mkdir, mkdtemp, rm, unlink, writeFile } from "node:fs/promises";
3
- import os from "node:os";
1
+ import { describe, expect, test } from "bun:test";
2
+ import { unlink } from "node:fs/promises";
4
3
  import path from "node:path";
4
+ import { tempDirs, writeText } from "../testHelpers";
5
5
  import { TestSnapshot } from "./TestSnapshot";
6
6
 
7
- const roots: string[] = [];
7
+ const makeRoot = tempDirs("akan-snapshot-");
8
8
  const git = async (cwd: string, ...args: string[]) => {
9
9
  const proc = Bun.spawn(["git", ...args], {
10
10
  cwd,
@@ -20,13 +20,9 @@ const git = async (cwd: string, ...args: string[]) => {
20
20
  });
21
21
  expect(await proc.exited).toBe(0);
22
22
  };
23
- const write = async (root: string, file: string, content: string) => {
24
- await mkdir(path.dirname(path.join(root, file)), { recursive: true });
25
- await writeFile(path.join(root, file), content);
26
- };
23
+ const write = async (root: string, file: string, content: string) => await writeText(path.join(root, file), content);
27
24
  const makeRepo = async () => {
28
- const root = await mkdtemp(path.join(os.tmpdir(), "akan-snapshot-"));
29
- roots.push(root);
25
+ const root = await makeRoot();
30
26
  await git(root, "init", "-q");
31
27
  await write(root, ".gitignore", "ignored/\nlocal/\n.env\n");
32
28
  await write(root, "tracked.ts", "export {};\n");
@@ -44,10 +40,6 @@ const tarEntries = async (tarPath: string) => {
44
40
  return (await new Response(proc.stdout).text()).split(/\r?\n/).filter(Boolean).sort();
45
41
  };
46
42
 
47
- afterAll(async () => {
48
- await Promise.all(roots.map((root) => rm(root, { recursive: true, force: true })));
49
- });
50
-
51
43
  describe("TestSnapshot", () => {
52
44
  test("packs what git would show — tracked and untracked, never ignored or deleted", async () => {
53
45
  const root = await makeRepo();
@@ -7,11 +7,7 @@ export interface SnapshotDrift {
7
7
  removed: string[];
8
8
  }
9
9
 
10
- /**
11
- * The exact bytes a platform run tests: every tracked file plus every untracked one git would show, which is
12
- * what `akan deploy-akan` goes on to build and publish. The per-file hashes are kept so the tree can be
13
- * compared again before publishing — a test run is only a gate if nothing changed underneath it.
14
- */
10
+ //? Per-file hashes are kept so the tree is re-checked before publishing: a run gates only if nothing changed under it.
15
11
  export class TestSnapshot {
16
12
  static readonly #secretPatterns = [
17
13
  /(^|\/)\.env(\.(?!(template|example|sample)$)[^/]+)?$/,
@@ -48,7 +48,7 @@ export class WindowsSshTarget extends PlatformTestTarget {
48
48
  [context.snapshot.tarPath, "src.tar"],
49
49
  [context.envPath, "env"],
50
50
  ] as const) {
51
- const copied = await WindowsSshTarget.#run([
51
+ const copied = await WindowsSshTarget.spawnText([
52
52
  ...this.#scpBase(),
53
53
  local,
54
54
  this.#remoteScpPath(`${runDir}\\${remoteName}`),
@@ -71,13 +71,7 @@ export class WindowsSshTarget extends PlatformTestTarget {
71
71
  "$ProgressPreference = 'SilentlyContinue'",
72
72
  "[Console]::OutputEncoding = [Text.Encoding]::UTF8",
73
73
  "'PID=' + $PID",
74
- "$env:GIT_AUTHOR_NAME = 'akan-test'",
75
- "$env:GIT_AUTHOR_EMAIL = 'akan-test@localhost'",
76
- "$env:GIT_COMMITTER_NAME = 'akan-test'",
77
- "$env:GIT_COMMITTER_EMAIL = 'akan-test@localhost'",
78
- "$env:GIT_CONFIG_COUNT = '1'",
79
- "$env:GIT_CONFIG_KEY_0 = 'init.defaultBranch'",
80
- "$env:GIT_CONFIG_VALUE_0 = 'main'",
74
+ ...Object.entries(PlatformTestTarget.gitIdentityEnv).map(([key, value]) => `$env:${key} = '${value}'`),
81
75
  `Set-Location -LiteralPath '${runDir}\\w'`,
82
76
  `cmd /c "${command} 2>&1"`,
83
77
  "exit $LASTEXITCODE",
@@ -112,10 +106,10 @@ export class WindowsSshTarget extends PlatformTestTarget {
112
106
  const vm = this.#config.utmVm;
113
107
  if (!vm || !existsSync(WindowsSshTarget.utmctlPath))
114
108
  throw new Error(`cannot reach ${this.#config.user}@${this.#host} over ssh`);
115
- const status = (await WindowsSshTarget.#run([WindowsSshTarget.utmctlPath, "status", vm])).stdout.trim();
116
- if (status !== "started") await WindowsSshTarget.#run([WindowsSshTarget.utmctlPath, "start", vm]);
109
+ const status = (await WindowsSshTarget.spawnText([WindowsSshTarget.utmctlPath, "status", vm])).stdout.trim();
110
+ if (status !== "started") await WindowsSshTarget.spawnText([WindowsSshTarget.utmctlPath, "start", vm]);
117
111
  for (let attempt = 0; attempt < 24; attempt++) {
118
- const addresses = (await WindowsSshTarget.#run([WindowsSshTarget.utmctlPath, "ip-address", vm])).stdout;
112
+ const addresses = (await WindowsSshTarget.spawnText([WindowsSshTarget.utmctlPath, "ip-address", vm])).stdout;
119
113
  const ipv4 = addresses.split(/\s+/).find((address) => /^\d+\.\d+\.\d+\.\d+$/.test(address));
120
114
  if (ipv4) this.#host = ipv4;
121
115
  if ((await this.#ssh("exit 0")).exitCode === 0) return;
@@ -152,16 +146,6 @@ export class WindowsSshTarget extends PlatformTestTarget {
152
146
  }
153
147
 
154
148
  async #ssh(script: string) {
155
- return await WindowsSshTarget.#run([...this.#sshBase(), script]);
156
- }
157
-
158
- static async #run(command: string[]) {
159
- const proc = Bun.spawn(command, { stdin: "ignore", stdout: "pipe", stderr: "pipe" });
160
- const [stdout, stderr, exitCode] = await Promise.all([
161
- new Response(proc.stdout).text(),
162
- new Response(proc.stderr).text(),
163
- proc.exited,
164
- ]);
165
- return { stdout, stderr, exitCode };
149
+ return await WindowsSshTarget.spawnText([...this.#sshBase(), script]);
166
150
  }
167
151
  }
package/prompter.ts CHANGED
@@ -1,7 +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";
4
+ import { getDirname } from "./fileSys";
5
5
 
6
6
  interface FileUpdateRequestProps {
7
7
  context: string;
@@ -33,11 +33,8 @@ export class Prompter {
33
33
  const guidelineRoot = await Prompter.#getGuidelineRoot();
34
34
  return (await fsPromise.readdir(guidelineRoot)).filter((name) => !name.startsWith("_")).sort();
35
35
  }
36
- /**
37
- * The name is checked against the directory listing rather than interpolated straight into a path:
38
- * it arrives from an MCP tool argument, and an unknown one has to name the valid set instead of
39
- * surfacing an ENOENT that carries the host's own paths.
40
- */
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.
41
38
  static async getInstruction(guideName: string): Promise<string> {
42
39
  const guideNames = await Prompter.listGuidelines();
43
40
  if (!guideNames.includes(guideName))
@@ -1,30 +1,21 @@
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
20
  test("warns on an abstract over the line limit and says how to trim it", async () => {
30
21
  const root = await makeWorkspace({
@@ -185,15 +176,6 @@ describe("AkanQualityScanner ssr rules", () => {
185
176
  });
186
177
  });
187
178
 
188
- const signalOf = (entries: string) =>
189
- [
190
- `import { endpoint, slice } from "akanjs/signal";`,
191
- `export class PostSlice extends slice(srv.post, { guards: {}, mcp: { get: true } }, (init) => ({`,
192
- entries,
193
- `})) {}`,
194
- "",
195
- ].join("\n");
196
-
197
179
  describe("AkanQualityScanner layout rules", () => {
198
180
  test("flags an unknown app root file but not a facet entrypoint", async () => {
199
181
  const root = await makeWorkspace({