@jskit-ai/jskit-cli 0.2.81 → 0.2.83

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 (28) hide show
  1. package/package.json +6 -4
  2. package/src/server/appBlueprint.js +1 -1
  3. package/src/server/commandHandlers/helperMap.js +104 -0
  4. package/src/server/commandHandlers/session.js +127 -3
  5. package/src/server/commandHandlers/show.js +169 -34
  6. package/src/server/core/argParser.js +8 -0
  7. package/src/server/core/commandCatalog.js +60 -2
  8. package/src/server/core/createCommandHandlers.js +4 -1
  9. package/src/server/helperMap.js +463 -0
  10. package/src/server/helperMapPaths.js +7 -0
  11. package/src/server/sessionRuntime/appReadiness.js +55 -0
  12. package/src/server/sessionRuntime/constants.js +326 -87
  13. package/src/server/sessionRuntime/preconditions.js +382 -5
  14. package/src/server/sessionRuntime/promptRenderer.js +15 -2
  15. package/src/server/sessionRuntime/prompts/automated_checks.md +42 -0
  16. package/src/server/sessionRuntime/prompts/deep_ui_check.md +53 -0
  17. package/src/server/sessionRuntime/prompts/execute_plan.md +33 -7
  18. package/src/server/sessionRuntime/prompts/final_comment.md +3 -1
  19. package/src/server/sessionRuntime/prompts/issue_details.md +46 -0
  20. package/src/server/sessionRuntime/prompts/new_issue.md +15 -2
  21. package/src/server/sessionRuntime/prompts/plan_issue.md +40 -9
  22. package/src/server/sessionRuntime/prompts/resolve_deslop_findings.md +16 -0
  23. package/src/server/sessionRuntime/prompts/review_changes.md +46 -5
  24. package/src/server/sessionRuntime/prompts/update_blueprint.md +38 -0
  25. package/src/server/sessionRuntime/prompts/user_check.md +15 -1
  26. package/src/server/sessionRuntime/responses.js +860 -62
  27. package/src/server/sessionRuntime.js +1699 -140
  28. package/src/server/sessionRuntime/prompts/doctor_failure.md +0 -26
