@cosmicdrift/kumiko-cli 0.413.1 → 0.415.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.
- package/package.json +12 -12
- package/src/changes.json +7 -0
- package/src/commands/api-docs.ts +20 -24
- package/src/commands/check-lint.ts +87 -0
- package/src/commands/check.ts +64 -15
- package/src/index.ts +0 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cosmicdrift/kumiko-cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.415.0",
|
|
4
4
|
"description": "CLI for Kumiko apps. `bunx @cosmicdrift/kumiko-cli new app <name>` scaffolds a runnable workspace; `add feature <name>` mounts a feature; `check` runs boot validation plus the guard suites with the step list derived from kumiko.json; `agent lint`, `project` and `consumer` operate on an existing app; `handbuch build` renders the HTML handbuch.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Marc Frost <marc@cosmicdriftgamestudio.com>",
|
|
@@ -36,19 +36,19 @@
|
|
|
36
36
|
}
|
|
37
37
|
},
|
|
38
38
|
"dependencies": {
|
|
39
|
-
"@cosmicdrift/kumiko-dev-server": "0.
|
|
40
|
-
"@cosmicdrift/kumiko-feature-ast": "0.
|
|
41
|
-
"@cosmicdrift/kumiko-guards": "0.
|
|
42
|
-
"@cosmicdrift/kumiko-server-runtime": "0.
|
|
43
|
-
"@cosmicdrift/kumiko-testing": "0.
|
|
39
|
+
"@cosmicdrift/kumiko-dev-server": "0.415.0",
|
|
40
|
+
"@cosmicdrift/kumiko-feature-ast": "0.415.0",
|
|
41
|
+
"@cosmicdrift/kumiko-guards": "0.415.0",
|
|
42
|
+
"@cosmicdrift/kumiko-server-runtime": "0.415.0",
|
|
43
|
+
"@cosmicdrift/kumiko-testing": "0.415.0",
|
|
44
44
|
"marked": "^18.1.2",
|
|
45
45
|
"zod": "^4.6.7"
|
|
46
46
|
},
|
|
47
47
|
"peerDependencies": {
|
|
48
48
|
"sharp": "^0.35.5",
|
|
49
|
-
"@cosmicdrift/kumiko-bundled-features": "0.
|
|
50
|
-
"@cosmicdrift/kumiko-framework": "0.
|
|
51
|
-
"@cosmicdrift/kumiko-renderer": "0.
|
|
49
|
+
"@cosmicdrift/kumiko-bundled-features": "0.415.0",
|
|
50
|
+
"@cosmicdrift/kumiko-framework": "0.415.0",
|
|
51
|
+
"@cosmicdrift/kumiko-renderer": "0.415.0"
|
|
52
52
|
},
|
|
53
53
|
"peerDependenciesMeta": {
|
|
54
54
|
"sharp": {
|
|
@@ -65,9 +65,9 @@
|
|
|
65
65
|
}
|
|
66
66
|
},
|
|
67
67
|
"devDependencies": {
|
|
68
|
-
"@cosmicdrift/kumiko-bundled-features": "0.
|
|
69
|
-
"@cosmicdrift/kumiko-framework": "0.
|
|
70
|
-
"@cosmicdrift/kumiko-renderer": "0.
|
|
68
|
+
"@cosmicdrift/kumiko-bundled-features": "0.415.0",
|
|
69
|
+
"@cosmicdrift/kumiko-framework": "0.415.0",
|
|
70
|
+
"@cosmicdrift/kumiko-renderer": "0.415.0",
|
|
71
71
|
"sharp": "^0.35.5"
|
|
72
72
|
},
|
|
73
73
|
"publishConfig": {
|
package/src/changes.json
CHANGED
|
@@ -1,4 +1,11 @@
|
|
|
1
1
|
[
|
|
2
|
+
{
|
|
3
|
+
"version": "0.415.0",
|
|
4
|
+
"type": "breaking",
|
|
5
|
+
"title": "kumiko check fails when biome.json does not extend the Kumiko Biome preset",
|
|
6
|
+
"detail": "The lint step now reads `biome.json` or `biome.jsonc` (comments allowed) before it starts Biome and follows relative `extends` entries. It passes only if the chain reaches `@cosmicdrift/kumiko-framework/biome-rules`, `@cosmicdrift/kumiko-framework/biome` or a relative path to the framework's `presets/biome-rules.json` or `presets/biome-app.json`. Without the preset the SSRF, path-traversal, event-store write and admin-API plugins are missing and the step used to stay green; now it exits 1 and does not start Biome.",
|
|
7
|
+
"migration": "Add `\"extends\": [\"@cosmicdrift/kumiko-framework/biome\"]` to the repo's `biome.json` (`@cosmicdrift/kumiko-framework/biome-rules` for repos that keep their own file layout)."
|
|
8
|
+
},
|
|
2
9
|
{
|
|
3
10
|
"version": "0.413.0",
|
|
4
11
|
"type": "fix",
|
package/src/commands/api-docs.ts
CHANGED
|
@@ -17,7 +17,6 @@ import type { CliCommand, CliCommandContext } from "./types";
|
|
|
17
17
|
const USAGE = "Usage: kumiko api-docs [--out <dir>] [--locales en,de] [--check]";
|
|
18
18
|
const DEFAULT_OUT_DIR = "api-docs";
|
|
19
19
|
const DEFAULT_EXAMPLES_DIR = "api-examples";
|
|
20
|
-
const INDEX_FILE = "index.json";
|
|
21
20
|
const WEBHOOK_EXAMPLES_SUBDIR = "webhooks";
|
|
22
21
|
const WEBHOOK_EXAMPLES_PREFIX = `${WEBHOOK_EXAMPLES_SUBDIR}/`;
|
|
23
22
|
|
|
@@ -91,17 +90,17 @@ function appSource(cwd: string): { name: string; version: string } {
|
|
|
91
90
|
}
|
|
92
91
|
|
|
93
92
|
/** Files the previous run listed in its index.json, so stale pages can go without touching foreign files. */
|
|
94
|
-
function previouslyGenerated(outDir: string): string[] {
|
|
95
|
-
const indexPath = join(outDir,
|
|
93
|
+
function previouslyGenerated(outDir: string, indexFile: string): string[] {
|
|
94
|
+
const indexPath = join(outDir, indexFile);
|
|
96
95
|
if (!existsSync(indexPath)) return [];
|
|
97
96
|
try {
|
|
98
97
|
const parsed = IndexFileSchema.safeParse(readJson(indexPath));
|
|
99
98
|
return parsed.success
|
|
100
|
-
? [
|
|
101
|
-
: [
|
|
99
|
+
? [indexFile, ...parsed.data.pages.map((page) => page.path)]
|
|
100
|
+
: [indexFile];
|
|
102
101
|
} catch {
|
|
103
102
|
// skip: an unreadable index.json is rewritten below, so there is nothing stale to learn from it
|
|
104
|
-
return [
|
|
103
|
+
return [indexFile];
|
|
105
104
|
}
|
|
106
105
|
}
|
|
107
106
|
|
|
@@ -135,8 +134,8 @@ function readExamples<T>(
|
|
|
135
134
|
return examples;
|
|
136
135
|
}
|
|
137
136
|
|
|
138
|
-
function writeOutput(outDir: string, files: ReadonlyMap<string, string
|
|
139
|
-
for (const stale of previouslyGenerated(outDir)) {
|
|
137
|
+
function writeOutput(outDir: string, files: ReadonlyMap<string, string>, indexFile: string): void {
|
|
138
|
+
for (const stale of previouslyGenerated(outDir, indexFile)) {
|
|
140
139
|
const target = resolve(outDir, stale);
|
|
141
140
|
if (
|
|
142
141
|
!files.has(stale) &&
|
|
@@ -156,6 +155,7 @@ function writeOutput(outDir: string, files: ReadonlyMap<string, string>): void {
|
|
|
156
155
|
|
|
157
156
|
type Values = { out?: string; locales?: string; check?: boolean };
|
|
158
157
|
type AgentTools = typeof import("@cosmicdrift/kumiko-bundled-features/agent-tools");
|
|
158
|
+
type ResolvedApiDocsConfig = ReturnType<AgentTools["resolveApiDocsConfig"]>;
|
|
159
159
|
|
|
160
160
|
function errorBlock(ctx: CliCommandContext, message: string): number {
|
|
161
161
|
ctx.out.err("");
|
|
@@ -198,15 +198,14 @@ function report(
|
|
|
198
198
|
for (const line of problemLines) ctx.out.err(` ${line}`);
|
|
199
199
|
return 1;
|
|
200
200
|
}
|
|
201
|
-
writeOutput(target.outDir, files);
|
|
201
|
+
writeOutput(target.outDir, files, tools.INDEX_FILE);
|
|
202
202
|
for (const line of problemLines) ctx.out.log(` warning ${line}`);
|
|
203
203
|
ctx.out.log(` ✓ ${files.size} files written to ${target.outDir}`);
|
|
204
204
|
return 0;
|
|
205
205
|
}
|
|
206
206
|
|
|
207
207
|
type Target = {
|
|
208
|
-
|
|
209
|
-
baseUrl: string;
|
|
208
|
+
resolved: ResolvedApiDocsConfig;
|
|
210
209
|
outDir: string;
|
|
211
210
|
onDisk: Map<string, string>;
|
|
212
211
|
check: boolean;
|
|
@@ -219,9 +218,11 @@ function resolveTarget(
|
|
|
219
218
|
apiDocs: AppApiDocsConfig,
|
|
220
219
|
values: Values,
|
|
221
220
|
): Target | number {
|
|
222
|
-
const
|
|
223
|
-
|
|
224
|
-
|
|
221
|
+
const resolved = tools.resolveApiDocsConfig(apiDocs, {
|
|
222
|
+
appName: appSource(ctx.cwd).name,
|
|
223
|
+
locales: values.locales?.split(",").map((locale) => locale.trim()),
|
|
224
|
+
});
|
|
225
|
+
const badLocales = localeProblem(resolved.locales, tools);
|
|
225
226
|
if (badLocales !== undefined) return usageError(ctx, badLocales);
|
|
226
227
|
if (apiDocs.baseUrl === undefined) {
|
|
227
228
|
return errorBlock(
|
|
@@ -238,13 +239,13 @@ function resolveTarget(
|
|
|
238
239
|
}
|
|
239
240
|
const onDisk = readOutDir(outDir);
|
|
240
241
|
const check = values.check === true;
|
|
241
|
-
if (!check && onDisk.size > 0 && !onDisk.has(INDEX_FILE)) {
|
|
242
|
+
if (!check && onDisk.size > 0 && !onDisk.has(tools.INDEX_FILE)) {
|
|
242
243
|
return errorBlock(
|
|
243
244
|
ctx,
|
|
244
|
-
`${outDir} is not empty and holds no ${INDEX_FILE}, so it was not generated by this command. Pick another --out.`,
|
|
245
|
+
`${outDir} is not empty and holds no ${tools.INDEX_FILE}, so it was not generated by this command. Pick another --out.`,
|
|
245
246
|
);
|
|
246
247
|
}
|
|
247
|
-
return {
|
|
248
|
+
return { resolved, outDir, onDisk, check };
|
|
248
249
|
}
|
|
249
250
|
|
|
250
251
|
async function generate(
|
|
@@ -259,7 +260,7 @@ async function generate(
|
|
|
259
260
|
const apiDocs = config.apiDocs ?? {};
|
|
260
261
|
const target = resolveTarget(ctx, tools, apiDocs, values);
|
|
261
262
|
if (typeof target === "number") return target;
|
|
262
|
-
const {
|
|
263
|
+
const { resolved, outDir, onDisk, check } = target;
|
|
263
264
|
|
|
264
265
|
const examplesDir = resolve(ctx.cwd, apiDocs.examplesDir ?? DEFAULT_EXAMPLES_DIR);
|
|
265
266
|
const examples = readExamples(
|
|
@@ -282,12 +283,7 @@ async function generate(
|
|
|
282
283
|
registry: createRegistry(
|
|
283
284
|
composeFeatures(allAppFeatures(config), { includeBundled: config.includeBundled === true }),
|
|
284
285
|
),
|
|
285
|
-
config:
|
|
286
|
-
...apiDocs,
|
|
287
|
-
locales,
|
|
288
|
-
baseUrl,
|
|
289
|
-
appName: apiDocs.appName ?? appSource(ctx.cwd).name,
|
|
290
|
-
},
|
|
286
|
+
config: resolved,
|
|
291
287
|
source: { framework: frameworkVersion(), app: appSource(ctx.cwd) },
|
|
292
288
|
examples,
|
|
293
289
|
webhookExamples,
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
2
|
+
import { dirname, join, resolve } from "node:path";
|
|
3
|
+
import type { Output } from "../output";
|
|
4
|
+
|
|
5
|
+
const PRESET_PACKAGE_EXTENDS: ReadonlySet<string> = new Set([
|
|
6
|
+
"@cosmicdrift/kumiko-framework/biome-rules",
|
|
7
|
+
"@cosmicdrift/kumiko-framework/biome",
|
|
8
|
+
]);
|
|
9
|
+
const PRESET_FILE_SUFFIXES = ["/presets/biome-rules.json", "/presets/biome-app.json"];
|
|
10
|
+
const BIOME_CONFIG_NAMES = ["biome.json", "biome.jsonc"];
|
|
11
|
+
const PRESET_FIX = '"extends": ["@cosmicdrift/kumiko-framework/biome"]';
|
|
12
|
+
|
|
13
|
+
function readExtendsList(configPath: string): string[] {
|
|
14
|
+
let parsed: unknown;
|
|
15
|
+
try {
|
|
16
|
+
parsed = Bun.JSONC.parse(readFileSync(configPath, "utf-8"));
|
|
17
|
+
} catch {
|
|
18
|
+
return [];
|
|
19
|
+
}
|
|
20
|
+
const list =
|
|
21
|
+
typeof parsed === "object" && parsed !== null && "extends" in parsed
|
|
22
|
+
? parsed.extends
|
|
23
|
+
: undefined;
|
|
24
|
+
const entries = Array.isArray(list) ? list : [list];
|
|
25
|
+
return entries.filter((entry): entry is string => typeof entry === "string");
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function extendsFrameworkPreset(configPath: string, visited: Set<string>): boolean {
|
|
29
|
+
if (visited.has(configPath)) return false;
|
|
30
|
+
visited.add(configPath);
|
|
31
|
+
return readExtendsList(configPath).some((entry) => {
|
|
32
|
+
if (PRESET_PACKAGE_EXTENDS.has(entry)) return true;
|
|
33
|
+
if (!entry.startsWith(".")) return false;
|
|
34
|
+
const target = resolve(dirname(configPath), entry);
|
|
35
|
+
if (PRESET_FILE_SUFFIXES.some((suffix) => target.endsWith(suffix))) return true;
|
|
36
|
+
return existsSync(target) && extendsFrameworkPreset(target, visited);
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function repoExtendsFrameworkPreset(repoRoot: string): boolean {
|
|
41
|
+
const configPath = BIOME_CONFIG_NAMES.map((name) => join(repoRoot, name)).find((candidate) =>
|
|
42
|
+
existsSync(candidate),
|
|
43
|
+
);
|
|
44
|
+
return configPath !== undefined && extendsFrameworkPreset(configPath, new Set());
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function resolveBiomeBin(repoRoot: string): string | undefined {
|
|
48
|
+
try {
|
|
49
|
+
return join(dirname(Bun.resolveSync("@biomejs/biome/package.json", repoRoot)), "bin", "biome");
|
|
50
|
+
} catch {
|
|
51
|
+
return undefined;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/** Runs the repo's own Biome so builtin rules and the biome-rules plugins count towards `kumiko check`. */
|
|
56
|
+
export async function runLintCheckStep(out: Output, repoRoot: string): Promise<number> {
|
|
57
|
+
const biomeBin = resolveBiomeBin(repoRoot);
|
|
58
|
+
if (biomeBin === undefined) {
|
|
59
|
+
out.err(
|
|
60
|
+
" ✗ @biomejs/biome is not installed in this repo — kumiko check needs it for the lint step.",
|
|
61
|
+
);
|
|
62
|
+
out.err(" Fix: `bun add -d @biomejs/biome`");
|
|
63
|
+
return 1;
|
|
64
|
+
}
|
|
65
|
+
// Without the preset the SSRF, path-traversal, event-store-write and admin-API plugins are missing and the lint step stays green.
|
|
66
|
+
if (!repoExtendsFrameworkPreset(repoRoot)) {
|
|
67
|
+
out.err(
|
|
68
|
+
" ✗ biome.json does not extend the Kumiko Biome preset — the security plugins (SSRF, path traversal, event-store writes, admin API) would not run.",
|
|
69
|
+
);
|
|
70
|
+
out.err(` Fix: add ${PRESET_FIX} to biome.json`);
|
|
71
|
+
return 1;
|
|
72
|
+
}
|
|
73
|
+
const proc = Bun.spawn(["bun", biomeBin, "lint", "."], {
|
|
74
|
+
cwd: repoRoot,
|
|
75
|
+
stdout: "pipe",
|
|
76
|
+
stderr: "pipe",
|
|
77
|
+
});
|
|
78
|
+
const [stdout, stderr, code] = await Promise.all([
|
|
79
|
+
new Response(proc.stdout).text(),
|
|
80
|
+
new Response(proc.stderr).text(),
|
|
81
|
+
proc.exited,
|
|
82
|
+
]);
|
|
83
|
+
for (const line of `${stdout}${stderr}`.split("\n")) {
|
|
84
|
+
if (line.trim().length > 0) out.log(` ${line}`);
|
|
85
|
+
}
|
|
86
|
+
return code === 0 ? 0 : 1;
|
|
87
|
+
}
|
package/src/commands/check.ts
CHANGED
|
@@ -1,17 +1,30 @@
|
|
|
1
1
|
import { existsSync } from "node:fs";
|
|
2
2
|
import { join, resolve } from "node:path";
|
|
3
3
|
import { REPO_MANIFEST_FILE, type RepoManifest } from "@cosmicdrift/kumiko-guards/repo-manifest";
|
|
4
|
+
import type { Output } from "../output";
|
|
4
5
|
import type { CliCommand, CliCommandContext } from "./types";
|
|
5
6
|
|
|
6
7
|
const APP_SCHEMA_FILE = "kumiko/schema.ts";
|
|
8
|
+
const APP_CONFIG_FILE = "kumiko.config.ts";
|
|
9
|
+
const BIOME_CONFIG_FILES = ["biome.json", "biome.jsonc"] as const;
|
|
7
10
|
|
|
8
|
-
export type CheckStepId =
|
|
11
|
+
export type CheckStepId =
|
|
12
|
+
| "boot"
|
|
13
|
+
| "guards"
|
|
14
|
+
| "ui"
|
|
15
|
+
| "checks"
|
|
16
|
+
| "lint"
|
|
17
|
+
| "agent"
|
|
18
|
+
| "managed"
|
|
19
|
+
| "scripts";
|
|
9
20
|
|
|
10
21
|
const CHECK_STEP_IDS: readonly CheckStepId[] = [
|
|
11
22
|
"boot",
|
|
12
23
|
"guards",
|
|
13
24
|
"ui",
|
|
14
25
|
"checks",
|
|
26
|
+
"lint",
|
|
27
|
+
"agent",
|
|
15
28
|
"managed",
|
|
16
29
|
"scripts",
|
|
17
30
|
];
|
|
@@ -29,6 +42,10 @@ export type RepoFacts = {
|
|
|
29
42
|
/** A `kind: "app"` repo without one is an app by role only (an Astro site, a
|
|
30
43
|
* docs repo) — there is no composed feature set for validateBoot to check. */
|
|
31
44
|
readonly hasAppSchema: boolean;
|
|
45
|
+
/** `kumiko agent lint` loads kumiko.config.ts and fails without it. */
|
|
46
|
+
readonly hasAppConfig: boolean;
|
|
47
|
+
/** The repo lints with Biome (builtin rules plus the biome-rules plugins); without a config there is nothing to run. */
|
|
48
|
+
readonly hasBiomeConfig: boolean;
|
|
32
49
|
readonly manifestSource: "file" | "derived";
|
|
33
50
|
};
|
|
34
51
|
|
|
@@ -82,23 +99,42 @@ export function resolveCheckSteps(manifest: RepoManifest, facts: RepoFacts): Che
|
|
|
82
99
|
label: "Repo checks",
|
|
83
100
|
why: `kind "${manifest.kind}" — testGlobs ${manifest.testGlobs.join(", ")}`,
|
|
84
101
|
});
|
|
85
|
-
|
|
86
|
-
steps.push(
|
|
87
|
-
{
|
|
88
|
-
id: "managed",
|
|
89
|
-
label: "Managed files",
|
|
90
|
-
why: 'kind "app" — framework-owned files against the installed template',
|
|
91
|
-
},
|
|
92
|
-
{
|
|
93
|
-
id: "scripts",
|
|
94
|
-
label: "Standard scripts",
|
|
95
|
-
why: 'kind "app" — package.json scripts against the script standard',
|
|
96
|
-
},
|
|
97
|
-
);
|
|
98
|
-
}
|
|
102
|
+
steps.push(...resolveLintAndAppSteps(manifest, facts));
|
|
99
103
|
return { kind: "steps", steps, waivers };
|
|
100
104
|
}
|
|
101
105
|
|
|
106
|
+
function resolveLintAndAppSteps(manifest: RepoManifest, facts: RepoFacts): readonly CheckStep[] {
|
|
107
|
+
const steps: CheckStep[] = [];
|
|
108
|
+
if (facts.hasBiomeConfig) {
|
|
109
|
+
steps.push({
|
|
110
|
+
id: "lint",
|
|
111
|
+
label: "Biome lint",
|
|
112
|
+
why: `${BIOME_CONFIG_FILES.join(" or ")} present — biome lint over the repo (builtin rules and the biome-rules plugins that replaced single-file guards)`,
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
if (manifest.kind !== "app") return steps;
|
|
116
|
+
if (facts.hasAppConfig) {
|
|
117
|
+
steps.push({
|
|
118
|
+
id: "agent",
|
|
119
|
+
label: "Agent docs",
|
|
120
|
+
why: `kind "app" with ${APP_CONFIG_FILE} — kumiko agent lint over its composed features`,
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
steps.push(
|
|
124
|
+
{
|
|
125
|
+
id: "managed",
|
|
126
|
+
label: "Managed files",
|
|
127
|
+
why: 'kind "app" — framework-owned files against the installed template',
|
|
128
|
+
},
|
|
129
|
+
{
|
|
130
|
+
id: "scripts",
|
|
131
|
+
label: "Standard scripts",
|
|
132
|
+
why: 'kind "app" — package.json scripts against the script standard',
|
|
133
|
+
},
|
|
134
|
+
);
|
|
135
|
+
return steps;
|
|
136
|
+
}
|
|
137
|
+
|
|
102
138
|
export type GuardsCli = Pick<
|
|
103
139
|
typeof import("@cosmicdrift/kumiko-guards"),
|
|
104
140
|
"cliFlagsError" | "findLocalRepo" | "runGuardsCli" | "runRepoChecksCli" | "runUiGuardsCli"
|
|
@@ -112,6 +148,8 @@ export type SchemaCli = Pick<
|
|
|
112
148
|
export type AppCheckSteps = {
|
|
113
149
|
readonly runManagedCheckStep: typeof import("./check-managed").runManagedCheckStep;
|
|
114
150
|
readonly runScriptsCheckStep: typeof import("./check-scripts").runScriptsCheckStep;
|
|
151
|
+
readonly runLintCheckStep: typeof import("./check-lint").runLintCheckStep;
|
|
152
|
+
readonly runAgentLint: (out: Output, repoRoot: string) => Promise<number>;
|
|
115
153
|
};
|
|
116
154
|
|
|
117
155
|
export type CheckDeps = {
|
|
@@ -127,6 +165,9 @@ const DEFAULT_DEPS: CheckDeps = {
|
|
|
127
165
|
loadAppSteps: async () => ({
|
|
128
166
|
runManagedCheckStep: (await import("./check-managed")).runManagedCheckStep,
|
|
129
167
|
runScriptsCheckStep: (await import("./check-scripts")).runScriptsCheckStep,
|
|
168
|
+
runLintCheckStep: (await import("./check-lint")).runLintCheckStep,
|
|
169
|
+
runAgentLint: async (out, repoRoot) =>
|
|
170
|
+
(await import("./agent")).agentCommand.run({ argv: ["lint"], cwd: repoRoot, out }),
|
|
130
171
|
}),
|
|
131
172
|
};
|
|
132
173
|
|
|
@@ -239,6 +280,8 @@ function findCheckedRepo(ctx: CliCommandContext, guards: GuardsCli): LocalRepo |
|
|
|
239
280
|
function printCheckPlan(ctx: CliCommandContext, repo: LocalRepo): readonly CheckStep[] | undefined {
|
|
240
281
|
const plan = resolveCheckSteps(repo.manifest, {
|
|
241
282
|
hasAppSchema: existsSync(join(repo.absPath, APP_SCHEMA_FILE)),
|
|
283
|
+
hasAppConfig: existsSync(join(repo.absPath, APP_CONFIG_FILE)),
|
|
284
|
+
hasBiomeConfig: BIOME_CONFIG_FILES.some((file) => existsSync(join(repo.absPath, file))),
|
|
242
285
|
manifestSource: repo.manifestSource,
|
|
243
286
|
});
|
|
244
287
|
if (plan.kind === "error") {
|
|
@@ -287,6 +330,10 @@ async function runStep(
|
|
|
287
330
|
// findLocalRepo walks upwards, so ctx.cwd may be a subdirectory without kumiko/schema.ts.
|
|
288
331
|
return await runSchemaCli(["validate"], repo.absPath, ctx.out);
|
|
289
332
|
}
|
|
333
|
+
case "lint":
|
|
334
|
+
return (await deps.loadAppSteps()).runLintCheckStep(ctx.out, repo.absPath);
|
|
335
|
+
case "agent":
|
|
336
|
+
return (await deps.loadAppSteps()).runAgentLint(ctx.out, repo.absPath);
|
|
290
337
|
case "managed":
|
|
291
338
|
return (await deps.loadAppSteps()).runManagedCheckStep(
|
|
292
339
|
ctx.out,
|
|
@@ -319,6 +366,8 @@ export const checkCommand: CliCommand = {
|
|
|
319
366
|
" ui uiRoots declared UI guards (App-Mounting 2.0)",
|
|
320
367
|
' kind "app" must declare uiRoots; uiRoots: [] skips the UI guards on purpose',
|
|
321
368
|
' checks kind != "tooling" repo checks over sourceRoots + testGlobs',
|
|
369
|
+
" lint biome.json present biome lint (builtin rules and the biome-rules GritQL plugins)",
|
|
370
|
+
` agent kind "app" + ${APP_CONFIG_FILE} kumiko agent lint (AI-agent doc gaps in the composed features)`,
|
|
322
371
|
' managed kind "app" framework-owned files against the installed template',
|
|
323
372
|
' scripts kind "app" package.json scripts against the script standard',
|
|
324
373
|
"",
|