@akanjs/devkit 3.1.1-rc.9 → 3.1.2-rc.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (188) 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 +105 -0
  8. package/akanConfig/akanConfig.ts +21 -1
  9. package/akanConfig/productionLockfile.test.ts +31 -0
  10. package/akanConfig/productionLockfile.ts +2 -0
  11. package/akanContext.ts +129 -4
  12. package/akanMcpContract.test.ts +28 -1
  13. package/akanMcpContract.ts +51 -3
  14. package/applicationBuildRunner.test.ts +53 -0
  15. package/applicationBuildRunner.ts +7 -4
  16. package/artifact/implicitRootLayout.ts +4 -2
  17. package/biome.base.json +4 -1
  18. package/cloud/globalConfig.test.ts +34 -0
  19. package/cloud/globalConfig.ts +2 -2
  20. package/codeAgent/agent/AkanCodeEnv.ts +26 -0
  21. package/codeAgent/agent/AkanCodeServices.ts +5 -2
  22. package/codeAgent/agent/CodeAgent.ts +11 -0
  23. package/codeAgent/agent/CodeAgentGate.ts +156 -10
  24. package/codeAgent/agent/CodeAgentProxy.ts +5 -4
  25. package/codeAgent/agent/CodeAgentRpcHost.ts +3 -0
  26. package/codeAgent/agent/CodeAgentRpcListener.ts +102 -16
  27. package/codeAgent/agent/SubagentPool.ts +5 -0
  28. package/codeAgent/agent/WorkspaceTrust.ts +200 -0
  29. package/codeAgent/agent/akanCodePaths.ts +8 -1
  30. package/codeAgent/agent/akanSystemPrompt.ts +5 -1
  31. package/codeAgent/agent/codeAgent.test.ts +41 -2
  32. package/codeAgent/agent/codeAgentContract.test.ts +119 -0
  33. package/codeAgent/agent/codeAgentHosts.ts +30 -0
  34. package/codeAgent/agent/codeAgentProxy.test.ts +53 -1
  35. package/codeAgent/agent/codeAgentRpcListener.test.ts +115 -13
  36. package/codeAgent/agent/stringArg.ts +1 -1
  37. package/codeAgent/feedback/PreviewFeedback.ts +2 -2
  38. package/codeAgent/feedback/VerifyFeedback.ts +3 -4
  39. package/codeAgent/index.ts +2 -2
  40. package/codeAgent/skills/akan-agent/SKILL.md +43 -0
  41. package/codeAgent/skills/akan-code/SKILL.md +38 -11
  42. package/codeAgent/skills/akan-debug/SKILL.md +41 -6
  43. package/codeAgent/skills/akan-deps/SKILL.md +1 -2
  44. package/codeAgent/skills/akan-err/SKILL.md +2 -1
  45. package/codeAgent/skills/akan-mcp/SKILL.md +40 -0
  46. package/codeAgent/skills/akan-page/SKILL.md +44 -0
  47. package/codeAgent/skills/akan-query/SKILL.md +39 -0
  48. package/codeAgent/skills/akan-runtime/SKILL.md +43 -0
  49. package/codeAgent/skills/akan-scaffold/SKILL.md +16 -6
  50. package/codeAgent/skills/akan-transport/SKILL.md +40 -0
  51. package/codeAgent/skills/akan-validate/SKILL.md +13 -11
  52. package/codeAgent/tools/AkanCli.ts +2 -1
  53. package/codeAgent/tools/AkanCodePlugins.ts +5 -1
  54. package/codeAgent/tools/AkanEditScope.ts +8 -3
  55. package/codeAgent/tools/AkanToolPack.ts +4 -10
  56. package/codeAgent/tools/AkanVerifier.ts +13 -31
  57. package/codeAgent/tools/McpServerConfig.ts +6 -4
  58. package/codeAgent/tools/McpSignIn.ts +7 -7
  59. package/codeAgent/tools/McpTokenStore.ts +63 -16
  60. package/codeAgent/tools/McpToolPack.ts +8 -1
  61. package/codeAgent/tools/WebToolPack.ts +50 -6
  62. package/codeAgent/tools/akanCodePlugins.test.ts +130 -60
  63. package/codeAgent/tools/mcpOAuth.test.ts +50 -14
  64. package/codeAgent/tools/mcpServerConfig.test.ts +130 -1
  65. package/commandDecorators/command.ts +4 -1
  66. package/commandDecorators/workspaceVersionCheck.test.ts +17 -7
  67. package/commandDecorators/workspaceVersionCheck.ts +4 -1
  68. package/commentDensityScanner.test.ts +121 -0
  69. package/commentDensityScanner.ts +101 -0
  70. package/componentSizeScanner.test.ts +36 -0
  71. package/componentSizeScanner.ts +52 -0
  72. package/componentWriteChainScanner.test.ts +137 -0
  73. package/componentWriteChainScanner.ts +104 -0
  74. package/conventionSet.test.ts +73 -0
  75. package/conventionSet.ts +67 -0
  76. package/createTunnel.test.ts +200 -2
  77. package/createTunnel.ts +80 -6
  78. package/csrE2e/ssrRegistry.e2e.test.ts +6 -1
  79. package/deprecatedCommentScanner.test.ts +54 -0
  80. package/deprecatedCommentScanner.ts +30 -0
  81. package/endpointReach.test.ts +58 -0
  82. package/endpointReach.ts +80 -0
  83. package/envSeed.test.ts +90 -0
  84. package/envSeed.ts +68 -0
  85. package/executors.test.ts +19 -5
  86. package/executors.ts +59 -4
  87. package/fileSys.test.ts +30 -2
  88. package/fileSys.ts +14 -0
  89. package/formSetterScanner.test.ts +44 -1
  90. package/formSetterScanner.ts +53 -8
  91. package/frontendBuild/csrArtifactBuilder.ts +7 -0
  92. package/frontendBuild/mcpAppViewBuilder.test.ts +63 -0
  93. package/frontendBuild/mcpAppViewBuilder.ts +7 -1
  94. package/frontendBuild/optionalPeers.test.ts +132 -0
  95. package/index.ts +1 -0
  96. package/integration/devStability.integration.test.ts +9 -5
  97. package/integration/ssrMemoryProbe.ts +6 -0
  98. package/kindBranchScanner.test.ts +89 -0
  99. package/kindBranchScanner.ts +158 -0
  100. package/lint/__fixtures__/no-oversized-list-limit/bad.tsx +18 -0
  101. package/lint/__fixtures__/no-oversized-list-limit/good.tsx +23 -0
  102. package/lint/no-oversized-list-limit.grit +73 -0
  103. package/lint/no-unguarded-endpoint.grit +5 -5
  104. package/linter.test.ts +12 -0
  105. package/linter.ts +35 -12
  106. package/maintainabilityBaseline.test.ts +280 -0
  107. package/maintainabilityBaseline.ts +220 -0
  108. package/maintainabilityContract.test.ts +846 -0
  109. package/maintainabilityContract.ts +404 -0
  110. package/maintainabilityExceptionScanner.test.ts +90 -0
  111. package/maintainabilityExceptionScanner.ts +85 -0
  112. package/maintainabilityLimits.ts +60 -0
  113. package/maintainabilityReport.test.ts +130 -0
  114. package/maintainabilityReport.ts +153 -0
  115. package/mobile/desktopRelease.test.ts +1 -1
  116. package/mobile/desktopRelease.ts +3 -1
  117. package/mobile/desktopServerStage.test.ts +1 -0
  118. package/mobile/desktopServerStage.ts +3 -1
  119. package/mobile/nativeApp.test.ts +20 -5
  120. package/mobile/nativeApp.ts +4 -1
  121. package/mobile/nativeConfig.test.ts +209 -5
  122. package/mobile/nativeConfig.ts +127 -6
  123. package/mobile/nativePublicEnv.test.ts +2 -0
  124. package/mobile/nativePublicEnv.ts +13 -0
  125. package/package.json +4 -2
  126. package/platformTest/LinuxDockerTarget.ts +3 -0
  127. package/{codeAgent/feedback → preview}/PreviewChrome.ts +13 -7
  128. package/preview/PreviewView.ts +219 -0
  129. package/preview/index.ts +2 -0
  130. package/preview/previewView.test.ts +76 -0
  131. package/prompter.ts +7 -0
  132. package/qualityScanner.test.ts +75 -0
  133. package/qualityScanner.ts +17 -1
  134. package/routeSourceValidator.test.ts +11 -2
  135. package/routeSourceValidator.ts +14 -2
  136. package/serviceLoopCallScanner.test.ts +56 -0
  137. package/serviceLoopCallScanner.ts +122 -0
  138. package/shellOnlyEnv.test.ts +65 -0
  139. package/shellOnlyEnv.ts +54 -0
  140. package/signalMutationIndex.test.ts +15 -0
  141. package/signalMutationIndex.ts +23 -82
  142. package/sourceComments.test.ts +45 -0
  143. package/sourceComments.ts +70 -0
  144. package/sshHostKeys.ts +59 -0
  145. package/storeAccessScanner.test.ts +84 -0
  146. package/storeAccessScanner.ts +71 -0
  147. package/storeSource.ts +160 -0
  148. package/storeWriteScanner.test.ts +292 -0
  149. package/storeWriteScanner.ts +50 -0
  150. package/subspace.test.ts +10 -0
  151. package/subspaceClone.ts +3 -1
  152. package/subspaceConfig.ts +6 -0
  153. package/testOnlyExportScanner.test.ts +83 -0
  154. package/testOnlyExportScanner.ts +124 -0
  155. package/transforms/barrelImportsPlugin.ts +10 -4
  156. package/transforms/mcpAppPageTrimmer.test.ts +8 -2
  157. package/transforms/mcpAppPageTrimmer.ts +5 -20
  158. package/transforms/pureLazyMarker.ts +1 -1
  159. package/typeChecker.ts +32 -0
  160. package/typecheck/projectTypecheck.test.ts +18 -0
  161. package/typecheck/projectTypecheck.ts +29 -5
  162. package/typecheck/typecheck.proc.ts +6 -0
  163. package/typecheck/typecheckFailure.ts +13 -0
  164. package/types.ts +1 -0
  165. package/workflow/artifacts.ts +16 -2
  166. package/workflow/executor.ts +22 -7
  167. package/workflow/guardCatalog.test.ts +59 -0
  168. package/workflow/guardCatalog.ts +108 -0
  169. package/workflow/index.ts +4 -0
  170. package/workflow/interactionScaffold.ts +122 -0
  171. package/workflow/mutationScaffold.ts +533 -0
  172. package/workflow/mutationSource.test.ts +108 -0
  173. package/workflow/mutationSource.ts +219 -0
  174. package/workflow/plan.ts +82 -0
  175. package/workflow/render.ts +19 -4
  176. package/workflow/source.test.ts +185 -0
  177. package/workflow/source.ts +151 -24
  178. package/workflow/sourceAst.ts +10 -48
  179. package/workflow/types.ts +18 -1
  180. package/workspaceLayout.ts +2 -0
  181. package/writePath.test.ts +109 -0
  182. package/writePath.ts +219 -0
  183. package/writePlacementScanner.test.ts +77 -0
  184. package/writePlacementScanner.ts +50 -0
  185. package/writingActionIndex.test.ts +99 -0
  186. package/writingActionIndex.ts +63 -0
  187. package/codeAgent/feedback/PreviewView.ts +0 -95
  188. /package/{codeAgent/feedback → preview}/previewChrome.test.ts +0 -0
