@akanjs/devkit 3.0.0-beta.17 → 3.0.0-beta.19
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.
- package/abstractDoc.ts +5 -4
- package/akanApp/akanApp.host.ts +23 -9
- package/akanConfig/akanConfig.test.ts +55 -30
- package/akanConfig/akanConfig.ts +29 -17
- package/akanConfig/types.ts +1 -0
- package/applicationBuildRunner.test.ts +5 -3
- package/applicationBuildRunner.ts +33 -6
- package/artifact/implicitRootLayout.test.ts +6 -11
- package/artifact/implicitRootLayout.ts +0 -15
- package/artifact/routeSeedIndex.test.ts +7 -6
- package/biome.base.json +6 -1
- package/capacitorApp.test.ts +28 -2
- package/capacitorApp.ts +25 -4
- package/cloud/constants.ts +23 -0
- package/cloud/globalConfig.ts +13 -0
- package/codeAgent/agent/AkanCodeServices.ts +38 -12
- package/codeAgent/agent/CodeAgent.ts +94 -30
- package/codeAgent/agent/CodeAgentAsks.ts +3 -1
- package/codeAgent/agent/CodeAgentEventMapper.ts +9 -1
- package/codeAgent/agent/CodeAgentProxy.ts +91 -0
- package/codeAgent/agent/CodeAgentRpcHost.ts +29 -9
- package/codeAgent/agent/CodeAgentRpcListener.ts +117 -0
- package/codeAgent/agent/CodeAgentSuspended.ts +92 -0
- package/codeAgent/agent/akanCodeModel.ts +1 -1
- package/codeAgent/agent/akanCodePaths.ts +23 -3
- package/codeAgent/agent/akanContextFiles.test.ts +2 -1
- package/codeAgent/agent/akanSystemPrompt.ts +5 -0
- package/codeAgent/agent/codeAgentContract.test.ts +17 -0
- package/codeAgent/agent/codeAgentProxy.test.ts +74 -0
- package/codeAgent/agent/codeAgentRpcListener.test.ts +113 -0
- package/codeAgent/agent/codeAgentSuspended.test.ts +47 -0
- package/codeAgent/skills/akan-code/SKILL.md +122 -0
- package/codeAgent/skills/akan-module/SKILL.md +2 -2
- package/codeAgent/skills/akan-store/SKILL.md +6 -2
- package/codeAgent/tools/AkanCodePlugins.ts +19 -3
- package/codeAgent/tools/AskToolPack.ts +111 -0
- package/codeAgent/tools/McpClient.ts +51 -5
- package/codeAgent/tools/McpOAuth.ts +268 -0
- package/codeAgent/tools/McpOAuthLoopback.ts +77 -0
- package/codeAgent/tools/McpServerConfig.ts +69 -34
- package/codeAgent/tools/McpSignIn.ts +161 -0
- package/codeAgent/tools/McpTokenStore.ts +92 -0
- package/codeAgent/tools/McpToolPack.ts +16 -3
- package/codeAgent/tools/askToolPack.test.ts +119 -0
- package/codeAgent/tools/mcpOAuth.test.ts +324 -0
- package/codeAgent/tools/mcpServerConfig.test.ts +106 -12
- package/commandDecorators/command.ts +2 -0
- package/commandDecorators/commandDecorators.test.ts +1 -1
- package/commandDecorators/helpFormatter.ts +2 -1
- package/dependencyScanner.ts +4 -2
- package/devkitUtils.test.ts +1 -1
- package/executors.linkFallback.test.ts +103 -0
- package/executors.test.ts +18 -46
- package/executors.ts +31 -8
- package/frontendBuild/autoImportSync.test.ts +13 -0
- package/frontendBuild/autoImportSync.ts +19 -1
- package/frontendBuild/buildRouteClient.test.ts +52 -1
- package/frontendBuild/cssImportResolver.ts +1 -1
- package/frontendBuild/fontOptimizer.test.ts +1 -1
- package/frontendBuild/frontendBuild.test.ts +19 -17
- package/frontendBuild/hmrWatcher.test.ts +1 -1
- package/frontendBuild/routeClientBuilder.ts +27 -10
- package/frontendBuild/sourceMtimeIndex.test.ts +6 -5
- package/getDirname.test.ts +4 -2
- package/incrementalBuilder/devWatchBatch.test.ts +7 -6
- package/incrementalBuilder/incrementalBuilder.proc.ts +1 -0
- package/index.ts +0 -1
- package/integration/devStabilityHarness.test.ts +7 -2
- package/lint/gritRules.test.ts +2 -1
- package/lint/no-deep-internal-import.grit +2 -2
- package/lint/no-redeclare-predefined-endpoint.grit +2 -1
- package/linter.ts +4 -1
- package/package.json +2 -2
- package/platformTest/BunTestReport.test.ts +60 -0
- package/platformTest/BunTestReport.ts +65 -0
- package/platformTest/IdleWatchedProcess.test.ts +64 -0
- package/platformTest/IdleWatchedProcess.ts +77 -0
- package/platformTest/LinuxDockerTarget.ts +120 -0
- package/platformTest/PlatformTestRun.test.ts +68 -0
- package/platformTest/PlatformTestRun.ts +141 -0
- package/platformTest/PlatformTestTarget.ts +120 -0
- package/platformTest/TestSnapshot.test.ts +80 -0
- package/platformTest/TestSnapshot.ts +107 -0
- package/platformTest/WindowsSshTarget.ts +167 -0
- package/prompter.ts +0 -7
- package/qualityScanner.test.ts +3 -2
- package/qualityScanner.ts +1 -1
- package/routeSourceValidator.test.ts +0 -1
- package/routeSourceValidator.ts +0 -12
- package/scanInfo.ts +5 -2
- package/subspace.test.ts +5 -2
- package/subspace.ts +19 -8
- package/workflow/executor.ts +52 -0
- package/workflow/rolloutGate.test.ts +1 -1
- package/workflow/source.ts +19 -13
- package/guideline.ts +0 -19
package/routeSourceValidator.ts
CHANGED
|
@@ -86,18 +86,6 @@ export class RouteSourceValidator {
|
|
|
86
86
|
if (exported.has("head") && exported.has("generateHead")) {
|
|
87
87
|
throw new Error(`[route-convention] head and generateHead cannot both be exported in ${filePath}`);
|
|
88
88
|
}
|
|
89
|
-
if (
|
|
90
|
-
!options.rootLayout &&
|
|
91
|
-
(exported.has("head") || exported.has("generateHead")) &&
|
|
92
|
-
(exported.has("metadata") || exported.has("generateMetadata"))
|
|
93
|
-
) {
|
|
94
|
-
throw new Error(
|
|
95
|
-
`[route-convention] head/generateHead and metadata/generateMetadata cannot both be exported in ${filePath}`,
|
|
96
|
-
);
|
|
97
|
-
}
|
|
98
|
-
if (exported.has("metadata") && exported.has("generateMetadata")) {
|
|
99
|
-
throw new Error(`[route-convention] metadata and generateMetadata cannot both be exported in ${filePath}`);
|
|
100
|
-
}
|
|
101
89
|
const chain = RouteSourceValidator.#readChain(sourceFile, filePath);
|
|
102
90
|
if (!chain) return { devOnly: RouteSourceValidator.#readDevOnly(sourceFile, filePath) };
|
|
103
91
|
const named = [...exported].filter((name) => name !== "default");
|
package/scanInfo.ts
CHANGED
|
@@ -524,16 +524,19 @@ export class PkgInfo {
|
|
|
524
524
|
const [tsconfig, rootPackageJson] = await Promise.all([exec.getTsConfig(), exec.workspace.getPackageJson()]);
|
|
525
525
|
const scanner = await createDependencyScanner(exec);
|
|
526
526
|
const npmSet = new Set(Object.keys({ ...rootPackageJson.dependencies, ...rootPackageJson.devDependencies }));
|
|
527
|
+
const workspacePathOf = (resolve: string) => resolve.replace(/^\.\//, "");
|
|
527
528
|
const pkgPathSet = new Set(
|
|
528
529
|
Object.keys(tsconfig.compilerOptions.paths ?? {})
|
|
529
|
-
.filter((path) =>
|
|
530
|
+
.filter((path) =>
|
|
531
|
+
tsconfig.compilerOptions.paths?.[path]?.some((resolve) => workspacePathOf(resolve).startsWith("pkgs/")),
|
|
532
|
+
)
|
|
530
533
|
.map((path) => path.replace("/*", "")),
|
|
531
534
|
);
|
|
532
535
|
const [npmDepSet, pkgPathDepSet] = await scanner.getImportSets([npmSet, pkgPathSet]);
|
|
533
536
|
const pkgDeps = [...pkgPathDepSet]
|
|
534
537
|
.map((path) => {
|
|
535
538
|
const pathSplitLength = path.split("/").length;
|
|
536
|
-
return (tsconfig.compilerOptions.paths?.[path]?.[0] ?? "*")
|
|
539
|
+
return workspacePathOf(tsconfig.compilerOptions.paths?.[path]?.[0] ?? "*")
|
|
537
540
|
.split("/")
|
|
538
541
|
.slice(1, 1 + pathSplitLength)
|
|
539
542
|
.join("/");
|
package/subspace.test.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { afterEach, beforeEach, describe, expect, test } from "bun:test";
|
|
1
|
+
import { afterEach, beforeEach, describe, expect, setDefaultTimeout, test } from "bun:test";
|
|
2
2
|
import { mkdir, mkdtemp, readdir, rm, writeFile } from "node:fs/promises";
|
|
3
3
|
import os from "node:os";
|
|
4
4
|
import path from "node:path";
|
|
@@ -7,6 +7,9 @@ import { FileSys } from "./fileSys";
|
|
|
7
7
|
import { formatSubspaceDiff, formatSubspacePushResults, formatSubspaceStatuses, Subspace } from "./subspace";
|
|
8
8
|
import { SubspaceConfig } from "./subspaceConfig";
|
|
9
9
|
|
|
10
|
+
//? Each case drives several real git clones and pushes; on a 4-core Windows VM that alone lands at 2-5s.
|
|
11
|
+
setDefaultTimeout(20_000);
|
|
12
|
+
|
|
10
13
|
const tempRoots: string[] = [];
|
|
11
14
|
const originalEnv = { ...process.env };
|
|
12
15
|
|
|
@@ -96,7 +99,7 @@ const makeMirror = async (servedApp: string, privateApp: string, libName: string
|
|
|
96
99
|
await write(path.join(wsRoot, "benchmarks/bench.ts"), "export {};\n");
|
|
97
100
|
await write(
|
|
98
101
|
path.join(wsRoot, "akan.subspace.ts"),
|
|
99
|
-
`export default { pushableBranches: ["develop"], exclude: ["benchmarks"], subspaces: [{ name: "acme", repo:
|
|
102
|
+
`export default { pushableBranches: ["develop"], exclude: ["benchmarks"], subspaces: [{ name: "acme", repo: ${JSON.stringify(bareRoot)}, apps: ["${servedApp}"] }] };\n`,
|
|
100
103
|
);
|
|
101
104
|
|
|
102
105
|
await makeSys(wsRoot, "apps", servedApp, {
|
package/subspace.ts
CHANGED
|
@@ -296,15 +296,25 @@ export class Subspace {
|
|
|
296
296
|
/**
|
|
297
297
|
* `git archive` emits exactly the tracked files at HEAD, which is why the copy needs no exclude list of
|
|
298
298
|
* its own: generated barrels, the `(libs)`/`public/libs` symlinks, env values, secrets and the lockfile
|
|
299
|
-
* are all outside git and cannot enter the archive.
|
|
300
|
-
*
|
|
299
|
+
* are all outside git and cannot enter the archive. Spawned without a shell, whose quoting differs between
|
|
300
|
+
* `sh` and `cmd.exe`.
|
|
301
301
|
*/
|
|
302
302
|
async #extract(paths: string[], excludes: string[] = []) {
|
|
303
|
-
|
|
304
|
-
const
|
|
305
|
-
await this.#workspace.
|
|
306
|
-
|
|
307
|
-
|
|
303
|
+
// Relative, because GNU tar reads a drive-letter archive name (`C:\…`) as `host:path` on a remote host.
|
|
304
|
+
const archivePath = path.relative(this.#workspace.workspaceRoot, `${this.#clonePath}.tar`);
|
|
305
|
+
await this.#workspace.spawn("git", ["archive", "-o", archivePath, "HEAD", "--", ...paths]);
|
|
306
|
+
try {
|
|
307
|
+
await this.#workspace.spawn("tar", [
|
|
308
|
+
"-x",
|
|
309
|
+
"-f",
|
|
310
|
+
archivePath,
|
|
311
|
+
"-C",
|
|
312
|
+
this.#clonePath,
|
|
313
|
+
...excludes.map((entry) => `--exclude=${entry}`),
|
|
314
|
+
]);
|
|
315
|
+
} finally {
|
|
316
|
+
await rm(path.join(this.#workspace.workspaceRoot, archivePath), { force: true });
|
|
317
|
+
}
|
|
308
318
|
}
|
|
309
319
|
|
|
310
320
|
/**
|
|
@@ -399,7 +409,8 @@ export class Subspace {
|
|
|
399
409
|
const gitignorePath = path.join(this.#clonePath, ".gitignore");
|
|
400
410
|
if (!(await FileSys.fileExists(gitignorePath))) return;
|
|
401
411
|
const [begin = "", end = ""] = Subspace.secretsMarkers;
|
|
402
|
-
|
|
412
|
+
// `git archive` checks text out as CRLF under Windows Git's default `core.autocrlf=true`.
|
|
413
|
+
const lines = (await FileSys.readText(gitignorePath)).split(/\r?\n/);
|
|
403
414
|
const beginIdx = lines.indexOf(begin);
|
|
404
415
|
const endIdx = lines.indexOf(end);
|
|
405
416
|
const filtered =
|
package/workflow/executor.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { capitalize } from "akanjs/common";
|
|
1
2
|
import ts from "typescript";
|
|
2
3
|
import type { AkanModuleContext } from "../akanContext";
|
|
3
4
|
import type { Sys, Workspace } from "../commandDecorators";
|
|
@@ -396,6 +397,54 @@ const addFieldUiSurfaceInspection = (plan: WorkflowPlan): WorkflowStepResult =>
|
|
|
396
397
|
};
|
|
397
398
|
};
|
|
398
399
|
|
|
400
|
+
const addMutationActionSurfaceInspection = (plan: WorkflowPlan): WorkflowStepResult => {
|
|
401
|
+
const module = workflowStringInput(plan.inputs.module) ?? "<module>";
|
|
402
|
+
const mutation = workflowStringInput(plan.inputs.mutation) ?? "<mutation>";
|
|
403
|
+
const moduleClassName = moduleComponentName(module);
|
|
404
|
+
return {
|
|
405
|
+
recommendations: [
|
|
406
|
+
{
|
|
407
|
+
code: "add-mutation-action-surface-review",
|
|
408
|
+
kind: "manual-action",
|
|
409
|
+
target: `*/${moduleSourcePaths(module).store}`,
|
|
410
|
+
action: `Name the endpoint's guards and add its dictionary .endpoint() entry with a .desc(). After sync, fetch.${mutation} exists; write a store action only for a toast, an optimistic update, or a multi-field write, and put the control that calls it in ${moduleClassName}.Util.tsx.`,
|
|
411
|
+
confidence: "medium",
|
|
412
|
+
message: `Workflow apply does not write store or UI code for ${module}.${mutation}; review whether a screen should call it.`,
|
|
413
|
+
},
|
|
414
|
+
],
|
|
415
|
+
nextActions: [
|
|
416
|
+
{
|
|
417
|
+
command: `akan workflow explain ${plan.workflow}`,
|
|
418
|
+
reason: "Review action surface guidance before manually editing store or UI files.",
|
|
419
|
+
},
|
|
420
|
+
],
|
|
421
|
+
};
|
|
422
|
+
};
|
|
423
|
+
|
|
424
|
+
const addSliceViewSurfaceInspection = (plan: WorkflowPlan): WorkflowStepResult => {
|
|
425
|
+
const module = workflowStringInput(plan.inputs.module) ?? "<module>";
|
|
426
|
+
const slice = workflowStringInput(plan.inputs.slice) ?? "<slice>";
|
|
427
|
+
const moduleClassName = moduleComponentName(module);
|
|
428
|
+
return {
|
|
429
|
+
recommendations: [
|
|
430
|
+
{
|
|
431
|
+
code: "add-slice-view-surface-review",
|
|
432
|
+
kind: "manual-action",
|
|
433
|
+
target: `*/${moduleSourcePaths(module).zone}`,
|
|
434
|
+
action: `Fill in the service query stub and add the dictionary .slice() entry. After sync, load fetch.init${moduleClassName}${capitalize(slice)}() in the page and pass the result to a ${moduleClassName}.Zone as an init prop.`,
|
|
435
|
+
confidence: "medium",
|
|
436
|
+
message: `Workflow apply does not write page or Zone code for ${module}.${slice}; review where the list should render.`,
|
|
437
|
+
},
|
|
438
|
+
],
|
|
439
|
+
nextActions: [
|
|
440
|
+
{
|
|
441
|
+
command: `akan workflow explain ${plan.workflow}`,
|
|
442
|
+
reason: "Review view surface guidance before manually editing Zone or page files.",
|
|
443
|
+
},
|
|
444
|
+
],
|
|
445
|
+
};
|
|
446
|
+
};
|
|
447
|
+
|
|
399
448
|
export const createWorkflowStepRegistry = ({
|
|
400
449
|
workspace,
|
|
401
450
|
createModule,
|
|
@@ -494,6 +543,8 @@ export const createWorkflowStepRegistry = ({
|
|
|
494
543
|
}),
|
|
495
544
|
),
|
|
496
545
|
[workflowStepKey("add-mutation", "update-signal")]: inspect,
|
|
546
|
+
[workflowStepKey("add-mutation", "update-action-surfaces")]: async (_step, plan) =>
|
|
547
|
+
addMutationActionSurfaceInspection(plan),
|
|
497
548
|
[workflowStepKey("add-slice", "update-service-query")]: async (_step, plan) =>
|
|
498
549
|
primitiveReportToWorkflowStepResult(
|
|
499
550
|
await addSlice({
|
|
@@ -503,6 +554,7 @@ export const createWorkflowStepRegistry = ({
|
|
|
503
554
|
}),
|
|
504
555
|
),
|
|
505
556
|
[workflowStepKey("add-slice", "update-signal-slice")]: inspect,
|
|
557
|
+
[workflowStepKey("add-slice", "connect-view-surfaces")]: async (_step, plan) => addSliceViewSurfaceInspection(plan),
|
|
506
558
|
};
|
|
507
559
|
};
|
|
508
560
|
|
|
@@ -24,7 +24,7 @@ const workspacePackageManifestPaths = async () => {
|
|
|
24
24
|
for (const workspace of workspaces) {
|
|
25
25
|
const glob = new Bun.Glob(`${workspace}/package.json`);
|
|
26
26
|
for await (const relativePath of glob.scan({ cwd: workspaceRoot, onlyFiles: true })) {
|
|
27
|
-
paths.add(relativePath);
|
|
27
|
+
paths.add(relativePath.split(path.sep).join("/"));
|
|
28
28
|
}
|
|
29
29
|
}
|
|
30
30
|
return [...paths].sort();
|
package/workflow/source.ts
CHANGED
|
@@ -160,21 +160,27 @@ export const normalizeFieldType = (typeName: string) => {
|
|
|
160
160
|
return normalizedTypes[typeName.toLowerCase()] ?? typeName;
|
|
161
161
|
};
|
|
162
162
|
|
|
163
|
-
export const
|
|
164
|
-
|
|
165
|
-
const
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
const nextNames = [...baseImport.names, typeName].sort();
|
|
163
|
+
export const ensureNamedImport = (content: string, moduleSpecifier: string, name: string) => {
|
|
164
|
+
const source = sourceFileFor("source.ts", content);
|
|
165
|
+
const namedImport = findNamedImport(source, moduleSpecifier);
|
|
166
|
+
if (namedImport) {
|
|
167
|
+
if (namedImport.names.includes(name)) return content;
|
|
168
|
+
const nextNames = [...namedImport.names, name].sort();
|
|
170
169
|
return spliceText(
|
|
171
170
|
content,
|
|
172
|
-
|
|
173
|
-
|
|
171
|
+
namedImport.namedBindingsStart,
|
|
172
|
+
namedImport.namedBindingsEnd,
|
|
174
173
|
`{ ${nextNames.join(", ")} }`,
|
|
175
174
|
);
|
|
176
175
|
}
|
|
177
|
-
return `import { ${
|
|
176
|
+
return `import { ${name} } from "${moduleSpecifier}";\n${content}`;
|
|
177
|
+
};
|
|
178
|
+
|
|
179
|
+
export const ensureBaseImport = (content: string, name: string) => ensureNamedImport(content, "akanjs/base", name);
|
|
180
|
+
|
|
181
|
+
export const ensureBaseTypeImport = (content: string, typeName: string) => {
|
|
182
|
+
if (typeName !== "Int" && typeName !== "Float") return content;
|
|
183
|
+
return ensureBaseImport(content, typeName);
|
|
178
184
|
};
|
|
179
185
|
|
|
180
186
|
export type FieldDefaultValue = string | number | boolean | null;
|
|
@@ -200,12 +206,12 @@ const booleanDefault = (rawDefault: FieldDefaultValue): string | null => {
|
|
|
200
206
|
};
|
|
201
207
|
|
|
202
208
|
const dateDefault = (rawDefault: FieldDefaultValue): string | null => {
|
|
203
|
-
if (typeof rawDefault === "number" && Number.isFinite(rawDefault)) return `
|
|
209
|
+
if (typeof rawDefault === "number" && Number.isFinite(rawDefault)) return `() => dayjs(${rawDefault})`;
|
|
204
210
|
if (typeof rawDefault !== "string") return null;
|
|
205
211
|
const trimmed = rawDefault.trim();
|
|
206
212
|
if (!trimmed) return null;
|
|
207
|
-
if (trimmed === "now") return "
|
|
208
|
-
if (!Number.isNaN(Date.parse(trimmed))) return `
|
|
213
|
+
if (trimmed === "now") return "() => dayjs()";
|
|
214
|
+
if (!Number.isNaN(Date.parse(trimmed))) return `() => dayjs(${JSON.stringify(trimmed)})`;
|
|
209
215
|
return null;
|
|
210
216
|
};
|
|
211
217
|
|
package/guideline.ts
DELETED
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
export interface GuideScan {
|
|
2
|
-
type: "example" | "source" | "usage" | (string & {});
|
|
3
|
-
description: string;
|
|
4
|
-
path: string;
|
|
5
|
-
filterText?: string;
|
|
6
|
-
sample?: number;
|
|
7
|
-
}
|
|
8
|
-
export interface GuideUpdate {
|
|
9
|
-
filePath: string;
|
|
10
|
-
contents: string[];
|
|
11
|
-
rules: string[];
|
|
12
|
-
}
|
|
13
|
-
export interface GuideGenerateJson {
|
|
14
|
-
title: string;
|
|
15
|
-
description: string;
|
|
16
|
-
scans: GuideScan[];
|
|
17
|
-
update: GuideUpdate;
|
|
18
|
-
page?: string;
|
|
19
|
-
}
|