@askrjs/cli 0.0.10 → 0.0.12
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/README.md +50 -0
- package/dist/add.d.ts +1 -10
- package/dist/add.js +1 -63
- package/dist/analyze.d.ts +88 -0
- package/dist/analyze.js +97 -0
- package/dist/cli.js +16 -0
- package/dist/create.js +1 -1
- package/dist/{discovery-BX-lnFRK.js → discovery-DUDrZCIC.js} +11 -2
- package/dist/file-changes-BAFLhEHZ.js +66 -0
- package/dist/file-changes-cmFN-rF8.d.ts +11 -0
- package/dist/guardrails-GPM9PJqO.js +121 -0
- package/dist/{planner-BDfYDKnI.js → planner-BEfWRd0x.js} +3 -3
- package/dist/runner-BRjjPhZY.js +1495 -0
- package/dist/runner-Ca43qqzG.js +289 -0
- package/dist/skills/askr-ssr-ssg/SKILL.md +2 -0
- package/dist/{skills-C2KzfTl9.js → skills-CSGdAZHN.js} +50 -1
- package/dist/skills.d.ts +12 -1
- package/dist/skills.js +2 -2
- package/dist/ssg.d.ts +1 -0
- package/dist/ssg.js +5 -4
- package/dist/templates/full-stack/AGENTS.md +6 -0
- package/dist/templates/full-stack/package.json +2 -1
- package/dist/templates/spa/AGENTS.md +7 -1
- package/dist/templates/spa/README.md +1 -1
- package/dist/templates/spa/package.json +2 -1
- package/dist/templates/spa/src/main.tsx +1 -1
- package/dist/templates/spa/src/pages/app/_layout.tsx +11 -8
- package/dist/templates/spa/src/pages/app/admin-home.tsx +97 -90
- package/dist/templates/spa/src/pages/app/agent-runs.tsx +33 -26
- package/dist/templates/spa/src/pages/public/home.tsx +12 -9
- package/dist/templates/ssg/AGENTS.md +6 -0
- package/dist/templates/ssg/package.json +2 -1
- package/dist/templates/ssg/src/components/site-shell.tsx +4 -3
- package/dist/templates/ssg/src/pages/about.tsx +14 -11
- package/dist/templates/ssg/src/pages/content.tsx +10 -7
- package/dist/templates/ssg/src/pages/example.tsx +0 -2
- package/dist/templates/ssg/src/pages/home.tsx +6 -3
- package/dist/templates/ssr/AGENTS.md +6 -0
- package/dist/templates/ssr/package.json +2 -1
- package/dist/templates/startkit/AGENTS.md +3 -0
- package/dist/templates/startkit/package.json +2 -1
- package/dist/templates/startkit/src/components/app-sidebar.tsx +23 -18
- package/dist/templates/startkit/src/components/data-table.tsx +8 -6
- package/dist/templates/startkit/src/pages/workspace/dashboard.tsx +16 -13
- package/dist/templates/startkit/src/routes/auth.ts +1 -2
- package/dist/templates/startkit/src/routes/public.ts +1 -2
- package/dist/templates/startkit/src/routes/workspace/accounts.ts +1 -2
- package/dist/templates/startkit/src/routes/workspace/index.ts +2 -3
- package/dist/update.js +1 -1
- package/package.json +7 -6
|
@@ -0,0 +1,289 @@
|
|
|
1
|
+
import { t as inspectBundledSkills } from "./skills-CSGdAZHN.js";
|
|
2
|
+
import { discoverWorkspaceProject } from "./discovery-DUDrZCIC.js";
|
|
3
|
+
import { analysisHasBlockingFindings, runAnalysis } from "./runner-BRjjPhZY.js";
|
|
4
|
+
import fs from "node:fs/promises";
|
|
5
|
+
import path from "node:path";
|
|
6
|
+
import { spawn } from "node:child_process";
|
|
7
|
+
import semver from "semver";
|
|
8
|
+
//#region src/guardrails/runner.ts
|
|
9
|
+
const SUPPORTED_NODE_RANGE = "^20.19.0 || >=22.12.0";
|
|
10
|
+
const VALIDATION_SCRIPTS = [
|
|
11
|
+
"lint",
|
|
12
|
+
"typecheck",
|
|
13
|
+
"test",
|
|
14
|
+
"build"
|
|
15
|
+
];
|
|
16
|
+
function summary(findings) {
|
|
17
|
+
return {
|
|
18
|
+
passed: findings.filter((entry) => entry.status === "pass").length,
|
|
19
|
+
warnings: findings.filter((entry) => entry.status === "warning").length,
|
|
20
|
+
errors: findings.filter((entry) => entry.status === "error").length
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
function dependencyRecord(manifest, section) {
|
|
24
|
+
const value = manifest[section];
|
|
25
|
+
return value && typeof value === "object" && !Array.isArray(value) ? value : {};
|
|
26
|
+
}
|
|
27
|
+
function hasAskrDependency(manifest) {
|
|
28
|
+
return [
|
|
29
|
+
"dependencies",
|
|
30
|
+
"devDependencies",
|
|
31
|
+
"peerDependencies",
|
|
32
|
+
"optionalDependencies"
|
|
33
|
+
].some((section) => "@askrjs/askr" in dependencyRecord(manifest, section));
|
|
34
|
+
}
|
|
35
|
+
async function existingLockfiles(root) {
|
|
36
|
+
return (await Promise.all([
|
|
37
|
+
"package-lock.json",
|
|
38
|
+
"pnpm-lock.yaml",
|
|
39
|
+
"yarn.lock",
|
|
40
|
+
"bun.lock",
|
|
41
|
+
"bun.lockb"
|
|
42
|
+
].map(async (name) => ({
|
|
43
|
+
name,
|
|
44
|
+
exists: Boolean(await fs.stat(path.join(root, name)).catch(() => null))
|
|
45
|
+
})))).filter((entry) => entry.exists).map((entry) => entry.name);
|
|
46
|
+
}
|
|
47
|
+
function declaredPackageManager(manifest) {
|
|
48
|
+
if (typeof manifest.packageManager !== "string") return null;
|
|
49
|
+
return /^(npm|pnpm|yarn|bun)@/.exec(manifest.packageManager)?.[1] ?? null;
|
|
50
|
+
}
|
|
51
|
+
function lockfilePackageManager(lockfile) {
|
|
52
|
+
if (lockfile === "package-lock.json") return "npm";
|
|
53
|
+
if (lockfile === "pnpm-lock.yaml") return "pnpm";
|
|
54
|
+
if (lockfile === "yarn.lock") return "yarn";
|
|
55
|
+
return "bun";
|
|
56
|
+
}
|
|
57
|
+
function packageManagerFinding(lockfiles, manifest) {
|
|
58
|
+
const declared = declaredPackageManager(manifest);
|
|
59
|
+
if (lockfiles.length > 1) return {
|
|
60
|
+
id: "askr/doctor-package-manager",
|
|
61
|
+
status: "error",
|
|
62
|
+
message: `Multiple package-manager lockfiles are present: ${lockfiles.join(", ")}.`,
|
|
63
|
+
remediation: "Keep the lockfile for the project's canonical package manager."
|
|
64
|
+
};
|
|
65
|
+
if (lockfiles.length === 0) return {
|
|
66
|
+
id: "askr/doctor-package-manager",
|
|
67
|
+
status: "warning",
|
|
68
|
+
message: "No package-manager lockfile is present.",
|
|
69
|
+
remediation: "Install dependencies with the project's chosen package manager."
|
|
70
|
+
};
|
|
71
|
+
const detected = lockfilePackageManager(lockfiles[0]);
|
|
72
|
+
if (declared && declared !== detected) return {
|
|
73
|
+
id: "askr/doctor-package-manager",
|
|
74
|
+
status: "error",
|
|
75
|
+
message: `packageManager declares ${declared}, but ${lockfiles[0]} belongs to ${detected}.`,
|
|
76
|
+
remediation: "Align packageManager and the committed lockfile."
|
|
77
|
+
};
|
|
78
|
+
return {
|
|
79
|
+
id: "askr/doctor-package-manager",
|
|
80
|
+
status: "pass",
|
|
81
|
+
message: `Package manager is ${declared ?? detected} (${lockfiles[0]}).`
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
async function skillsFinding(root) {
|
|
85
|
+
const status = await inspectBundledSkills({ cwd: root });
|
|
86
|
+
if (status.installed === 0) return {
|
|
87
|
+
id: "askr/doctor-agent-guidance",
|
|
88
|
+
status: "warning",
|
|
89
|
+
message: "Project-local Askr agent skills are not installed.",
|
|
90
|
+
remediation: "Run `askr skills install`, or `askr skills sync` for an existing project."
|
|
91
|
+
};
|
|
92
|
+
if (!status.current) return {
|
|
93
|
+
id: "askr/doctor-agent-guidance",
|
|
94
|
+
status: "warning",
|
|
95
|
+
message: `Project-local Askr agent skills are stale (${[
|
|
96
|
+
status.missing.length > 0 ? `${status.missing.length} missing` : "",
|
|
97
|
+
status.modified.length > 0 ? `${status.modified.length} modified` : "",
|
|
98
|
+
status.obsolete.length > 0 ? `${status.obsolete.length} obsolete` : ""
|
|
99
|
+
].filter(Boolean).join(", ")}).`,
|
|
100
|
+
remediation: "Run `askr skills sync` to restore the current bundled guidance."
|
|
101
|
+
};
|
|
102
|
+
return {
|
|
103
|
+
id: "askr/doctor-agent-guidance",
|
|
104
|
+
status: "pass",
|
|
105
|
+
message: `${status.bundled} project-local Askr agent skill(s) are current.`
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
function analysisFinding(analysis) {
|
|
109
|
+
if (analysis.summary.errors > 0) return {
|
|
110
|
+
id: "askr/doctor-analysis",
|
|
111
|
+
status: "error",
|
|
112
|
+
message: `Static analysis found ${analysis.summary.errors} error(s) and ${analysis.summary.warnings} warning(s).`,
|
|
113
|
+
remediation: "Run `askr repair`, review remaining findings, then run `askr check`."
|
|
114
|
+
};
|
|
115
|
+
if (analysis.summary.warnings > 0) return {
|
|
116
|
+
id: "askr/doctor-analysis",
|
|
117
|
+
status: "warning",
|
|
118
|
+
message: `Static analysis found ${analysis.summary.warnings} warning(s).`,
|
|
119
|
+
remediation: "Review the reported performance or lifecycle guidance."
|
|
120
|
+
};
|
|
121
|
+
return {
|
|
122
|
+
id: "askr/doctor-analysis",
|
|
123
|
+
status: "pass",
|
|
124
|
+
message: `Static analysis is clean across ${analysis.workspaces.length} workspace(s).`
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
async function runDoctor(options, runtime = {}) {
|
|
128
|
+
const project = await discoverWorkspaceProject({
|
|
129
|
+
cwd: options.cwd,
|
|
130
|
+
workspacePatterns: [...options.workspacePatterns]
|
|
131
|
+
});
|
|
132
|
+
const analysis = await runAnalysis({
|
|
133
|
+
cwd: options.cwd,
|
|
134
|
+
workspacePatterns: [...options.workspacePatterns],
|
|
135
|
+
check: true
|
|
136
|
+
});
|
|
137
|
+
const rootWorkspace = project.workspaces.find((workspace) => workspace.isRoot);
|
|
138
|
+
if (!rootWorkspace) throw new Error("Discovered project is missing its root workspace.");
|
|
139
|
+
const nodeVersion = runtime.nodeVersion ?? process.versions.node;
|
|
140
|
+
const findings = [semver.satisfies(nodeVersion, SUPPORTED_NODE_RANGE) ? {
|
|
141
|
+
id: "askr/doctor-node",
|
|
142
|
+
status: "pass",
|
|
143
|
+
message: `Node ${nodeVersion} satisfies ${SUPPORTED_NODE_RANGE}.`
|
|
144
|
+
} : {
|
|
145
|
+
id: "askr/doctor-node",
|
|
146
|
+
status: "error",
|
|
147
|
+
message: `Node ${nodeVersion} does not satisfy ${SUPPORTED_NODE_RANGE}.`,
|
|
148
|
+
remediation: `Install a Node version matching ${SUPPORTED_NODE_RANGE}.`
|
|
149
|
+
}, packageManagerFinding(await existingLockfiles(project.root), rootWorkspace.manifest)];
|
|
150
|
+
const sourceWorkspaces = new Set(analysis.workspaces.filter((workspace) => workspace.files > 0).map((workspace) => workspace.name));
|
|
151
|
+
const missingFramework = project.selectedWorkspaces.filter((workspace) => sourceWorkspaces.has(workspace.name) && !hasAskrDependency(workspace.manifest));
|
|
152
|
+
findings.push(missingFramework.length === 0 ? {
|
|
153
|
+
id: "askr/doctor-framework-dependency",
|
|
154
|
+
status: "pass",
|
|
155
|
+
message: "Source workspaces declare @askrjs/askr."
|
|
156
|
+
} : {
|
|
157
|
+
id: "askr/doctor-framework-dependency",
|
|
158
|
+
status: "error",
|
|
159
|
+
message: `Source workspace(s) do not declare @askrjs/askr: ${missingFramework.map((workspace) => workspace.name).join(", ")}.`,
|
|
160
|
+
remediation: "Declare @askrjs/askr in each application workspace."
|
|
161
|
+
});
|
|
162
|
+
findings.push(await skillsFinding(project.root), analysisFinding(analysis));
|
|
163
|
+
return {
|
|
164
|
+
schemaVersion: 1,
|
|
165
|
+
command: "doctor",
|
|
166
|
+
root: project.root,
|
|
167
|
+
findings,
|
|
168
|
+
analysis,
|
|
169
|
+
summary: summary(findings)
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
function scriptsFromManifest(manifest) {
|
|
173
|
+
const scripts = manifest.scripts;
|
|
174
|
+
if (!scripts || typeof scripts !== "object" || Array.isArray(scripts)) return {};
|
|
175
|
+
return Object.fromEntries(Object.entries(scripts).filter((entry) => typeof entry[1] === "string"));
|
|
176
|
+
}
|
|
177
|
+
async function defaultRunScript(executable, args, cwd) {
|
|
178
|
+
return new Promise((resolve, reject) => {
|
|
179
|
+
const child = spawn(executable, [...args], {
|
|
180
|
+
cwd,
|
|
181
|
+
env: {
|
|
182
|
+
...process.env,
|
|
183
|
+
NO_COLOR: "1"
|
|
184
|
+
},
|
|
185
|
+
stdio: [
|
|
186
|
+
"ignore",
|
|
187
|
+
"pipe",
|
|
188
|
+
"pipe"
|
|
189
|
+
]
|
|
190
|
+
});
|
|
191
|
+
let stdout = "";
|
|
192
|
+
let stderr = "";
|
|
193
|
+
child.stdout.setEncoding("utf8").on("data", (chunk) => {
|
|
194
|
+
stdout += chunk;
|
|
195
|
+
});
|
|
196
|
+
child.stderr.setEncoding("utf8").on("data", (chunk) => {
|
|
197
|
+
stderr += chunk;
|
|
198
|
+
});
|
|
199
|
+
child.on("error", reject);
|
|
200
|
+
child.on("close", (code) => {
|
|
201
|
+
resolve({
|
|
202
|
+
status: code === 0 ? "passed" : "failed",
|
|
203
|
+
exitCode: code,
|
|
204
|
+
stdout,
|
|
205
|
+
stderr
|
|
206
|
+
});
|
|
207
|
+
});
|
|
208
|
+
});
|
|
209
|
+
}
|
|
210
|
+
function managerCommand(lockfiles, manifest) {
|
|
211
|
+
return {
|
|
212
|
+
executable: declaredPackageManager(manifest) ?? (lockfiles.length === 1 ? lockfilePackageManager(lockfiles[0]) : "npm"),
|
|
213
|
+
args: (script) => ["run", script]
|
|
214
|
+
};
|
|
215
|
+
}
|
|
216
|
+
async function runCheck(options, runtime = {}) {
|
|
217
|
+
const project = await discoverWorkspaceProject({
|
|
218
|
+
cwd: options.cwd,
|
|
219
|
+
workspacePatterns: [...options.workspacePatterns]
|
|
220
|
+
});
|
|
221
|
+
const rootWorkspace = project.workspaces.find((workspace) => workspace.isRoot);
|
|
222
|
+
if (!rootWorkspace) throw new Error("Discovered project is missing its root workspace.");
|
|
223
|
+
const analysis = await runAnalysis({
|
|
224
|
+
cwd: options.cwd,
|
|
225
|
+
workspacePatterns: [...options.workspacePatterns],
|
|
226
|
+
check: true
|
|
227
|
+
});
|
|
228
|
+
const scripts = scriptsFromManifest(rootWorkspace.manifest);
|
|
229
|
+
const selected = VALIDATION_SCRIPTS.filter((name) => scripts[name]);
|
|
230
|
+
const manager = managerCommand(await existingLockfiles(project.root), rootWorkspace.manifest);
|
|
231
|
+
const results = [];
|
|
232
|
+
if (analysisHasBlockingFindings(analysis)) for (const name of selected) results.push({
|
|
233
|
+
name,
|
|
234
|
+
status: "skipped",
|
|
235
|
+
command: `${manager.executable} run ${name}`,
|
|
236
|
+
exitCode: null,
|
|
237
|
+
stdout: "",
|
|
238
|
+
stderr: "",
|
|
239
|
+
reason: "static analysis must pass first"
|
|
240
|
+
});
|
|
241
|
+
else for (const [index, name] of selected.entries()) {
|
|
242
|
+
const args = manager.args(name);
|
|
243
|
+
const result = await (runtime.runScript ?? defaultRunScript)(manager.executable, args, project.root);
|
|
244
|
+
results.push({
|
|
245
|
+
name,
|
|
246
|
+
command: [manager.executable, ...args].join(" "),
|
|
247
|
+
...result
|
|
248
|
+
});
|
|
249
|
+
if (result.status === "failed") {
|
|
250
|
+
for (const skipped of selected.slice(index + 1)) results.push({
|
|
251
|
+
name: skipped,
|
|
252
|
+
status: "skipped",
|
|
253
|
+
command: `${manager.executable} run ${skipped}`,
|
|
254
|
+
exitCode: null,
|
|
255
|
+
stdout: "",
|
|
256
|
+
stderr: "",
|
|
257
|
+
reason: `${name} failed`
|
|
258
|
+
});
|
|
259
|
+
break;
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
const failed = analysisHasBlockingFindings(analysis) || results.some((entry) => entry.status === "failed");
|
|
263
|
+
return {
|
|
264
|
+
schemaVersion: 1,
|
|
265
|
+
command: "check",
|
|
266
|
+
root: project.root,
|
|
267
|
+
analysis,
|
|
268
|
+
scripts: results,
|
|
269
|
+
status: failed ? "failed" : "passed"
|
|
270
|
+
};
|
|
271
|
+
}
|
|
272
|
+
async function runRepair(options) {
|
|
273
|
+
const analysis = await runAnalysis({
|
|
274
|
+
cwd: options.cwd,
|
|
275
|
+
workspacePatterns: [...options.workspacePatterns],
|
|
276
|
+
check: false
|
|
277
|
+
});
|
|
278
|
+
const needsReview = analysisHasBlockingFindings(analysis);
|
|
279
|
+
return {
|
|
280
|
+
schemaVersion: 1,
|
|
281
|
+
command: "repair",
|
|
282
|
+
root: analysis.root,
|
|
283
|
+
analysis,
|
|
284
|
+
status: needsReview ? "needs-review" : "clean",
|
|
285
|
+
nextAction: needsReview ? "Review the remaining semantic diagnostics, then run `askr repair` again." : "Run `askr check` to execute the full validation path."
|
|
286
|
+
};
|
|
287
|
+
}
|
|
288
|
+
//#endregion
|
|
289
|
+
export { runCheck, runDoctor, runRepair };
|
|
@@ -34,6 +34,8 @@ Use this when the app renders outside the browser or produces static output. The
|
|
|
34
34
|
## Copy This Shape
|
|
35
35
|
|
|
36
36
|
```ts
|
|
37
|
+
import { createRouteRegistry } from "@askrjs/askr/router";
|
|
38
|
+
|
|
37
39
|
const registry = createRouteRegistry(() => {
|
|
38
40
|
page("/docs/{slug}", DocsPage, {
|
|
39
41
|
entries: async () => [{ slug: "getting-started" }, { slug: "routing" }],
|
|
@@ -4,6 +4,7 @@ import fs from "node:fs/promises";
|
|
|
4
4
|
import path from "node:path";
|
|
5
5
|
import { constants } from "node:fs";
|
|
6
6
|
import { fileURLToPath } from "node:url";
|
|
7
|
+
import { createHash } from "node:crypto";
|
|
7
8
|
//#region src/bin/skill-review.ts
|
|
8
9
|
const TEXT_FILE_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
9
10
|
".cjs",
|
|
@@ -303,6 +304,7 @@ const REVIEW_PROMPTS = [
|
|
|
303
304
|
re("local Sidebar", String.raw`export\s+(?:default\s+)?function\s+Sidebar\b`)
|
|
304
305
|
]),
|
|
305
306
|
requireAny("Keeps Askr-native route or state primitives in use.", [
|
|
307
|
+
re("registerRoutes", String.raw`\bregisterRoutes\s*\(`),
|
|
306
308
|
re("createRouteRegistry", String.raw`\bcreateRouteRegistry\s*\(`),
|
|
307
309
|
re("state()", String.raw`\bstate\s*\(`),
|
|
308
310
|
re("resource()", String.raw`\bresource\s*\(`)
|
|
@@ -554,6 +556,53 @@ async function listBundledSkills() {
|
|
|
554
556
|
const source = await findBundledSkillsDir();
|
|
555
557
|
return (await fs.readdir(source, { withFileTypes: true })).filter((entry) => entry.isDirectory()).map((entry) => entry.name).sort();
|
|
556
558
|
}
|
|
559
|
+
async function directoryFingerprint(directory) {
|
|
560
|
+
if (!(await fs.stat(directory).catch(() => null))?.isDirectory()) return null;
|
|
561
|
+
const hash = createHash("sha256");
|
|
562
|
+
const visit = async (current, relative) => {
|
|
563
|
+
const entries = await fs.readdir(current, { withFileTypes: true });
|
|
564
|
+
entries.sort((left, right) => left.name.localeCompare(right.name));
|
|
565
|
+
for (const entry of entries) {
|
|
566
|
+
const childRelative = relative ? `${relative}/${entry.name}` : entry.name;
|
|
567
|
+
const child = path.join(current, entry.name);
|
|
568
|
+
hash.update(`${entry.isDirectory() ? "d" : "f"}:${childRelative}\0`);
|
|
569
|
+
if (entry.isDirectory()) await visit(child, childRelative);
|
|
570
|
+
else if (entry.isFile()) hash.update(await fs.readFile(child));
|
|
571
|
+
else if (entry.isSymbolicLink()) hash.update(await fs.readlink(child));
|
|
572
|
+
}
|
|
573
|
+
};
|
|
574
|
+
await visit(directory, "");
|
|
575
|
+
return hash.digest("hex");
|
|
576
|
+
}
|
|
577
|
+
async function inspectBundledSkills(options = {}) {
|
|
578
|
+
const root = path.resolve(options.cwd ?? process.cwd());
|
|
579
|
+
const sourceRoot = await findBundledSkillsDir();
|
|
580
|
+
const targetRoot = path.join(root, PROJECT_SKILLS_DIR);
|
|
581
|
+
const bundledNames = await listBundledSkills();
|
|
582
|
+
const targetEntries = await fs.readdir(targetRoot, { withFileTypes: true }).catch(() => []);
|
|
583
|
+
const targetDirectories = new Set(targetEntries.filter((entry) => entry.isDirectory()).map((entry) => entry.name));
|
|
584
|
+
const missing = [];
|
|
585
|
+
const modified = [];
|
|
586
|
+
for (const name of bundledNames) {
|
|
587
|
+
if (!targetDirectories.has(name)) {
|
|
588
|
+
missing.push(name);
|
|
589
|
+
continue;
|
|
590
|
+
}
|
|
591
|
+
const [sourceFingerprint, targetFingerprint] = await Promise.all([directoryFingerprint(path.join(sourceRoot, name)), directoryFingerprint(path.join(targetRoot, name))]);
|
|
592
|
+
if (sourceFingerprint !== targetFingerprint) modified.push(name);
|
|
593
|
+
}
|
|
594
|
+
const bundledSet = new Set(bundledNames);
|
|
595
|
+
const obsolete = [...targetDirectories].filter((name) => name.startsWith(MANAGED_PREFIX) && !bundledSet.has(name)).sort();
|
|
596
|
+
const installed = bundledNames.filter((name) => targetDirectories.has(name)).length;
|
|
597
|
+
return {
|
|
598
|
+
bundled: bundledNames.length,
|
|
599
|
+
installed,
|
|
600
|
+
missing,
|
|
601
|
+
modified,
|
|
602
|
+
obsolete,
|
|
603
|
+
current: missing.length === 0 && modified.length === 0 && obsolete.length === 0
|
|
604
|
+
};
|
|
605
|
+
}
|
|
557
606
|
async function copyDir(src, dest) {
|
|
558
607
|
const entries = await fs.readdir(src, { withFileTypes: true });
|
|
559
608
|
await fs.mkdir(dest, { recursive: true });
|
|
@@ -741,4 +790,4 @@ async function main() {
|
|
|
741
790
|
}
|
|
742
791
|
if (isDirectExecution(import.meta.url)) main();
|
|
743
792
|
//#endregion
|
|
744
|
-
export {
|
|
793
|
+
export { syncBundledSkills as i, installBundledSkills as n, runSkillsCli as r, inspectBundledSkills as t };
|
package/dist/skills.d.ts
CHANGED
|
@@ -1,5 +1,16 @@
|
|
|
1
1
|
//#region src/bin/skills.d.ts
|
|
2
2
|
type CliIo = Pick<Console, "error" | "log">;
|
|
3
|
+
interface BundledSkillStatus {
|
|
4
|
+
readonly bundled: number;
|
|
5
|
+
readonly installed: number;
|
|
6
|
+
readonly missing: readonly string[];
|
|
7
|
+
readonly modified: readonly string[];
|
|
8
|
+
readonly obsolete: readonly string[];
|
|
9
|
+
readonly current: boolean;
|
|
10
|
+
}
|
|
11
|
+
declare function inspectBundledSkills(options?: {
|
|
12
|
+
cwd?: string;
|
|
13
|
+
}): Promise<BundledSkillStatus>;
|
|
3
14
|
declare function installBundledSkills(options?: {
|
|
4
15
|
cwd?: string;
|
|
5
16
|
force?: boolean;
|
|
@@ -15,4 +26,4 @@ declare function syncBundledSkills(options?: {
|
|
|
15
26
|
}>;
|
|
16
27
|
declare function runSkillsCli(args?: string[], io?: CliIo): Promise<number>;
|
|
17
28
|
//#endregion
|
|
18
|
-
export { installBundledSkills, runSkillsCli, syncBundledSkills };
|
|
29
|
+
export { BundledSkillStatus, inspectBundledSkills, installBundledSkills, runSkillsCli, syncBundledSkills };
|
package/dist/skills.js
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { n as
|
|
3
|
-
export { installBundledSkills, runSkillsCli, syncBundledSkills };
|
|
2
|
+
import { i as syncBundledSkills, n as installBundledSkills, r as runSkillsCli, t as inspectBundledSkills } from "./skills-CSGdAZHN.js";
|
|
3
|
+
export { inspectBundledSkills, installBundledSkills, runSkillsCli, syncBundledSkills };
|
package/dist/ssg.d.ts
CHANGED
package/dist/ssg.js
CHANGED
|
@@ -458,8 +458,9 @@ async function runSsgCli(args = process.argv.slice(2), deps = {}, io = console)
|
|
|
458
458
|
}
|
|
459
459
|
const configModule = imported;
|
|
460
460
|
const candidate = configModule.default ?? configModule.staticConfig ?? configModule;
|
|
461
|
-
|
|
462
|
-
|
|
461
|
+
const hasRoutes = Array.isArray(candidate.routes);
|
|
462
|
+
if (hasRoutes === (candidate.registry !== void 0)) {
|
|
463
|
+
io.error("Error: Config must provide exactly one route source: routes or registry");
|
|
463
464
|
return 1;
|
|
464
465
|
}
|
|
465
466
|
const config = candidate;
|
|
@@ -467,7 +468,7 @@ async function runSsgCli(args = process.argv.slice(2), deps = {}, io = console)
|
|
|
467
468
|
io.error("Error: Config must provide siteUrl to generate sitemap.xml, or set sitemap: false");
|
|
468
469
|
return 1;
|
|
469
470
|
}
|
|
470
|
-
io.log("Generating registered routes...");
|
|
471
|
+
io.log(hasRoutes ? `Generating ${config.routes?.length ?? 0} routes...` : "Generating registered routes...");
|
|
471
472
|
const createStaticGen = typeof resolvedDeps.createStaticGen === "function" ? resolvedDeps.createStaticGen : await loadCreateStaticGen();
|
|
472
473
|
cliStagingDir = await createSiblingStage(resolvedOutputDir, "askr-ssg");
|
|
473
474
|
if (parsed.incremental && !parsed.forceFull && await pathExists(resolvedOutputDir)) await fs$1.cp(resolvedOutputDir, cliStagingDir, {
|
|
@@ -476,7 +477,7 @@ async function runSsgCli(args = process.argv.slice(2), deps = {}, io = console)
|
|
|
476
477
|
});
|
|
477
478
|
const generationOutputDir = cliStagingDir;
|
|
478
479
|
const ssg = createStaticGen({
|
|
479
|
-
registry: config.registry,
|
|
480
|
+
...hasRoutes ? { routes: config.routes } : { registry: config.registry },
|
|
480
481
|
outputDir: generationOutputDir,
|
|
481
482
|
seed: config.seed,
|
|
482
483
|
dataOverrides: config.dataOverrides,
|
|
@@ -9,3 +9,9 @@ This project is the progressive full-stack stage of an Askr application.
|
|
|
9
9
|
- Keep `index.html` as the only document source. Preserve exactly one `<!--askr-head-->` and one `<!--askr-app-->` marker.
|
|
10
10
|
- Use native forms first; enhanced submission must preserve the same validation and authorization behavior.
|
|
11
11
|
- Never log cookies, authorization values, tokens, form fields, request bodies, or personal data.
|
|
12
|
+
|
|
13
|
+
## Recovery and completion
|
|
14
|
+
|
|
15
|
+
- Run `askr repair` after analyzer failures; it applies only safe mechanical fixes.
|
|
16
|
+
- Resolve remaining semantic diagnostics deliberately.
|
|
17
|
+
- Run `npm run check` before declaring work complete. It requires clean Askr analysis, then runs lint, typecheck, tests, and build.
|
|
@@ -10,8 +10,9 @@
|
|
|
10
10
|
"test": "vp test run",
|
|
11
11
|
"typecheck": "tsc --noEmit",
|
|
12
12
|
"lint": "vp lint src server.ts tests vite.config.ts",
|
|
13
|
+
"analyze": "askr analyze --check",
|
|
13
14
|
"fmt": "vp fmt .",
|
|
14
|
-
"check": "
|
|
15
|
+
"check": "askr check"
|
|
15
16
|
},
|
|
16
17
|
"dependencies": {
|
|
17
18
|
"@askrjs/askr": ">=0.0.53 <0.1.0",
|
|
@@ -16,7 +16,7 @@ npm run fmt # Prettier
|
|
|
16
16
|
|
|
17
17
|
## Architecture
|
|
18
18
|
|
|
19
|
-
- **Routing:** `src/main.tsx` imports `src/pages/_routes.tsx`, then
|
|
19
|
+
- **Routing:** `src/main.tsx` imports the `pageRegistry` from `src/pages/_routes.tsx`, then passes it to `createSPA()`. Route branches live under `src/pages/public`, `src/pages/auth`, and `src/pages/app`.
|
|
20
20
|
- **Layouts:** `_layout.tsx` files own shells. The root layout owns `ThemeScope`; public layouts own landing chrome, auth layouts own sign-in chrome, and app layouts own authenticated sidebar chrome.
|
|
21
21
|
- **UI:** Prefer the `@askrjs/themes/components` catalog before writing local components. Use app-local components only for product concepts such as `MetricCard` and `StatusBadge`; keep charts in `@askrjs/charts`.
|
|
22
22
|
- **State:** `const [value, setValue] = state(initial)`. Read with `value()`, update with `setValue(...)`. Use `derive()` for computed values and `resource()` for async data.
|
|
@@ -58,3 +58,9 @@ tests/
|
|
|
58
58
|
- Use `Link` and `navigate` from `@askrjs/askr/router`.
|
|
59
59
|
- Use headless `@askrjs/ui/*` for behavior primitives and `@askrjs/themes/*` for composed visual surfaces.
|
|
60
60
|
- Avoid hardcoded color systems, custom component catalogs, and React habits like effect-driven data loading.
|
|
61
|
+
|
|
62
|
+
## Recovery and completion
|
|
63
|
+
|
|
64
|
+
- Run `askr repair` after analyzer failures; it applies only safe mechanical fixes.
|
|
65
|
+
- Resolve remaining semantic diagnostics deliberately.
|
|
66
|
+
- Run `npm run check` before declaring work complete. It requires clean Askr analysis, then runs lint, typecheck, tests, and build.
|
|
@@ -11,8 +11,9 @@
|
|
|
11
11
|
"typecheck": "tsc --noEmit",
|
|
12
12
|
"lint": "vp lint .",
|
|
13
13
|
"lint:fix": "npm run lint -- --fix",
|
|
14
|
+
"analyze": "askr analyze --check",
|
|
14
15
|
"fmt": "vp fmt .",
|
|
15
|
-
"check": "
|
|
16
|
+
"check": "askr check"
|
|
16
17
|
},
|
|
17
18
|
"dependencies": {
|
|
18
19
|
"@askrjs/askr": ">=0.0.53 <0.1.0",
|
|
@@ -6,6 +6,7 @@ import {
|
|
|
6
6
|
SettingsIcon,
|
|
7
7
|
SunIcon,
|
|
8
8
|
} from '@askrjs/lucide';
|
|
9
|
+
import { For } from '@askrjs/askr/control';
|
|
9
10
|
import { Link, navigate } from '@askrjs/askr/router';
|
|
10
11
|
import { Button } from '@askrjs/themes/components';
|
|
11
12
|
import { Container, Inline, Stack } from '@askrjs/themes/components';
|
|
@@ -42,14 +43,16 @@ export default function AppLayout({ children }: { children?: unknown }) {
|
|
|
42
43
|
</Link>
|
|
43
44
|
</NavBrand>
|
|
44
45
|
<NavGroup label="Workspace">
|
|
45
|
-
{appNavItems
|
|
46
|
-
|
|
47
|
-
<
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
46
|
+
<For each={[...appNavItems]} by={(item) => item.href}>
|
|
47
|
+
{(item) => (
|
|
48
|
+
<NavLink href={item.href} match={item.match}>
|
|
49
|
+
<Inline as="span" gap="2" align="center">
|
|
50
|
+
{icons[item.icon]}
|
|
51
|
+
<span>{item.label}</span>
|
|
52
|
+
</Inline>
|
|
53
|
+
</NavLink>
|
|
54
|
+
)}
|
|
55
|
+
</For>
|
|
53
56
|
</NavGroup>
|
|
54
57
|
<NavGroup label="Session" align="end">
|
|
55
58
|
<NavLink href="/" match="exact">
|