@akanjs/devkit 3.1.1-rc.9 → 3.1.1

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 (128) hide show
  1. package/CHANGELOG.md +117 -0
  2. package/agentAssets.test.ts +65 -0
  3. package/agentAssets.ts +166 -0
  4. package/agentsIndex.ts +4 -4
  5. package/akanApp/akanApp.host.test.ts +12 -0
  6. package/akanApp/akanApp.host.ts +12 -0
  7. package/akanConfig/akanConfig.test.ts +30 -0
  8. package/akanConfig/akanConfig.ts +10 -0
  9. package/akanConfig/productionLockfile.test.ts +31 -0
  10. package/akanConfig/productionLockfile.ts +2 -0
  11. package/akanContext.ts +129 -4
  12. package/applicationBuildRunner.test.ts +17 -0
  13. package/applicationBuildRunner.ts +3 -1
  14. package/biome.base.json +4 -1
  15. package/codeAgent/agent/CodeAgent.ts +4 -0
  16. package/codeAgent/agent/SubagentPool.ts +5 -0
  17. package/codeAgent/agent/akanSystemPrompt.ts +1 -1
  18. package/codeAgent/agent/codeAgent.test.ts +41 -2
  19. package/codeAgent/feedback/PreviewFeedback.ts +2 -2
  20. package/codeAgent/feedback/VerifyFeedback.ts +3 -4
  21. package/codeAgent/index.ts +2 -2
  22. package/codeAgent/skills/akan-agent/SKILL.md +36 -0
  23. package/codeAgent/skills/akan-code/SKILL.md +1 -0
  24. package/codeAgent/skills/akan-debug/SKILL.md +40 -6
  25. package/codeAgent/skills/akan-deps/SKILL.md +1 -2
  26. package/codeAgent/skills/akan-mcp/SKILL.md +40 -0
  27. package/codeAgent/skills/akan-page/SKILL.md +44 -0
  28. package/codeAgent/skills/akan-query/SKILL.md +38 -0
  29. package/codeAgent/skills/akan-runtime/SKILL.md +40 -0
  30. package/codeAgent/skills/akan-scaffold/SKILL.md +16 -6
  31. package/codeAgent/skills/akan-transport/SKILL.md +40 -0
  32. package/codeAgent/skills/akan-validate/SKILL.md +13 -11
  33. package/codeAgent/tools/AkanCli.ts +2 -1
  34. package/codeAgent/tools/AkanCodePlugins.ts +5 -1
  35. package/codeAgent/tools/AkanEditScope.ts +8 -3
  36. package/codeAgent/tools/AkanToolPack.ts +4 -10
  37. package/codeAgent/tools/AkanVerifier.ts +13 -31
  38. package/codeAgent/tools/akanCodePlugins.test.ts +60 -58
  39. package/commandDecorators/workspaceVersionCheck.test.ts +17 -7
  40. package/commandDecorators/workspaceVersionCheck.ts +4 -1
  41. package/commentDensityScanner.test.ts +121 -0
  42. package/commentDensityScanner.ts +101 -0
  43. package/componentSizeScanner.test.ts +36 -0
  44. package/componentSizeScanner.ts +52 -0
  45. package/componentWriteChainScanner.test.ts +137 -0
  46. package/componentWriteChainScanner.ts +104 -0
  47. package/conventionSet.test.ts +73 -0
  48. package/conventionSet.ts +67 -0
  49. package/csrE2e/ssrRegistry.e2e.test.ts +6 -1
  50. package/deprecatedCommentScanner.test.ts +54 -0
  51. package/deprecatedCommentScanner.ts +30 -0
  52. package/endpointReach.test.ts +58 -0
  53. package/endpointReach.ts +80 -0
  54. package/executors.test.ts +0 -4
  55. package/executors.ts +18 -2
  56. package/index.ts +1 -0
  57. package/integration/devStability.integration.test.ts +9 -5
  58. package/kindBranchScanner.test.ts +89 -0
  59. package/kindBranchScanner.ts +158 -0
  60. package/lint/__fixtures__/no-oversized-list-limit/bad.tsx +18 -0
  61. package/lint/__fixtures__/no-oversized-list-limit/good.tsx +23 -0
  62. package/lint/no-oversized-list-limit.grit +73 -0
  63. package/linter.test.ts +12 -0
  64. package/linter.ts +35 -12
  65. package/maintainabilityBaseline.test.ts +280 -0
  66. package/maintainabilityBaseline.ts +220 -0
  67. package/maintainabilityContract.test.ts +846 -0
  68. package/maintainabilityContract.ts +404 -0
  69. package/maintainabilityExceptionScanner.test.ts +90 -0
  70. package/maintainabilityExceptionScanner.ts +85 -0
  71. package/maintainabilityLimits.ts +60 -0
  72. package/maintainabilityReport.test.ts +130 -0
  73. package/maintainabilityReport.ts +153 -0
  74. package/mobile/desktopRelease.test.ts +1 -1
  75. package/mobile/desktopRelease.ts +3 -1
  76. package/package.json +3 -2
  77. package/platformTest/LinuxDockerTarget.ts +3 -0
  78. package/{codeAgent/feedback → preview}/PreviewChrome.ts +13 -7
  79. package/preview/PreviewView.ts +219 -0
  80. package/preview/index.ts +2 -0
  81. package/preview/previewView.test.ts +76 -0
  82. package/prompter.ts +7 -0
  83. package/qualityScanner.test.ts +75 -0
  84. package/qualityScanner.ts +17 -1
  85. package/routeSourceValidator.test.ts +11 -2
  86. package/routeSourceValidator.ts +14 -2
  87. package/serviceLoopCallScanner.test.ts +56 -0
  88. package/serviceLoopCallScanner.ts +122 -0
  89. package/signalMutationIndex.test.ts +15 -0
  90. package/signalMutationIndex.ts +1 -1
  91. package/sourceComments.test.ts +45 -0
  92. package/sourceComments.ts +70 -0
  93. package/storeAccessScanner.test.ts +84 -0
  94. package/storeAccessScanner.ts +71 -0
  95. package/storeSource.ts +159 -0
  96. package/storeWriteScanner.test.ts +292 -0
  97. package/storeWriteScanner.ts +50 -0
  98. package/testOnlyExportScanner.test.ts +83 -0
  99. package/testOnlyExportScanner.ts +124 -0
  100. package/typeChecker.ts +32 -0
  101. package/typecheck/projectTypecheck.test.ts +18 -0
  102. package/typecheck/projectTypecheck.ts +29 -5
  103. package/typecheck/typecheck.proc.ts +6 -0
  104. package/typecheck/typecheckFailure.ts +13 -0
  105. package/types.ts +1 -0
  106. package/workflow/artifacts.ts +16 -2
  107. package/workflow/executor.ts +22 -7
  108. package/workflow/guardCatalog.test.ts +59 -0
  109. package/workflow/guardCatalog.ts +108 -0
  110. package/workflow/index.ts +4 -0
  111. package/workflow/interactionScaffold.ts +122 -0
  112. package/workflow/mutationScaffold.ts +519 -0
  113. package/workflow/mutationSource.test.ts +108 -0
  114. package/workflow/mutationSource.ts +219 -0
  115. package/workflow/plan.ts +54 -0
  116. package/workflow/render.ts +19 -4
  117. package/workflow/source.test.ts +185 -0
  118. package/workflow/source.ts +151 -24
  119. package/workflow/types.ts +18 -1
  120. package/workspaceLayout.ts +2 -0
  121. package/writePath.test.ts +109 -0
  122. package/writePath.ts +219 -0
  123. package/writePlacementScanner.test.ts +77 -0
  124. package/writePlacementScanner.ts +50 -0
  125. package/writingActionIndex.test.ts +99 -0
  126. package/writingActionIndex.ts +63 -0
  127. package/codeAgent/feedback/PreviewView.ts +0 -95
  128. /package/{codeAgent/feedback → preview}/previewChrome.test.ts +0 -0
