@akanjs/devkit 3.0.0-alpha.2 → 3.0.0-alpha.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 (47) hide show
  1. package/README.ko.md +1 -1
  2. package/README.md +1 -1
  3. package/agentsIndex.test.ts +10 -0
  4. package/agentsIndex.ts +47 -1
  5. package/aiEditor.ts +1 -1
  6. package/akanConfig/akanConfig.test.ts +0 -10
  7. package/akanConfig/akanConfig.ts +1 -11
  8. package/akanContext.ts +43 -31
  9. package/applicationBuildRunner.test.ts +1 -1
  10. package/applicationBuildRunner.ts +1 -2
  11. package/biome.base.json +317 -0
  12. package/biomeBase.ts +9 -0
  13. package/capacitorApp.test.ts +8 -0
  14. package/capacitorApp.ts +112 -18
  15. package/executors.test.ts +42 -5
  16. package/executors.ts +43 -4
  17. package/frontendBuild/allRoutesBuilder.ts +27 -8
  18. package/frontendBuild/buildRouteClient.test.ts +28 -2
  19. package/frontendBuild/clientBuildTypes.ts +4 -0
  20. package/frontendBuild/clientEntriesBundler.ts +4 -1
  21. package/frontendBuild/cssCandidateCache.test.ts +38 -0
  22. package/frontendBuild/cssCandidateCache.ts +15 -3
  23. package/frontendBuild/cssCompiler.ts +122 -11
  24. package/frontendBuild/cssImportResolver.ts +8 -7
  25. package/frontendBuild/frontendBuild.test.ts +88 -1
  26. package/frontendBuild/precompressArtifacts.ts +35 -7
  27. package/frontendBuild/routeClientBuilder.ts +26 -7
  28. package/frontendBuild/ssrBaseArtifactBuilder.ts +20 -4
  29. package/frontendBuild/vendorSpecifiers.ts +1 -0
  30. package/getCredentials.ts +1 -3
  31. package/incrementalBuilder/devWatchBatch.test.ts +18 -20
  32. package/lint/no-bang-comment-in-client.grit +20 -0
  33. package/lint/no-import-client-in-server.grit +48 -0
  34. package/lint/no-import-server-in-client.grit +45 -0
  35. package/lint/no-model-type-in-util-zone.grit +16 -0
  36. package/lint/no-return-in-store-action.grit +35 -0
  37. package/linter.ts +17 -12
  38. package/package.json +4 -4
  39. package/qualityScanner.test.ts +23 -0
  40. package/qualityScanner.ts +5 -44
  41. package/repoIdentity.ts +42 -0
  42. package/routeSourceValidator.test.ts +41 -0
  43. package/routeSourceValidator.ts +2 -44
  44. package/scanInfo.ts +2 -43
  45. package/transforms/externalizeFrameworkPlugin.ts +0 -1
  46. package/workspaceLayout.test.ts +26 -0
  47. package/workspaceLayout.ts +60 -0
package/README.ko.md CHANGED
@@ -56,7 +56,7 @@ await runner.build();
56
56
 
57
57
  ## 요구사항
58
58
 