@@ -0,0 +1,463 @@
1
+ import {
2
+ mkdir,
3
+ readFile,
4
+ readdir,
5
+ stat,
6
+ writeFile
7
+ } from "node:fs/promises";
8
+ import path from "node:path";
9
+ import { compileScript, parse as parseVueSfc } from "@vue/compiler-sfc";
10
+ import tsMorph from "ts-morph";
11
+ import {
12
+ HELPER_MAP_JSON_RELATIVE_PATH,
13
+ HELPER_MAP_MARKDOWN_RELATIVE_PATH
14
+ } from "./helperMapPaths.js";
15
+
16
+ const {
17
+ ModuleKind,
18
+ ModuleResolutionKind,
19
+ Project,
20
+ ScriptTarget
21
+ } = tsMorph;
22
+
23
+ const HELPER_MAP_SCHEMA_VERSION = 1;
24
+ const CODE_EXTENSIONS = new Set([".cjs", ".js", ".jsx", ".mjs", ".ts", ".tsx", ".vue"]);
25
+ const APP_SCAN_ROOTS = Object.freeze(["src", "packages", "config", "server", "scripts"]);
26
+ const EXCLUDED_DIR_NAMES = new Set([
27
+ ".git",
28
+ ".jskit",
29
+ ".npm-cache",
30
+ "coverage",
31
+ "dist",
32
+ "node_modules"
33
+ ]);
34
+ const HELPER_NAME_PATTERN =
35
+ /^(assert|build|coerce|create|ensure|extract|format|get|has|is|list|load|make|map|normalize|parse|read|render|resolve|run|serialize|to|update|use|validate|write)[A-Z_]/u;
36
+
37
+ async function pathExists(filePath) {
38
+ try {
39
+ await stat(filePath);
40
+ return true;
41
+ } catch (error) {
42
+ if (error && error.code === "ENOENT") {
43
+ return false;
44
+ }
45
+ throw error;
46
+ }
47
+ }
48
+
49
+ async function readJsonFile(filePath) {
50
+ return JSON.parse(await readFile(filePath, "utf8"));
51
+ }
52
+
53
+ function normalizePackageDependencies(packageJson = {}) {
54
+ const dependencies = {
55
+ ...(packageJson.dependencies || {}),
56
+ ...(packageJson.devDependencies || {}),
57
+ ...(packageJson.peerDependencies || {}),
58
+ ...(packageJson.optionalDependencies || {})
59
+ };
60
+ return Object.keys(dependencies).sort((left, right) => left.localeCompare(right));
61
+ }
62
+
63
+ function classifySymbol(name = "") {
64
+ if (!name || name === "default") {
65
+ return "default";
66
+ }
67
+ if (/^use[A-Z]/u.test(name)) {
68
+ return "composable";
69
+ }
70
+ if (/^[A-Z]/u.test(name)) {
71
+ return "component_or_class";
72
+ }
73
+ if (HELPER_NAME_PATTERN.test(name)) {
74
+ return "helper";
75
+ }
76
+ return "export";
77
+ }
78
+
79
+ function createExportAnalysisProject() {
80
+ return new Project({
81
+ skipAddingFilesFromTsConfig: true,
82
+ compilerOptions: {
83
+ allowJs: true,
84
+ checkJs: false,
85
+ module: ModuleKind.ESNext,
86
+ moduleResolution: ModuleResolutionKind.NodeNext,
87
+ target: ScriptTarget.ESNext
88
+ }
89
+ });
90
+ }
91
+
92
+ function kindFromDeclaration(declaration, exportName = "") {
93
+ if (exportName === "default") {
94
+ return "default";
95
+ }
96
+ switch (declaration.getKindName()) {
97
+ case "ClassDeclaration":
98
+ return "class";
99
+ case "EnumDeclaration":
100
+ return "enum";
101
+ case "FunctionDeclaration":
102
+ case "FunctionExpression":
103
+ case "MethodDeclaration":
104
+ return "function";
105
+ case "InterfaceDeclaration":
106
+ return "interface";
107
+ case "TypeAliasDeclaration":
108
+ return "type";
109
+ case "VariableDeclaration": {
110
+ const initializer = typeof declaration.getInitializer === "function"
111
+ ? declaration.getInitializer()
112
+ : null;
113
+ const initializerKind = initializer?.getKindName?.() || "";
114
+ return initializerKind === "ArrowFunction" || initializerKind === "FunctionExpression"
115
+ ? "function"
116
+ : "value";
117
+ }
118
+ default:
119
+ return "export";
120
+ }
121
+ }
122
+
123
+ function addSymbol(symbols, symbol) {
124
+ if (!symbol.name) {
125
+ return;
126
+ }
127
+ const key = `${symbol.name}:${symbol.kind}`;
128
+ if (symbols.has(key)) {
129
+ return;
130
+ }
131
+ symbols.set(key, {
132
+ name: symbol.name,
133
+ kind: symbol.kind,
134
+ role: classifySymbol(symbol.name)
135
+ });
136
+ }
137
+
138
+ function extractExportedSymbols(sourceFile) {
139
+ const symbols = new Map();
140
+ for (const [name, declarations] of sourceFile.getExportedDeclarations()) {
141
+ for (const declaration of declarations) {
142
+ addSymbol(symbols, {
143
+ name,
144
+ kind: kindFromDeclaration(declaration, name)
145
+ });
146
+ }
147
+ }
148
+
149
+ return [...symbols.values()].sort((left, right) => {
150
+ const byName = left.name.localeCompare(right.name);
151
+ return byName || left.kind.localeCompare(right.kind);
152
+ });
153
+ }
154
+
155
+ function extractVueScriptSource(source = "", filePath = "") {
156
+ const parsed = parseVueSfc(source, {
157
+ filename: filePath
158
+ });
159
+ if (parsed.errors.length > 0) {
160
+ throw new Error(parsed.errors.map((error) => error.message || String(error)).join("; "));
161
+ }
162
+ const descriptor = parsed.descriptor;
163
+ if (descriptor.scriptSetup) {
164
+ return compileScript(descriptor, {
165
+ id: filePath
166
+ }).content;
167
+ }
168
+ if (descriptor.script) {
169
+ return descriptor.script.content;
170
+ }
171
+ return "";
172
+ }
173
+
174
+ async function addCodeFileToProject(project, file) {
175
+ if (path.extname(file.absolutePath) !== ".vue") {
176
+ return project.addSourceFileAtPath(file.absolutePath);
177
+ }
178
+ const source = extractVueScriptSource(await readFile(file.absolutePath, "utf8"), file.absolutePath);
179
+ if (!source.trim()) {
180
+ return null;
181
+ }
182
+ return project.createSourceFile(`${file.absolutePath}.ts`, source, {
183
+ overwrite: true
184
+ });
185
+ }
186
+
187
+ async function walkCodeFiles(rootPath, relativeRoot = "") {
188
+ const entries = await readdir(rootPath, {
189
+ withFileTypes: true
190
+ });
191
+ const files = [];
192
+ for (const entry of entries.sort((left, right) => left.name.localeCompare(right.name))) {
193
+ if (EXCLUDED_DIR_NAMES.has(entry.name)) {
194
+ continue;
195
+ }
196
+ const absolutePath = path.join(rootPath, entry.name);
197
+ const relativePath = path.join(relativeRoot, entry.name).split(path.sep).join("/");
198
+ if (entry.isDirectory()) {
199
+ files.push(...await walkCodeFiles(absolutePath, relativePath));
200
+ continue;
201
+ }
202
+ if (!entry.isFile() || !CODE_EXTENSIONS.has(path.extname(entry.name))) {
203
+ continue;
204
+ }
205
+ files.push({
206
+ absolutePath,
207
+ relativePath
208
+ });
209
+ }
210
+ return files;
211
+ }
212
+
213
+ async function collectAppExports(targetRoot) {
214
+ const scanFiles = [];
215
+ for (const scanRoot of APP_SCAN_ROOTS) {
216
+ const rootPath = path.join(targetRoot, scanRoot);
217
+ if (await pathExists(rootPath)) {
218
+ scanFiles.push(...await walkCodeFiles(rootPath, scanRoot));
219
+ }
220
+ }
221
+
222
+ const project = createExportAnalysisProject();
223
+ const files = [];
224
+ for (const file of scanFiles.sort((left, right) => left.relativePath.localeCompare(right.relativePath))) {
225
+ const sourceFile = await addCodeFileToProject(project, file);
226
+ if (!sourceFile) {
227
+ continue;
228
+ }
229
+ const symbols = extractExportedSymbols(sourceFile);
230
+ if (symbols.length === 0) {
231
+ continue;
232
+ }
233
+ files.push({
234
+ path: file.relativePath,
235
+ exports: symbols
236
+ });
237
+ }
238
+ return files;
239
+ }
240
+
241
+ function flattenPackageExports(exportsField) {
242
+ const targets = new Map();
243
+
244
+ function addTarget(subpath, target) {
245
+ if (!target || target.includes("*")) {
246
+ return;
247
+ }
248
+ targets.set(`${subpath}:${target}`, {
249
+ subpath,
250
+ target
251
+ });
252
+ }
253
+
254
+ function collect(value, subpath = ".") {
255
+ if (typeof value === "string") {
256
+ addTarget(subpath, value);
257
+ return;
258
+ }
259
+ if (!value || Array.isArray(value) || typeof value !== "object") {
260
+ return;
261
+ }
262
+ const entries = Object.entries(value);
263
+ const hasSubpathKeys = entries.some(([key]) => key.startsWith("."));
264
+ for (const [key, nested] of entries) {
265
+ if (key.startsWith(".")) {
266
+ collect(nested, key);
267
+ } else {
268
+ collect(nested, hasSubpathKeys ? subpath : subpath || ".");
269
+ }
270
+ }
271
+ }
272
+
273
+ collect(exportsField, ".");
274
+ return [...targets.values()].sort((left, right) => {
275
+ const bySubpath = left.subpath.localeCompare(right.subpath);
276
+ return bySubpath || left.target.localeCompare(right.target);
277
+ });
278
+ }
279
+
280
+ async function collectJskitPackageExports(targetRoot, packageJson = {}) {
281
+ const packageNames = normalizePackageDependencies(packageJson)
282
+ .filter((name) => name.startsWith("@jskit-ai/"));
283
+ const packages = [];
284
+ const project = createExportAnalysisProject();
285
+
286
+ for (const packageName of packageNames) {
287
+ const packageRoot = path.join(targetRoot, "node_modules", ...packageName.split("/"));
288
+ const packageJsonPath = path.join(packageRoot, "package.json");
289
+ if (!await pathExists(packageJsonPath)) {
290
+ packages.push({
291
+ name: packageName,
292
+ installed: false,
293
+ exports: []
294
+ });
295
+ continue;
296
+ }
297
+
298
+ const installedPackageJson = await readJsonFile(packageJsonPath);
299
+ const exportTargets = flattenPackageExports(installedPackageJson.exports || {});
300
+ const exports = [];
301
+ for (const exportTarget of exportTargets) {
302
+ const normalizedTarget = exportTarget.target.replace(/^\.\//u, "");
303
+ const targetPath = path.join(packageRoot, normalizedTarget);
304
+ const ext = path.extname(targetPath);
305
+ if (!CODE_EXTENSIONS.has(ext) || !await pathExists(targetPath)) {
306
+ continue;
307
+ }
308
+ const sourceFile = await addCodeFileToProject(project, {
309
+ absolutePath: targetPath,
310
+ relativePath: normalizedTarget
311
+ });
312
+ exports.push({
313
+ subpath: exportTarget.subpath,
314
+ target: normalizedTarget.split(path.sep).join("/"),
315
+ exports: sourceFile ? extractExportedSymbols(sourceFile) : []
316
+ });
317
+ }
318
+
319
+ packages.push({
320
+ name: packageName,
321
+ version: installedPackageJson.version || "",
322
+ installed: true,
323
+ exports
324
+ });
325
+ }
326
+
327
+ return packages;
328
+ }
329
+
330
+ function renderExportList(symbols = []) {
331
+ if (symbols.length === 0) {
332
+ return " - no exported symbols detected";
333
+ }
334
+ return symbols
335
+ .map((symbol) => ` - ${symbol.name} (${symbol.kind}, ${symbol.role})`)
336
+ .join("\n");
337
+ }
338
+
339
+ function renderHelperMapMarkdown(map) {
340
+ const lines = [
341
+ "# JSKIT Helper Map",
342
+ "",
343
+ "Generated by `jskit helper-map update`. Read this before adding new helpers, composables, service functions, maps, or package glue.",
344
+ "",
345
+ `Root package: ${map.rootPackage.name || "unknown"}`,
346
+ "",
347
+ "## App-local exports",
348
+ ""
349
+ ];
350
+
351
+ if (map.app.files.length === 0) {
352
+ lines.push("No app-local exported helpers or symbols detected.", "");
353
+ } else {
354
+ for (const file of map.app.files) {
355
+ lines.push(`- ${file.path}`, renderExportList(file.exports), "");
356
+ }
357
+ }
358
+
359
+ lines.push("## Direct JSKIT package exports", "");
360
+ if (map.jskitPackages.length === 0) {
361
+ lines.push("No direct `@jskit-ai/*` dependencies were found.", "");
362
+ } else {
363
+ for (const packageEntry of map.jskitPackages) {
364
+ if (!packageEntry.installed) {
365
+ lines.push(`- ${packageEntry.name}: not installed in node_modules`, "");
366
+ continue;
367
+ }
368
+ lines.push(`- ${packageEntry.name}@${packageEntry.version || "unknown"}`);
369
+ if (packageEntry.exports.length === 0) {
370
+ lines.push(" - no exported code files detected");
371
+ } else {
372
+ for (const exportEntry of packageEntry.exports) {
373
+ lines.push(` - ${exportEntry.subpath} -> ${exportEntry.target}`);
374
+ for (const symbol of exportEntry.exports) {
375
+ lines.push(` - ${symbol.name} (${symbol.kind}, ${symbol.role})`);
376
+ }
377
+ }
378
+ }
379
+ lines.push("");
380
+ }
381
+ }
382
+
383
+ return `${lines.join("\n").replace(/\n{3,}/gu, "\n\n").trimEnd()}\n`;
384
+ }
385
+
386
+ async function buildHelperMap({ targetRoot }) {
387
+ const packageJsonPath = path.join(targetRoot, "package.json");
388
+ const packageJson = await readJsonFile(packageJsonPath);
389
+ const map = {
390
+ schemaVersion: HELPER_MAP_SCHEMA_VERSION,
391
+ generatedBy: "jskit helper-map update",
392
+ rootPackage: {
393
+ name: packageJson.name || "",
394
+ version: packageJson.version || ""
395
+ },
396
+ app: {
397
+ files: await collectAppExports(targetRoot)
398
+ },
399
+ jskitPackages: await collectJskitPackageExports(targetRoot, packageJson)
400
+ };
401
+ return {
402
+ ok: true,
403
+ map,
404
+ helperMapJsonPath: path.join(targetRoot, HELPER_MAP_JSON_RELATIVE_PATH),
405
+ helperMapMarkdownPath: path.join(targetRoot, HELPER_MAP_MARKDOWN_RELATIVE_PATH)
406
+ };
407
+ }
408
+
409
+ async function readHelperMap({ targetRoot }) {
410
+ const helperMapJsonPath = path.join(targetRoot, HELPER_MAP_JSON_RELATIVE_PATH);
411
+ const helperMapMarkdownPath = path.join(targetRoot, HELPER_MAP_MARKDOWN_RELATIVE_PATH);
412
+ if (!await pathExists(helperMapJsonPath)) {
413
+ return {
414
+ ok: true,
415
+ exists: false,
416
+ helperMapJsonPath,
417
+ helperMapMarkdownPath,
418
+ map: null,
419
+ markdown: ""
420
+ };
421
+ }
422
+ return {
423
+ ok: true,
424
+ exists: true,
425
+ helperMapJsonPath,
426
+ helperMapMarkdownPath,
427
+ map: await readJsonFile(helperMapJsonPath),
428
+ markdown: await pathExists(helperMapMarkdownPath) ? await readFile(helperMapMarkdownPath, "utf8") : ""
429
+ };
430
+ }
431
+
432
+ async function updateHelperMap({ targetRoot }) {
433
+ const payload = await buildHelperMap({ targetRoot });
434
+ const markdown = renderHelperMapMarkdown(payload.map);
435
+ const json = `${JSON.stringify(payload.map, null, 2)}\n`;
436
+ const currentJson = await pathExists(payload.helperMapJsonPath)
437
+ ? await readFile(payload.helperMapJsonPath, "utf8")
438
+ : "";
439
+ const currentMarkdown = await pathExists(payload.helperMapMarkdownPath)
440
+ ? await readFile(payload.helperMapMarkdownPath, "utf8")
441
+ : "";
442
+ const changed = currentJson !== json || currentMarkdown !== markdown;
443
+ if (changed) {
444
+ await mkdir(path.dirname(payload.helperMapJsonPath), {
445
+ recursive: true
446
+ });
447
+ await writeFile(payload.helperMapJsonPath, json, "utf8");
448
+ await writeFile(payload.helperMapMarkdownPath, markdown, "utf8");
449
+ }
450
+ return {
451
+ ...payload,
452
+ changed,
453
+ markdown
454
+ };
455
+ }
456
+
457
+ export {
458
+ HELPER_MAP_JSON_RELATIVE_PATH,
459
+ HELPER_MAP_MARKDOWN_RELATIVE_PATH,
460
+ buildHelperMap,
461
+ readHelperMap,
462
+ updateHelperMap
463
+ };
@@ -0,0 +1,7 @@
1
+ const HELPER_MAP_JSON_RELATIVE_PATH = ".jskit/helper-map.json";
2
+ const HELPER_MAP_MARKDOWN_RELATIVE_PATH = ".jskit/helper-map.md";
3
+
4
+ export {
5
+ HELPER_MAP_JSON_RELATIVE_PATH,
6
+ HELPER_MAP_MARKDOWN_RELATIVE_PATH
7
+ };
@@ -0,0 +1,55 @@
1
+ import { access, stat } from "node:fs/promises";
2
+ import { constants as fsConstants } from "node:fs";
3
+ import path from "node:path";
4
+
5
+ const REQUIRED_READY_FILES = Object.freeze([
6
+ "package.json",
7
+ ".jskit/lock.json",
8
+ "config/public.js"
9
+ ]);
10
+ const REQUIRED_READY_DIRECTORIES = Object.freeze([
11
+ "src",
12
+ "packages"
13
+ ]);
14
+
15
+ async function pathExists(filePath) {
16
+ try {
17
+ await access(filePath, fsConstants.F_OK);
18
+ return true;
19
+ } catch {
20
+ return false;
21
+ }
22
+ }
23
+
24
+ async function directoryExists(filePath) {
25
+ try {
26
+ return (await stat(filePath)).isDirectory();
27
+ } catch {
28
+ return false;
29
+ }
30
+ }
31
+
32
+ async function inspectReadyJskitAppRoot(rootPath) {
33
+ const missing = [];
34
+ for (const relativePath of REQUIRED_READY_FILES) {
35
+ if (!await pathExists(path.join(rootPath, relativePath))) {
36
+ missing.push(relativePath);
37
+ }
38
+ }
39
+ for (const relativePath of REQUIRED_READY_DIRECTORIES) {
40
+ if (!await directoryExists(path.join(rootPath, relativePath))) {
41
+ missing.push(`${relativePath}/`);
42
+ }
43
+ }
44
+ return {
45
+ missing,
46
+ ok: missing.length < 1,
47
+ requiredDirectories: [...REQUIRED_READY_DIRECTORIES],
48
+ requiredFiles: [...REQUIRED_READY_FILES],
49
+ rootPath
50
+ };
51
+ }
52
+
53
+ export {
54
+ inspectReadyJskitAppRoot
55
+ };