@@ -0,0 +1,220 @@
1
+ import { JsonFormatter } from "./jsonFormatter";
2
+ import type { DebtRuleId, MaintainabilityViolation } from "./maintainabilityContract";
3
+
4
+ export interface BaselineEntryGrowth {
5
+ rule: DebtRuleId;
6
+ key: string;
7
+ count: number;
8
+ committed?: number;
9
+ }
10
+
11
+ export interface BaselineEntry {
12
+ key: string;
13
+ count: number;
14
+ reason?: string;
15
+ }
16
+
17
+ type Counts = ReadonlyMap<string, number>;
18
+
19
+ //* One section per rule, each key holding what the gate measured: lines, store accesses, writes or calls. A count
20
+ //* only falls, so a recorded entry cannot take on more of the debt it records.
21
+ export class MaintainabilityBaseline {
22
+ static readonly fileName = "akan.baseline.json";
23
+ static readonly reasonMinChars = 10;
24
+ static readonly debtRules = [
25
+ "akan.size.file",
26
+ "akan.size.component",
27
+ "akan.component.store-access",
28
+ "akan.store.multi-write",
29
+ "akan.store.root-write-chain",
30
+ "akan.component.write-chain",
31
+ "akan.write.placement",
32
+ "akan.service.call-in-loop",
33
+ ] as const satisfies readonly DebtRuleId[];
34
+ static readonly reasonedRules: ReadonlySet<DebtRuleId> = new Set([
35
+ "akan.size.file",
36
+ "akan.size.component",
37
+ "akan.component.store-access",
38
+ "akan.service.call-in-loop",
39
+ ]);
40
+
41
+ static countOf(violation: MaintainabilityViolation) {
42
+ if (violation.kind === "size") return violation.lineCount;
43
+ if (violation.kind === "storeAccess") return violation.accessCount;
44
+ if (violation.kind === "multiWrite") return violation.writeCount;
45
+ if (violation.kind === "writePlacement") return violation.writes.length;
46
+ return violation.calls.length;
47
+ }
48
+
49
+ static isDebtRule(name: string): name is DebtRuleId {
50
+ return (MaintainabilityBaseline.debtRules as readonly string[]).includes(name);
51
+ }
52
+
53
+ //? `lenient` reads another commit's copy, whose sections an older or newer CLI may have named differently.
54
+ static parse(
55
+ text: string,
56
+ source = MaintainabilityBaseline.fileName,
57
+ { lenient = false }: { lenient?: boolean } = {},
58
+ ) {
59
+ let json: unknown;
60
+ try {
61
+ json = JSON.parse(text);
62
+ } catch {
63
+ throw new Error(`${source} is not valid JSON.`);
64
+ }
65
+ if (typeof json !== "object" || json === null || Array.isArray(json))
66
+ throw new Error(`${source} holds an object keyed by rule id.`);
67
+ const sections = new Map<DebtRuleId, Counts>();
68
+ let reasons = new Map<string, string>();
69
+ for (const [name, value] of Object.entries(json)) {
70
+ if (name === "reasons") {
71
+ const entries = MaintainabilityBaseline.#reasonsOf(value);
72
+ if (!entries) throw new Error(`${source}: "reasons" maps an entry's key to a sentence.`);
73
+ reasons = new Map(entries);
74
+ continue;
75
+ }
76
+ if (!MaintainabilityBaseline.isDebtRule(name)) {
77
+ if (lenient) continue;
78
+ throw new Error(
79
+ `${source}: "${name}" is not a rule id. Its sections are ${MaintainabilityBaseline.debtRules.join(", ")} and "reasons"; rewrite it with \`akan quality baseline --force\`.`,
80
+ );
81
+ }
82
+ const counts = MaintainabilityBaseline.#countsOf(value);
83
+ if (!counts) throw new Error(`${source}: "${name}" maps each key to a whole-number count.`);
84
+ sections.set(name, new Map(counts));
85
+ }
86
+ return new MaintainabilityBaseline(sections, reasons);
87
+ }
88
+
89
+ static of(violations: MaintainabilityViolation[], reasons: ReadonlyMap<string, string> = new Map()) {
90
+ const sections = new Map<DebtRuleId, Map<string, number>>();
91
+ for (const violation of violations) {
92
+ const counts = sections.get(violation.rule) ?? new Map<string, number>();
93
+ const count = MaintainabilityBaseline.countOf(violation);
94
+ counts.set(violation.key, Math.max(counts.get(violation.key) ?? 0, count));
95
+ sections.set(violation.rule, counts);
96
+ }
97
+ return new MaintainabilityBaseline(sections).#withReasonsOf(reasons);
98
+ }
99
+
100
+ constructor(
101
+ readonly sections: ReadonlyMap<DebtRuleId, Counts> = new Map(),
102
+ readonly reasons: ReadonlyMap<string, string> = new Map(),
103
+ ) {}
104
+
105
+ get isEmpty() {
106
+ return [...this.sections.values()].every((counts) => !counts.size);
107
+ }
108
+
109
+ allows(violation: MaintainabilityViolation) {
110
+ const recorded = this.recordedOf(violation);
111
+ return recorded !== undefined && MaintainabilityBaseline.countOf(violation) <= recorded;
112
+ }
113
+
114
+ recordedOf(violation: MaintainabilityViolation) {
115
+ return this.sections.get(violation.rule)?.get(violation.key);
116
+ }
117
+
118
+ reasonOf(key: string) {
119
+ const reason = this.#holdsReasoned(key) ? this.reasons.get(key) : undefined;
120
+ return reason && reason.replace(/\s/g, "").length >= MaintainabilityBaseline.reasonMinChars ? reason : undefined;
121
+ }
122
+
123
+ entriesByRule() {
124
+ return new Map(
125
+ [...this.sections]
126
+ .filter(([, counts]) => counts.size)
127
+ .map(([rule, counts]): [DebtRuleId, BaselineEntry[]] => [
128
+ rule,
129
+ [...counts].map(([key, count]) => ({
130
+ key,
131
+ count,
132
+ reason: MaintainabilityBaseline.reasonedRules.has(rule) ? this.reasonOf(key) : undefined,
133
+ })),
134
+ ]),
135
+ );
136
+ }
137
+
138
+ tightenedBy(violations: MaintainabilityViolation[]) {
139
+ const current = MaintainabilityBaseline.of(violations);
140
+ const sections = new Map(
141
+ [...this.sections].map(([rule, counts]) => [
142
+ rule,
143
+ MaintainabilityBaseline.#lowered(counts, current.sections.get(rule) ?? new Map()),
144
+ ]),
145
+ );
146
+ return new MaintainabilityBaseline(sections).#withReasonsOf(this.reasons);
147
+ }
148
+
149
+ #withReasonsOf(reasons: ReadonlyMap<string, string>) {
150
+ return new MaintainabilityBaseline(
151
+ this.sections,
152
+ new Map([...reasons].filter(([key]) => this.#holdsReasoned(key))),
153
+ );
154
+ }
155
+
156
+ //* Only a shape entry grows with a reason: a write rule guards correctness, whatever the reason.
157
+ grownFrom(committed: MaintainabilityBaseline): BaselineEntryGrowth[] {
158
+ return [...this.sections].flatMap(([rule, counts]) =>
159
+ MaintainabilityBaseline.#raised(counts, committed.sections.get(rule) ?? new Map())
160
+ .filter(([key]) => !(MaintainabilityBaseline.reasonedRules.has(rule) && this.reasonOf(key)))
161
+ .map(([key, count, recorded]) => ({ rule, key, count, committed: recorded })),
162
+ );
163
+ }
164
+
165
+ stringify() {
166
+ const sections = MaintainabilityBaseline.debtRules.flatMap((rule) => {
167
+ const counts = this.sections.get(rule);
168
+ return counts?.size ? [[rule, MaintainabilityBaseline.#sortedObjectOf(counts)] as const] : [];
169
+ });
170
+ return JsonFormatter.stringify(
171
+ {
172
+ ...Object.fromEntries(sections),
173
+ ...(this.reasons.size ? { reasons: MaintainabilityBaseline.#sortedObjectOf(this.reasons) } : {}),
174
+ },
175
+ MaintainabilityBaseline.fileName,
176
+ );
177
+ }
178
+
179
+ #holdsReasoned(key: string) {
180
+ return [...this.sections].some(
181
+ ([rule, counts]) => MaintainabilityBaseline.reasonedRules.has(rule) && counts.has(key),
182
+ );
183
+ }
184
+
185
+ static #countsOf(value: unknown) {
186
+ if (typeof value !== "object" || value === null || Array.isArray(value)) return null;
187
+ const entries = Object.entries(value);
188
+ return entries.every(([, count]) => Number.isInteger(count)) ? (entries as [string, number][]) : null;
189
+ }
190
+
191
+ static #reasonsOf(value: unknown) {
192
+ if (typeof value !== "object" || value === null || Array.isArray(value)) return null;
193
+ const entries = Object.entries(value);
194
+ return entries.every(([, reason]) => typeof reason === "string") ? (entries as [string, string][]) : null;
195
+ }
196
+
197
+ static #lowered(recorded: Counts, current: Counts) {
198
+ return new Map(
199
+ [...recorded].flatMap(([key, count]) => {
200
+ const measured = current.get(key);
201
+ return measured === undefined ? [] : [[key, Math.min(count, measured)] as const];
202
+ }),
203
+ );
204
+ }
205
+
206
+ static #raised(working: Counts, committed: Counts) {
207
+ return [...working].flatMap(([key, count]) => {
208
+ const recorded = committed.get(key);
209
+ return recorded !== undefined && count <= recorded ? [] : [[key, count, recorded] as const];
210
+ });
211
+ }
212
+
213
+ static #sortedObjectOf<T>(values: ReadonlyMap<string, T>) {
214
+ return Object.fromEntries([...values].sort(([a], [b]) => MaintainabilityBaseline.#byKey(a, b)));
215
+ }
216
+
217
+ static #byKey(a: string, b: string) {
218
+ return a < b ? -1 : a > b ? 1 : 0;
219
+ }
220
+ }