59
- - [Bun](https://bun.sh) `>=1.3.13`
59
+ - [Bun](https://bun.sh) `>=1.4.0`
60
60
  - TypeScript
61
61
  - Optional peer는 Capacitor integration처럼 해당 기능을 사용할 때만 필요합니다.
62
62
 
package/README.md CHANGED
@@ -60,7 +60,7 @@ defaults, the shares they derive from `AKAN_MEMORY_LIMIT`, and what a small cont
60
60
 
61
61
  ## Requirements
62
62
 
63
- - [Bun](https://bun.sh) `>=1.3.13`
63
+ - [Bun](https://bun.sh) `>=1.4.0`
64
64
  - TypeScript
65
65
  - Optional peers are only needed for the features that use them, such as Capacitor integration.
66
66
 
@@ -10,6 +10,7 @@ import {
10
10
  renderRecipeEntries,
11
11
  renderScopeAgentBlock,
12
12
  renderScopeAgentsMd,
13
+ renderScopeClaudeMd,
13
14
  upsertAgentBlock,
14
15
  } from "./agentsIndex";
15
16
  import type { RecipeInfo } from "./recipeScanner";
@@ -48,6 +49,15 @@ describe("renderScopeAgentBlock", () => {
48
49
  });
49
50
  });
50
51
 
52
+ describe("renderScopeClaudeMd", () => {
53
+ test("points at AGENTS.md and restates only the comment rule", () => {
54
+ const claude = renderScopeClaudeMd({ type: "app", name: "minimal" });
55
+ expect(claude).toContain("@AGENTS.md");
56
+ expect(claude).toContain("## Comments — Overrides Your Default");
57
+ expect(claude).not.toContain("## Recipes In Scope");
58
+ });
59
+ });
60
+
51
61
  describe("upsertAgentBlock / extractAgentBlock", () => {
52
62
  test("round-trips: fresh file → replace block → extract equals block", () => {
53
63
  const fresh = renderScopeAgentsMd({ type: "app", name: "minimal" }, "OLD");
package/agentsIndex.ts CHANGED
@@ -19,6 +19,32 @@ const loadRecipeScanner = () => (recipeScannerLoad ??= import("./recipeScanner")
19
19
  export const AGENT_BLOCK_START = "<!-- akan:agent:start -->";
20
20
  export const AGENT_BLOCK_END = "<!-- akan:agent:end -->";
21
21
 
22
+ const AGENT_VERSION_PREFIX = "<!-- akan:agent:version ";
23
+
24
+ /**
25
+ * The generating release, stamped into the workspace block. Nothing re-runs `akan agent install` on its own, so
26
+ * without a stamp a workspace carrying conventions from four releases ago is indistinguishable from a current one —
27
+ * `akan doctor` compares this against the installed devkit and says so.
28
+ */
29
+ export const stampBlockVersion = (block: string, version: string): string =>
30
+ `${AGENT_VERSION_PREFIX}${version} -->\n\n${block}`;
31
+
32
+ /** The stamped version of a committed AGENTS.md, or null when it predates stamping or carries no block. */
33
+ export const extractBlockVersion = (content: string): string | null =>
34
+ content.match(/<!-- akan:agent:version ([^\s]+) -->/)?.[1] ?? null;
35
+
36
+ /** The running `@akanjs/devkit` version; null when its package.json is unreadable, which must not fail a doctor run. */
37
+ export const readDevkitVersion = async (): Promise<string | null> => {
38
+ const { readFile } = await import("node:fs/promises");
39
+ const { getDirname } = await import("./getDirname");
40
+ try {
41
+ const raw = await readFile(`${getDirname(import.meta.url)}/package.json`, "utf-8");
42
+ return (JSON.parse(raw) as { version?: string }).version ?? null;
43
+ } catch {
44
+ return null;
45
+ }
46
+ };
47
+
22
48
  export interface AgentsIndexScope {
23
49
  type: "app" | "lib";
24
50
  name: string;
@@ -139,8 +165,28 @@ ${block}
139
165
  ${AGENT_BLOCK_END}
140
166
  `;
141
167
 
142
- /** Claude Code reads nested CLAUDE.md files as it works under a directory; keep it a thin pointer. */
168
+ export const CLAUDE_COMMENT_RULE = `## Comments — Overrides Your Default
169
+
170
+ Write **no comments** unless the comment passes the test below. This is the rule agents break most often here, so it
171
+ is repeated outside the guide: a diff that adds a comment the test rejects is a diff to redo.
172
+
173
+ Before typing \`//\`, \`/*\`, or a doc block, ask — **does this sentence carry a fact that is nowhere in the code?**
174
+
175
+ - Restates the identifier, the signature, or the line under it → delete it.
176
+ - Labels a section (\`// helpers\`, \`// state\`) or narrates a step (\`// fetch the user\`, \`// then save\`) → delete it.
177
+ - JSDoc on an ordinary function, or a why/how preamble on ordinary logic → delete it.
178
+ - Explains the edit you just made, for whoever reads the diff → say it in your reply, not in the file.
179
+ - Names a vendor or protocol quirk, an infrastructure constraint, a library gotcha, security reasoning, a math
180
+ derivation, a domain field's business meaning, a state transition, or why an obvious alternative was rejected →
181
+ keep it, one line.
182
+
183
+ That keep-list is exact — \`Comments\` in the guide is the full version. "It aids readability" and "this logic is
184
+ subtle" are not on it: rename or split the code instead. When you edit an existing file, match its density; if the
185
+ surrounding code carries none, your diff carries none.`;
186
+
143
187
  export const renderScopeClaudeMd = (scope: AgentsIndexScope): string => `# ${scope.name} — Claude Code Guide
144
188
 
145
189
  @AGENTS.md
190
+
191
+ ${CLAUDE_COMMENT_RULE}
146
192
  `;
package/aiEditor.ts CHANGED
@@ -19,7 +19,7 @@ import type { FileContent } from "./types";
19
19
 
20
20
  const MAX_ASK_TRY = 300;
21
21
 
22
- const deepSeekLlmModels = ["deepseek-chat", "deepseek-reasoner"] as const;
22
+ const deepSeekLlmModels = ["deepseek-v4-flash", "deepseek-v4-pro"] as const;
23
23
 
24
24
  const openAiLlmModels = ["gpt-5.5"] as const;
25
25
 
@@ -19,7 +19,6 @@ const packageJson: PackageJson = {
19
19
  react: "19.0.0",
20
20
  "react-dom": "19.0.0",
21
21
  "react-server-dom-webpack": "19.0.0",
22
- sharp: "1.0.0",
23
22
  "@external/runtime": "2.0.0",
24
23
  },
25
24
  };
@@ -144,8 +143,6 @@ describe("AkanAppConfig", () => {
144
143
  react: "19.0.0",
145
144
  "react-dom": "19.0.0",
146
145
  "react-server-dom-webpack": "19.0.0",
147
- croner: akanPackageJson.peerDependencies?.croner,
148
- sharp: "1.0.0",
149
146
  "@external/runtime": "2.0.0",
150
147
  },
151
148
  });
@@ -184,8 +181,6 @@ describe("AkanAppConfig", () => {
184
181
  react: runtimeDependencies.react,
185
182
  "react-dom": runtimeDependencies["react-dom"],
186
183
  "react-server-dom-webpack": runtimeDependencies["react-server-dom-webpack"],
187
- croner: runtimeDependencies.croner,
188
- sharp: runtimeDependencies.sharp,
189
184
  });
190
185
  });
191
186
 
@@ -198,9 +193,6 @@ describe("AkanAppConfig", () => {
198
193
  const multipleConfig = new AkanAppConfig(app, [], packageJson, { defaultDatabaseMode: "multiple" }, baseDevEnv);
199
194
  const clusterConfig = new AkanAppConfig(app, [], packageJson, { defaultDatabaseMode: "cluster" }, baseDevEnv);
200
195
 
201
- expect(singleConfig.getProductionPackageJson().dependencies).toMatchObject({
202
- croner: runtimeDependencies.croner,
203
- });
204
196
  expect(singleConfig.getProductionPackageJson().dependencies).not.toHaveProperty("ioredis");
205
197
  expect(singleConfig.getProductionPackageJson().dependencies).not.toHaveProperty("bullmq");
206
198
  expect(singleConfig.getProductionPackageJson().dependencies).not.toHaveProperty("@libsql/client");
@@ -210,7 +202,6 @@ describe("AkanAppConfig", () => {
210
202
  expect(multipleConfig.getProductionPackageJson().dependencies).toMatchObject({
211
203
  "@libsql/client": runtimeDependencies["@libsql/client"],
212
204
  bullmq: runtimeDependencies.bullmq,
213
- croner: runtimeDependencies.croner,
214
205
  ioredis: runtimeDependencies.ioredis,
215
206
  protobufjs: runtimeDependencies.protobufjs,
216
207
  });
@@ -218,7 +209,6 @@ describe("AkanAppConfig", () => {
218
209
 
219
210
  expect(clusterConfig.getProductionPackageJson().dependencies).toMatchObject({
220
211
  bullmq: runtimeDependencies.bullmq,
221
- croner: runtimeDependencies.croner,
222
212
  ioredis: runtimeDependencies.ioredis,
223
213
  postgres: runtimeDependencies.postgres,
224
214
  protobufjs: runtimeDependencies.protobufjs,
@@ -50,8 +50,6 @@ const DEFAULT_OPTIMIZE_IMPORTS = [
50
50
  ];
51
51
  const WORKSPACE_BARREL_FACETS = ["ui", "webkit", "common", "client", "server"] as const;
52
52
  const SSR_RUNTIME_PACKAGES = ["react", "react-dom", "react-server-dom-webpack"] as const;
53
- const NATIVE_RUNTIME_PACKAGES = ["sharp"] as const;
54
- const DEFAULT_BACKEND_RUNTIME_PACKAGES = ["croner"] as const;
55
53
  // The firebase client (push tokens) and the Capacitor toolchain — `@capacitor/cli` (`npx cap`),
56
54
  // `@capacitor/assets` (`npx @capacitor/assets`) plus the `@capacitor/core`/`ios`/`android` runtime
57
55
  // and native-platform packages that `npx cap add`/`sync` resolve from the workspace node_modules.
@@ -92,8 +90,6 @@ const DATABASE_MODE_RUNTIME_PACKAGES = {
92
90
  } satisfies Record<DatabaseMode, readonly string[]>;
93
91
  const AKAN_RUNTIME_PACKAGES = new Set<string>([
94
92
  ...SSR_RUNTIME_PACKAGES,
95
- ...NATIVE_RUNTIME_PACKAGES,
96
- ...DEFAULT_BACKEND_RUNTIME_PACKAGES,
97
93
  ...MOBILE_RUNTIME_PACKAGES,
98
94
  ...Object.values(DATABASE_MODE_RUNTIME_PACKAGES).flat(),
99
95
  ]);
@@ -428,13 +424,7 @@ CMD [${command.map((c) => `"${c}"`).join(",")}]`;
428
424
  return akanPackageJson.dependencies?.[lib] ?? akanPackageJson.peerDependencies?.[lib];
429
425
  }
430
426
  #getProductionRuntimePackages() {
431
- return [
432
- ...this.externalLibs,
433
- ...SSR_RUNTIME_PACKAGES,
434
- ...NATIVE_RUNTIME_PACKAGES,
435
- ...DEFAULT_BACKEND_RUNTIME_PACKAGES,
436
- ...this.getDatabaseModeRuntimePackages(),
437
- ];
427
+ return [...this.externalLibs, ...SSR_RUNTIME_PACKAGES, ...this.getDatabaseModeRuntimePackages()];
438
428
  }
439
429
  getDatabaseModeRuntimePackages(databaseMode: DatabaseMode = this.defaultDatabaseMode) {
440
430
  return [...DATABASE_MODE_RUNTIME_PACKAGES[databaseMode]];
package/akanContext.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  import { readdir } from "node:fs/promises";
2
2
  import path from "node:path";
3
3
  import { capitalize } from "akanjs/common";
4
+ import { extractBlockVersion, readDevkitVersion } from "./agentsIndex";
4
5
  import { AppExecutor, LibExecutor, type SysExecutor, type WorkspaceExecutor } from "./executors";
5
6
  import { FileSys } from "./fileSys";
6
7
  import { collectRecipeSources, findInlineRecipeDuplicates, scanRecipes } from "./recipeScanner";
@@ -14,6 +15,7 @@ import {
14
15
  workflowRunArtifactPath,
15
16
  workflowSyncDir,
16
17
  } from "./workflow";
18
+ import { appRootAllowedDirs, appRootAllowedFiles, isScannedAppRootEntry } from "./workspaceLayout";
17
19
 
18
20
  export type AkanContextFormat = "json" | "markdown";
19
21
  export type AkanModuleKind = "domain" | "service" | "scalar";
@@ -275,6 +277,8 @@ const validationCommands = [
275
277
  "akan test <app-or-lib-or-pkg>",
276
278
  "akan build <app-name>",
277
279
  "akan doctor --strict --format json",
280
+ "akan quality scan [--format json]",
281
+ "akan quality ssr [--format json]",
278
282
  ];
279
283
 
280
284
  const unknownGeneratedFilesFreshness: GeneratedFilesFreshness = {
@@ -313,36 +317,6 @@ const moduleShapeFiles = (module: AkanModuleContext) => {
313
317
  const constantFieldNames = (content: string) =>
314
318
  [...content.matchAll(/\b([A-Za-z_$][\w$]*)\s*:\s*field\(/g)].map((match) => match[1]).filter(Boolean);
315
319
 
316
- const appRootAllowFiles = new Set([
317
- // 스코프 에이전트 가이드 — scan(write) 이 유지 (agentsIndex.ts); scanInfo.ts 의 appRootAllowedFiles 와 동기
318
- "AGENTS.md",
319
- "CLAUDE.md",
320
- "akan.app.json",
321
- "akan.config.ts",
322
- "capacitor.config.ts",
323
- "client.ts",
324
- "main.ts",
325
- "package.json",
326
- "server.ts",
327
- "tsconfig.json",
328
- ]);
329
-
330
- const appRootAllowDirs = new Set([
331
- ".akan",
332
- "android",
333
- "common",
334
- "env",
335
- "ios",
336
- "lib",
337
- "page",
338
- "private",
339
- "public",
340
- "script",
341
- "srvkit",
342
- "ui",
343
- "webkit",
344
- ]);
345
-
346
320
  const safeReadDir = async (dirPath: string) => {
347
321
  try {
348
322
  return (await readdir(dirPath, { withFileTypes: true })).sort((a, b) => a.name.localeCompare(b.name));
@@ -647,6 +621,30 @@ export class AkanContextAnalyzer {
647
621
  };
648
622
  }
649
623
 
624
+ // The conventions in AGENTS.md ship with the package, but nothing re-renders them on `bun update` — a workspace
625
+ // keeps whichever release wrote its block until someone re-runs the install. Comparing the stamp against the
626
+ // running devkit is the only signal that the guide an agent is reading is older than the framework it describes.
627
+ static async #agentGuideDrift(workspace: WorkspaceExecutor) {
628
+ const installed = await readDevkitVersion();
629
+ if (!installed) return null;
630
+ const content = await safeReadText(path.join(workspace.workspaceRoot, "AGENTS.md"));
631
+ if (content === null) return null;
632
+ const stamped = extractBlockVersion(content);
633
+ if (stamped === installed) return null;
634
+ const hint = "Re-render the AGENTS.md managed block from the installed framework release.";
635
+ if (!stamped)
636
+ return {
637
+ code: "agent-guide-unstamped",
638
+ message: `AGENTS.md carries no generated-version stamp, so its conventions may predate @akanjs/devkit ${installed}`,
639
+ hint,
640
+ };
641
+ return {
642
+ code: "agent-guide-stale",
643
+ message: `AGENTS.md was generated by @akanjs/devkit ${stamped}, but ${installed} is installed`,
644
+ hint,
645
+ };
646
+ }
647
+
650
648
  static async doctor(
651
649
  workspace: WorkspaceExecutor,
652
650
  {
@@ -671,7 +669,8 @@ export class AkanContextAnalyzer {
671
669
  for (const app of context.apps) {
672
670
  const appPath = path.join(workspace.workspaceRoot, app.path);
673
671
  for (const entry of await safeReadDir(appPath)) {
674
- const allowed = entry.isDirectory() ? appRootAllowDirs.has(entry.name) : appRootAllowFiles.has(entry.name);
672
+ if (!isScannedAppRootEntry(entry.name)) continue;
673
+ const allowed = entry.isDirectory() ? appRootAllowedDirs.has(entry.name) : appRootAllowedFiles.has(entry.name);
675
674
  if (!allowed) {
676
675
  const action = repairAction(
677
676
  "module-shape",
@@ -691,6 +690,19 @@ export class AkanContextAnalyzer {
691
690
  }
692
691
  }
693
692
 
693
+ const agentDrift = await AkanContextAnalyzer.#agentGuideDrift(workspace);
694
+ if (agentDrift) {
695
+ const action = repairAction("generated", "akan agent install agents-md", agentDrift.hint, true);
696
+ diagnostics.push({
697
+ severity: "warning",
698
+ code: agentDrift.code,
699
+ path: "AGENTS.md",
700
+ message: agentDrift.message,
701
+ repairActions: [action],
702
+ });
703
+ repairActions.push(action);
704
+ }
705
+
694
706
  for (const sys of [...context.apps, ...context.libs]) {
695
707
  for (const module of sys.modules) {
696
708
  if (!module.abstract.exists) {
@@ -4,7 +4,7 @@ import { AKAN_OPTIONAL_BACKEND_EXTERNALS } from "./applicationBuildRunner";
4
4
  describe("ApplicationBuildRunner", () => {
5
5
  test("externalizes Akan optional backend dependencies", () => {
6
6
  expect(AKAN_OPTIONAL_BACKEND_EXTERNALS).toEqual(
7
- expect.arrayContaining(["@libsql/client", "bullmq", "croner", "ioredis", "postgres", "protobufjs"]),
7
+ expect.arrayContaining(["@libsql/client", "bullmq", "ioredis", "postgres", "protobufjs"]),
8
8
  );
9
9
  });
10
10
  });
@@ -56,7 +56,6 @@ const SSR_RENDER_EXTERNALS = [
56
56
  export const AKAN_OPTIONAL_BACKEND_EXTERNALS = [
57
57
  "@libsql/client",
58
58
  "bullmq",
59
- "croner",
60
59
  "ioredis",
61
60
  "postgres",
62
61
  "protobufjs",
@@ -112,7 +111,7 @@ export class ApplicationBuildRunner {
112
111
  () => precompressArtifacts(this.#app),
113
112
  (result) =>
114
113
  result.files > 0
115
- ? `${result.files} files, ${ApplicationBuildRunner.formatBytes(result.inputBytes)} -> ${ApplicationBuildRunner.formatBytes(result.outputBytes)}`
114
+ ? `${result.files} files, ${ApplicationBuildRunner.formatBytes(result.inputBytes)} -> gzip ${ApplicationBuildRunner.formatBytes(result.outputBytes)} / br ${ApplicationBuildRunner.formatBytes(result.brotliBytes)}`
116
115
  : "no files",
117
116
  phaseOptions,
118
117
  );
@@ -0,0 +1,317 @@
1
+ {
2
+ "$schema": "https://biomejs.dev/schemas/2.5.8/schema.json",
3
+ "vcs": {
4
+ "enabled": true,
5
+ "clientKind": "git",
6
+ "useIgnoreFile": false
7
+ },
8
+ "formatter": {
9
+ "enabled": true,
10
+ "indentStyle": "space",
11
+ "indentWidth": 2,
12
+ "lineWidth": 120
13
+ },
14
+ "linter": {
15
+ "enabled": true,
16
+ "rules": {
17
+ "preset": "recommended",
18
+ "suspicious": {
19
+ "noConsole": {
20
+ "level": "error",
21
+ "options": {
22
+ "allow": ["assert", "error", "info", "warn"]
23
+ }
24
+ },
25
+ "noArrayIndexKey": "off",
26
+ "noShadowRestrictedNames": "off",
27
+ "noUnnecessaryConditions": {
28
+ "level": "warn"
29
+ }
30
+ },
31
+ "correctness": {
32
+ "noUnusedFunctionParameters": "off",
33
+ "noUnusedImports": {
34
+ "level": "error",
35
+ "fix": "safe"
36
+ },
37
+ "useParseIntRadix": "off",
38
+ "useExhaustiveDependencies": "off",
39
+ "useHookAtTopLevel": "off"
40
+ },
41
+ "nursery": {
42
+ "useSortedClasses": {
43
+ "level": "error",
44
+ "fix": "safe",
45
+ "options": {
46
+ "attributes": ["classList"],
47
+ "functions": ["cn"]
48
+ }
49
+ }
50
+ },
51
+ "a11y": "off",
52
+ "complexity": {
53
+ "noStaticOnlyClass": "off"
54
+ },
55
+ "style": {
56
+ "useTemplate": {
57
+ "level": "warn",
58
+ "fix": "safe"
59
+ }
60
+ }
61
+ },
62
+ "domains": {
63
+ "project": "recommended",
64
+ "react": "recommended",
65
+ "test": "recommended",
66
+ "types": "all"
67
+ }
68
+ },
69
+ "javascript": {
70
+ "parser": {
71
+ "unsafeParameterDecoratorsEnabled": true
72
+ },
73
+ "formatter": {
74
+ "quoteStyle": "double"
75
+ }
76
+ },
77
+ "assist": {
78
+ "enabled": true,
79
+ "actions": {
80
+ "source": {
81
+ "organizeImports": "on"
82
+ }
83
+ }
84
+ },
85
+ "overrides": [
86
+ {
87
+ "includes": [
88
+ "**/env.client.local.ts",
89
+ "**/env.client.debug.ts",
90
+ "**/env.client.develop.ts",
91
+ "**/env.client.main.ts",
92
+ "**/env.client.testing.ts",
93
+ "**/env.server.local.ts",
94
+ "**/env.server.develop.ts",
95
+ "**/env.server.main.ts",
96
+ "**/env.server.testing.ts",
97
+ "**/env.server.type.ts",
98
+ "apps/*/lib/cnst.ts",
99
+ "apps/*/lib/dict.ts",
100
+ "apps/*/lib/db.ts",
101
+ "apps/*/lib/srv.ts",
102
+ "apps/*/lib/st.ts",
103
+ "apps/*/lib/sig.ts",
104
+ "apps/*/lib/useClient.ts",
105
+ "apps/*/lib/useServer.ts",
106
+ "apps/*/client.ts",
107
+ "apps/*/server.ts",
108
+ "apps/*/lib/*/index.tsx",
109
+ "libs/*/lib/cnst.ts",
110
+ "libs/*/lib/dict.ts",
111
+ "libs/*/lib/db.ts",
112
+ "libs/*/lib/srv.ts",
113
+ "libs/*/lib/st.ts",
114
+ "libs/*/lib/sig.ts",
115
+ "libs/*/lib/useClient.ts",
116
+ "libs/*/lib/useServer.ts",
117
+ "libs/*/client.ts",
118
+ "libs/*/server.ts",
119
+ "libs/*/index.ts",
120
+ "libs/*/lib/*/index.tsx",
121
+ "apps/*/lib/__lib/**",
122
+ "libs/*/lib/__lib/**"
123
+ ],
124
+ "linter": { "enabled": false },
125
+ "formatter": { "enabled": false },
126
+ "assist": { "enabled": false }
127
+ },
128
+ {
129
+ "includes": [
130
+ "apps/**/*.ts",
131
+ "apps/**/*.tsx",
132
+ "libs/**/*.ts",
133
+ "libs/**/*.tsx",
134
+ "!**/*.test.ts",
135
+ "!**/*.test.tsx",
136
+ "!**/*.spec.ts",
137
+ "!**/*.spec.tsx",
138
+ "!**/*.constant.ts",
139
+ "!**/common/**",
140
+ "!**/env/**"
141
+ ],
142
+ "plugins": ["./node_modules/@akanjs/devkit/lint/no-throw-raw-error.grit"]
143
+ },
144
+ {
145
+ "includes": [
146
+ "{apps,libs}/**/page/**/*.ts",
147
+ "{apps,libs}/**/page/**/*.tsx",
148
+ "{apps,libs}/**/*.Unit.tsx",
149
+ "{apps,libs}/**/*.View.tsx"
150
+ ],
151
+ "plugins": [
152
+ "./node_modules/@akanjs/devkit/lint/no-import-client-functions.grit",
153
+ "./node_modules/@akanjs/devkit/lint/no-use-client-in-server.grit"
154
+ ]
155
+ },
156
+ {
157
+ "includes": ["{apps,libs}/**/page/**/*.ts", "{apps,libs}/**/page/**/*.tsx"],
158
+ "plugins": ["./node_modules/@akanjs/devkit/lint/non-scalar-props-restricted.grit"]
159
+ },
160
+ {
161
+ "includes": [
162
+ "{apps,libs}/**/*.constant.ts",
163
+ "{apps,libs}/**/*.document.ts",
164
+ "{apps,libs}/**/*.service.ts",
165
+ "{apps,libs}/**/*.store.ts"
166
+ ],
167
+ "plugins": ["./node_modules/@akanjs/devkit/lint/no-js-private-class-method.grit"]
168
+ },
169
+ {
170
+ "includes": ["{apps,libs}/**/*.store.ts"],
171
+ "plugins": ["./node_modules/@akanjs/devkit/lint/no-return-in-store-action.grit"]
172
+ },
173
+ {
174
+ "includes": ["{apps,libs}/**/*.signal.ts"],
175
+ "plugins": ["./node_modules/@akanjs/devkit/lint/no-redeclare-predefined-endpoint.grit"]
176
+ },
177
+ {
178
+ "includes": ["{apps,libs}/**/*.Util.tsx", "{apps,libs}/**/*.Zone.tsx"],
179
+ "plugins": ["./node_modules/@akanjs/devkit/lint/no-model-type-in-util-zone.grit"]
180
+ },
181
+ {
182
+ "includes": [
183
+ "**/ui/**/*.ts",
184
+ "**/ui/**/*.tsx",
185
+ "**/webkit/**/*.ts",
186
+ "**/webkit/**/*.tsx",
187
+ "**/common/**/*.ts",
188
+ "**/page/**/*.tsx",
189
+ "**/*.constant.ts",
190
+ "**/*.store.ts",
191
+ "**/*.Template.tsx",
192
+ "**/*.Unit.tsx",
193
+ "**/*.Util.tsx",
194
+ "**/*.View.tsx",
195
+ "**/*.Zone.tsx",
196
+ "!**/*.test.ts",
197
+ "!**/*.test.tsx",
198
+ "!**/*.spec.ts",
199
+ "!**/*.spec.tsx"
200
+ ],
201
+ "plugins": ["./node_modules/@akanjs/devkit/lint/no-bang-comment-in-client.grit"]
202
+ },
203
+ {
204
+ "includes": [
205
+ "{apps,libs}/**/*.constant.ts",
206
+ "{apps,libs}/**/*.dictionary.ts",
207
+ "{apps,libs}/**/*.document.ts",
208
+ "{apps,libs}/**/*.service.ts",
209
+ "{apps,libs}/**/*.signal.ts",
210
+ "{apps,libs}/**/*.store.ts",
211
+ "{apps,libs}/**/*.Template.tsx",
212
+ "{apps,libs}/**/*.Unit.tsx",
213
+ "{apps,libs}/**/*.Util.tsx",
214
+ "{apps,libs}/**/*.View.tsx",
215
+ "{apps,libs}/**/*.Zone.tsx"
216
+ ],
217
+ "plugins": ["./node_modules/@akanjs/devkit/lint/no-deep-internal-import.grit"]
218
+ },
219
+ {
220
+ "includes": [
221
+ "{apps,libs}/**/page/**/*.ts",
222
+ "{apps,libs}/**/page/**/*.tsx",
223
+ "{apps,libs}/**/index.ts",
224
+ "{apps,libs}/**/index.tsx",
225
+ "{apps,libs}/**/cnst.ts",
226
+ "{apps,libs}/**/db.ts",
227
+ "{apps,libs}/**/dict.ts",
228
+ "{apps,libs}/**/option.ts",
229
+ "{apps,libs}/**/sig.ts",
230
+ "{apps,libs}/**/srv.ts",
231
+ "{apps,libs}/**/st.ts",
232
+ "{apps,libs}/**/*.constant.ts",
233
+ "{apps,libs}/**/*.dictionary.ts",
234
+ "{apps,libs}/**/*.document.ts",
235
+ "{apps,libs}/**/*.service.ts",
236
+ "{apps,libs}/**/*.signal.ts",
237
+ "{apps,libs}/**/*.signal.test.ts",
238
+ "{apps,libs}/**/*.service.test.ts",
239
+ "{apps,libs}/**/*.store.ts",
240
+ "{apps,libs}/**/*.Template.tsx",
241
+ "{apps,libs}/**/*.Unit.tsx",
242
+ "{apps,libs}/**/*.Util.tsx",
243
+ "{apps,libs}/**/*.View.tsx",
244
+ "{apps,libs}/**/*.Zone.tsx"
245
+ ],
246
+ "plugins": [
247
+ "./node_modules/@akanjs/devkit/lint/no-import-external-library.grit",
248
+ "./node_modules/@akanjs/devkit/lint/no-deep-internal-import.grit"
249
+ ]
250
+ },
251
+ {
252
+ "includes": [
253
+ "apps/**/*.tsx",
254
+ "libs/**/*.tsx",
255
+ "apps/**/ui/**/*.ts",
256
+ "libs/**/ui/**/*.ts",
257
+ "apps/**/webkit/**/*.ts",
258
+ "libs/**/webkit/**/*.ts",
259
+ "apps/**/page/**/*.ts",
260
+ "libs/**/page/**/*.ts",
261
+ "apps/**/common/**/*.ts",
262
+ "libs/**/common/**/*.ts",
263
+ "apps/**/*.store.ts",
264
+ "libs/**/*.store.ts",
265
+ "apps/**/*.constant.ts",
266
+ "libs/**/*.constant.ts",
267
+ "!**/*.test.ts",
268
+ "!**/*.test.tsx",
269
+ "!**/*.spec.ts",
270
+ "!**/*.spec.tsx"
271
+ ],
272
+ "plugins": ["./node_modules/@akanjs/devkit/lint/no-import-server-in-client.grit"]
273
+ },
274
+ {
275
+ "includes": [
276
+ "apps/**/*.document.ts",
277
+ "libs/**/*.document.ts",
278
+ "apps/**/*.dictionary.ts",
279
+ "libs/**/*.dictionary.ts",
280
+ "apps/**/*.service.ts",
281
+ "libs/**/*.service.ts",
282
+ "apps/**/*.signal.ts",
283
+ "libs/**/*.signal.ts",
284
+ "apps/**/srvkit/**/*.ts",
285
+ "libs/**/srvkit/**/*.ts",
286
+ "apps/**/common/**/*.ts",
287
+ "libs/**/common/**/*.ts",
288
+ "apps/**/*.constant.ts",
289
+ "libs/**/*.constant.ts",
290
+ "!**/*.test.ts",
291
+ "!**/*.test.tsx",
292
+ "!**/*.spec.ts",
293
+ "!**/*.spec.tsx"
294
+ ],
295
+ "plugins": ["./node_modules/@akanjs/devkit/lint/no-import-client-in-server.grit"]
296
+ },
297
+ {
298
+ "includes": [
299
+ "apps/**/*.ts",
300
+ "apps/**/*.tsx",
301
+ "libs/**/*.ts",
302
+ "libs/**/*.tsx",
303
+ "!**/*.test.ts",
304
+ "!**/*.test.tsx",
305
+ "!**/*.spec.ts",
306
+ "!**/*.spec.tsx"
307
+ ],
308
+ "plugins": [
309
+ "./node_modules/@akanjs/devkit/lint/no-raw-palette-class.grit",
310
+ "./node_modules/@akanjs/devkit/lint/no-arbitrary-color.grit",
311
+ "./node_modules/@akanjs/devkit/lint/no-daisyui-legacy-class.grit",
312
+ "./node_modules/@akanjs/devkit/lint/no-inline-color.grit",
313
+ "./node_modules/@akanjs/devkit/lint/no-interpolated-arbitrary-class.grit"
314
+ ]
315
+ }
316
+ ]
317
+ }
package/biomeBase.ts ADDED
@@ -0,0 +1,9 @@
1
+ /** `extends` target for a workspace `biome.json`; Biome resolves it through node_modules. */
2
+ export const biomeBaseConfig = "@akanjs/devkit/biome.base.json";
3
+
4
+ // Biome moves rules between groups across minors — `noUnnecessaryConditions` sat in `nursery` at 2.4 and moved to
5
+ // `suspicious` at 2.5 — and the stale position is a hard "unknown key" error, not a warning. A workspace whose
6
+ // Biome disagrees with the shipped base config therefore fails to load it at all, which is why the version is
7
+ // pinned here instead of resolved to latest at create time. Bump this and `biome.base.json` in one commit, and run
8
+ // `biome migrate --write` in the workspace root and in `pkgs/@akanjs/devkit` so both configs move together.
9
+ export const biomeVersion = "2.5.8";