@@ -0,0 +1,108 @@
1
+ import type { Sys } from "../commandDecorators";
2
+
3
+ interface CatalogEntry {
4
+ name: string;
5
+ specifier: string;
6
+ }
7
+
8
+ interface ActingUser {
9
+ argName: string;
10
+ specifier: string;
11
+ param: string;
12
+ value: string;
13
+ serviceParam: string;
14
+ }
15
+
16
+ export class GuardCatalog {
17
+ static readonly frameworkGuards = ["Public"] as const;
18
+ static readonly #actingArgOf = new Map<string, Omit<ActingUser, "specifier">>([
19
+ ["User", { argName: "Self", param: "self", value: "self.id", serviceParam: "selfId" }],
20
+ [
21
+ "SignedIn",
22
+ { argName: "CurrentUserId", param: "currentUserId", value: "currentUserId", serviceParam: "currentUserId" },
23
+ ],
24
+ ]);
25
+ readonly #guards: CatalogEntry[];
26
+ readonly #internalArgs: CatalogEntry[];
27
+ readonly reachedLibs: string[];
28
+
29
+ constructor(guards: CatalogEntry[], internalArgs: CatalogEntry[], reachedLibs: string[]) {
30
+ this.#guards = guards;
31
+ this.#internalArgs = internalArgs;
32
+ this.reachedLibs = reachedLibs;
33
+ }
34
+
35
+ static async load(sys: Sys) {
36
+ const reachedLibs = await GuardCatalog.#reachedLibs(sys);
37
+ const scopes = [{ type: sys.type, name: sys.name }, ...reachedLibs.map((name) => ({ type: "lib" as const, name }))];
38
+ const exports = (await Promise.all(scopes.map((scope) => GuardCatalog.#srvkitExports(sys, scope)))).flat();
39
+ const frameworkGuards = GuardCatalog.frameworkGuards.map((name) => ({ name, specifier: "akanjs/signal" }));
40
+ return new GuardCatalog(
41
+ [...exports.filter((entry) => entry.kind === "Guard"), ...frameworkGuards],
42
+ exports.filter((entry) => entry.kind === "InternalArg"),
43
+ reachedLibs,
44
+ );
45
+ }
46
+
47
+ get names() {
48
+ return [...new Set(this.#guards.map((guard) => guard.name))];
49
+ }
50
+
51
+ resolve(name: string) {
52
+ const matches = this.#guards.filter((guard) => guard.name === name);
53
+ const specifiers = [...new Set(matches.map((guard) => guard.specifier))];
54
+ return { specifier: specifiers[0] ?? null, ambiguous: specifiers.length > 1 ? specifiers : null };
55
+ }
56
+
57
+ actingUserOf(guards: readonly string[]): ActingUser | null {
58
+ for (const guard of guards) {
59
+ const acting = GuardCatalog.#actingArgOf.get(guard);
60
+ const specifier = acting
61
+ ? this.#internalArgs.find((entry) => entry.name === acting.argName)?.specifier
62
+ : undefined;
63
+ if (acting && specifier) return { ...acting, specifier };
64
+ }
65
+ return null;
66
+ }
67
+
68
+ static async #reachedLibs(sys: Sys) {
69
+ const workspaceLibs = await sys.workspace.getLibs();
70
+ const depsOf = async (type: "app" | "lib", name: string) => {
71
+ const jsonPath = `${type}s/${name}/akan.${type}.json`;
72
+ if (!(await sys.workspace.exists(jsonPath))) return null;
73
+ const { libDeps } = (await sys.workspace.readJson(jsonPath)) as { libDeps?: unknown };
74
+ return Array.isArray(libDeps) ? libDeps.filter((lib): lib is string => workspaceLibs.includes(lib)) : [];
75
+ };
76
+ const direct = await depsOf(sys.type, sys.name);
77
+ if (!direct) return workspaceLibs.filter((lib) => sys.type !== "lib" || lib !== sys.name);
78
+ const reached = new Set<string>();
79
+ const queue = [...direct];
80
+ while (queue.length) {
81
+ const lib = queue.shift();
82
+ if (!lib || reached.has(lib) || (sys.type === "lib" && lib === sys.name)) continue;
83
+ reached.add(lib);
84
+ queue.push(...((await depsOf("lib", lib)) ?? []));
85
+ }
86
+ return [...reached];
87
+ }
88
+
89
+ static async #srvkitExports(sys: Sys, scope: { type: "app" | "lib"; name: string }) {
90
+ const dir = `${scope.type}s/${scope.name}/srvkit`;
91
+ const files = (await sys.workspace.readdir(dir)).filter(
92
+ (file) => /\.tsx?$/.test(file) && !/\.(test|spec)\.tsx?$/.test(file) && file !== "index.ts",
93
+ );
94
+ const specifier = `@${scope.type}s/${scope.name}/srvkit`;
95
+ const contents = await Promise.all(files.map((file) => sys.workspace.readFile(`${dir}/${file}`)));
96
+ return contents.flatMap((content) =>
97
+ [...content.matchAll(/export class ([A-Z][A-Za-z0-9_]*)\s+implements\s+([^{]+)\{/g)].flatMap((match) => {
98
+ const implemented = (match[2] ?? "").split(",").map((name) => name.replace(/<.*$/s, "").trim());
99
+ const kind = implemented.includes("Guard")
100
+ ? "Guard"
101
+ : implemented.includes("InternalArg")
102
+ ? "InternalArg"
103
+ : null;
104
+ return kind && match[1] ? [{ kind, name: match[1], specifier }] : [];
105
+ }),
106
+ );
107
+ }
108
+ }
package/workflow/index.ts CHANGED
@@ -1,6 +1,10 @@
1
1
  export * from "./artifacts";
2
2
  export * from "./executor";
3
+ export * from "./guardCatalog";
4
+ export * from "./interactionScaffold";
3
5
  export * from "./moduleIndex";
6
+ export * from "./mutationScaffold";
7
+ export * from "./mutationSource";
4
8
  export * from "./plan";
5
9
  export * from "./render";
6
10
  export * from "./rolloutGate";
@@ -0,0 +1,122 @@
1
+ import { capitalize, lowerlize } from "akanjs/common";
2
+ import type { Sys } from "../commandDecorators";
3
+ import type { ScaffoldFile, WorkflowDiagnostic } from "./types";
4
+
5
+ export class InteractionScaffold {
6
+ readonly name: string;
7
+ readonly className: string;
8
+ readonly hookName: string;
9
+ readonly #commonSpecifier: string;
10
+
11
+ constructor(sys: Sys, name: string) {
12
+ this.name = name;
13
+ this.className = capitalize(name);
14
+ this.hookName = `use${this.className}`;
15
+ this.#commonSpecifier = `@${sys.type}s/${sys.name}/common`;
16
+ }
17
+
18
+ static nameOf(raw: string) {
19
+ const name = lowerlize(raw.replace(/^use(?=[A-Z])/, ""));
20
+ return /^[a-z][A-Za-z0-9]*$/.test(name) ? name : null;
21
+ }
22
+
23
+ static async build(sys: Sys, raw: string | null) {
24
+ const diagnostics: WorkflowDiagnostic[] = [];
25
+ const name = raw ? InteractionScaffold.nameOf(raw) : null;
26
+ if (!name) {
27
+ diagnostics.push({
28
+ severity: "error",
29
+ code: "create-interaction-name-invalid",
30
+ input: "interaction",
31
+ message: `Interaction name "${raw ?? ""}" must be camelCase (boardKeys), optionally prefixed with use.`,
32
+ });
33
+ return { diagnostics, files: [] as ScaffoldFile[] };
34
+ }
35
+ const scaffold = new InteractionScaffold(sys, name);
36
+ const files = scaffold.files;
37
+ const taken = (
38
+ await Promise.all(
39
+ scaffold.claimedPaths.map(async (path) =>
40
+ (await sys.exists(path)) ? `${sys.type}s/${sys.name}/${path}` : null,
41
+ ),
42
+ )
43
+ ).filter((path): path is string => !!path);
44
+ if (taken.length)
45
+ diagnostics.push({
46
+ severity: "error",
47
+ code: "create-interaction-exists",
48
+ input: "interaction",
49
+ message: `${taken.join(", ")} already exists; pick another interaction name or extend the existing files.`,
50
+ });
51
+ return { diagnostics, files: taken.length ? [] : files };
52
+ }
53
+
54
+ get claimedPaths() {
55
+ const { name, hookName } = this;
56
+ return [
57
+ `common/${name}.ts`,
58
+ `common/${name}.tsx`,
59
+ `common/${name}.test.ts`,
60
+ `webkit/${hookName}.tsx`,
61
+ `webkit/${hookName}.ts`,
62
+ ];
63
+ }
64
+
65
+ get files(): ScaffoldFile[] {
66
+ return [
67
+ {
68
+ path: `common/${this.name}.ts`,
69
+ content: this.logicSource,
70
+ action: "create",
71
+ reason: "Pure interaction logic.",
72
+ },
73
+ {
74
+ path: `common/${this.name}.test.ts`,
75
+ content: this.testSource,
76
+ action: "create",
77
+ reason: "Test of the pure interaction logic.",
78
+ },
79
+ { path: `webkit/${this.hookName}.tsx`, content: this.hookSource, action: "create", reason: "Client hook." },
80
+ ];
81
+ }
82
+
83
+ get logicSource() {
84
+ return [
85
+ `export class ${this.className} {`,
86
+ " static next<State extends object>(state: State, input: Partial<State>) {",
87
+ " return { ...state, ...input };",
88
+ " }",
89
+ "}",
90
+ "",
91
+ ].join("\n");
92
+ }
93
+
94
+ get testSource() {
95
+ return [
96
+ 'import { describe, expect, it } from "bun:test";',
97
+ `import { ${this.className} } from "./${this.name}";`,
98
+ "",
99
+ `describe(${JSON.stringify(this.className)}, () => {`,
100
+ ' it("applies an input over the state it is handed", () => {',
101
+ ` expect(${this.className}.next({ idx: 0, isOpen: false }, { idx: 1 })).toEqual({ idx: 1, isOpen: false });`,
102
+ " });",
103
+ "});",
104
+ "",
105
+ ].join("\n");
106
+ }
107
+
108
+ get hookSource() {
109
+ return [
110
+ '"use client";',
111
+ `import { ${this.className} } from "${this.#commonSpecifier}";`,
112
+ "",
113
+ `export const ${this.hookName} = <State extends object>(state: State, commit: (next: State) => void) => {`,
114
+ " const change = async (input: Partial<State>) => {",
115
+ ` commit(${this.className}.next(state, input));`,
116
+ " };",
117
+ " return { change };",
118
+ "};",
119
+ "",
120
+ ].join("\n");
121
+ }
122
+ }