@jskit-ai/jskit-cli 0.2.92 → 0.2.97
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 +4 -4
- package/src/server/appBlueprint.js +37 -14
- package/src/server/core/argParser.js +0 -12
- package/src/server/core/commandCatalog.js +2 -92
- package/src/server/core/createCommandHandlers.js +0 -3
- package/src/server/helperMap.js +214 -56
- package/src/server/index.js +0 -1
- package/src/server/{sessionRuntime/prompts → prompts}/app_blueprint.md +1 -1
- package/src/server/commandHandlers/session.js +0 -471
- package/src/server/sessionRuntime/appReadiness.js +0 -55
- package/src/server/sessionRuntime/constants.js +0 -377
- package/src/server/sessionRuntime/io.js +0 -97
- package/src/server/sessionRuntime/paths.js +0 -163
- package/src/server/sessionRuntime/preconditions.js +0 -663
- package/src/server/sessionRuntime/promptRenderer.js +0 -41
- package/src/server/sessionRuntime/prompts/automated_checks_run.md +0 -28
- package/src/server/sessionRuntime/prompts/blueprint_updated.md +0 -29
- package/src/server/sessionRuntime/prompts/deep_ui_check_run.md +0 -40
- package/src/server/sessionRuntime/prompts/final_comment.md +0 -10
- package/src/server/sessionRuntime/prompts/final_report_created.md +0 -44
- package/src/server/sessionRuntime/prompts/issue_created.md +0 -26
- package/src/server/sessionRuntime/prompts/issue_prompt_rendered.md +0 -1
- package/src/server/sessionRuntime/prompts/make_plan.md +0 -57
- package/src/server/sessionRuntime/prompts/plan_executed.md +0 -39
- package/src/server/sessionRuntime/prompts/pr_failure.md +0 -28
- package/src/server/sessionRuntime/prompts/pr_merge_prepared.md +0 -22
- package/src/server/sessionRuntime/prompts/review_changes_accepted_resolve.md +0 -12
- package/src/server/sessionRuntime/prompts/review_prompt_rendered.md +0 -61
- package/src/server/sessionRuntime/prompts/user_check_completed.md +0 -17
- package/src/server/sessionRuntime/responses.js +0 -1481
- package/src/server/sessionRuntime/worktrees.js +0 -31
- package/src/server/sessionRuntime.js +0 -3659
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jskit-ai/jskit-cli",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.97",
|
|
4
4
|
"description": "Bundle and package orchestration CLI for JSKIT apps.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"files": [
|
|
@@ -20,9 +20,9 @@
|
|
|
20
20
|
"test": "node --test"
|
|
21
21
|
},
|
|
22
22
|
"dependencies": {
|
|
23
|
-
"@jskit-ai/jskit-catalog": "0.1.
|
|
24
|
-
"@jskit-ai/kernel": "0.1.
|
|
25
|
-
"@jskit-ai/shell-web": "0.1.
|
|
23
|
+
"@jskit-ai/jskit-catalog": "0.1.92",
|
|
24
|
+
"@jskit-ai/kernel": "0.1.84",
|
|
25
|
+
"@jskit-ai/shell-web": "0.1.83",
|
|
26
26
|
"@vue/compiler-sfc": "^3.5.29",
|
|
27
27
|
"ts-morph": "^28.0.0"
|
|
28
28
|
},
|
|
@@ -1,20 +1,42 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { constants as fsConstants } from "node:fs";
|
|
2
|
+
import { access, mkdir, readFile, writeFile } from "node:fs/promises";
|
|
2
3
|
import path from "node:path";
|
|
3
|
-
import {
|
|
4
|
-
PROMPT_DIRECTORY
|
|
5
|
-
} from "./sessionRuntime/constants.js";
|
|
6
|
-
import {
|
|
7
|
-
fileExists,
|
|
8
|
-
normalizeText,
|
|
9
|
-
readTextIfExists,
|
|
10
|
-
writeTextFile
|
|
11
|
-
} from "./sessionRuntime/io.js";
|
|
12
|
-
import {
|
|
13
|
-
renderTemplate
|
|
14
|
-
} from "./sessionRuntime/promptRenderer.js";
|
|
4
|
+
import { fileURLToPath } from "node:url";
|
|
15
5
|
|
|
16
6
|
const APP_BLUEPRINT_RELATIVE_PATH = ".jskit/APP_BLUEPRINT.md";
|
|
17
7
|
const APP_PROMPT_OVERRIDE_RELATIVE_ROOT = ".jskit/prompts";
|
|
8
|
+
const APP_BLUEPRINT_PROMPT_DIRECTORY = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "prompts");
|
|
9
|
+
|
|
10
|
+
function normalizeText(value) {
|
|
11
|
+
return String(value || "").trim();
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
async function fileExists(filePath) {
|
|
15
|
+
try {
|
|
16
|
+
await access(filePath, fsConstants.F_OK);
|
|
17
|
+
return true;
|
|
18
|
+
} catch {
|
|
19
|
+
return false;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
async function readTextIfExists(filePath) {
|
|
24
|
+
if (!filePath || !(await fileExists(filePath))) {
|
|
25
|
+
return "";
|
|
26
|
+
}
|
|
27
|
+
return readFile(filePath, "utf8");
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
async function writeTextFile(filePath, value) {
|
|
31
|
+
await mkdir(path.dirname(filePath), { recursive: true });
|
|
32
|
+
await writeFile(filePath, `${String(value || "").replace(/\s*$/u, "")}\n`, "utf8");
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function renderTemplate(source, values = {}) {
|
|
36
|
+
return String(source || "").replace(/\{\{\s*([A-Za-z0-9_]+)\s*\}\}/gu, (_match, key) => {
|
|
37
|
+
return String(values[key] ?? "");
|
|
38
|
+
});
|
|
39
|
+
}
|
|
18
40
|
|
|
19
41
|
function resolveAppBlueprintPaths(targetRoot = process.cwd()) {
|
|
20
42
|
const normalizedTargetRoot = path.resolve(normalizeText(targetRoot) || process.cwd());
|
|
@@ -37,7 +59,7 @@ async function readAppPromptTemplate(paths, templateName) {
|
|
|
37
59
|
if (await fileExists(overridePath)) {
|
|
38
60
|
return readTextIfExists(overridePath);
|
|
39
61
|
}
|
|
40
|
-
return readTextIfExists(path.join(
|
|
62
|
+
return readTextIfExists(path.join(APP_BLUEPRINT_PROMPT_DIRECTORY, normalizedName));
|
|
41
63
|
}
|
|
42
64
|
|
|
43
65
|
async function renderAppBlueprintPrompt({ targetRoot = process.cwd(), appBrief = "" } = {}) {
|
|
@@ -116,6 +138,7 @@ async function readTextInputFile(cwd, inputPath) {
|
|
|
116
138
|
|
|
117
139
|
export {
|
|
118
140
|
APP_BLUEPRINT_RELATIVE_PATH,
|
|
141
|
+
APP_BLUEPRINT_PROMPT_DIRECTORY,
|
|
119
142
|
APP_PROMPT_OVERRIDE_RELATIVE_ROOT,
|
|
120
143
|
extractAppBlueprintText,
|
|
121
144
|
readAppBlueprint,
|
|
@@ -23,8 +23,6 @@ function parseArgs(argv, { createCliError } = {}) {
|
|
|
23
23
|
verbose: false,
|
|
24
24
|
json: false,
|
|
25
25
|
all: false,
|
|
26
|
-
abandoned: false,
|
|
27
|
-
completed: false,
|
|
28
26
|
concrete: false,
|
|
29
27
|
help: true,
|
|
30
28
|
inlineOptions: {}
|
|
@@ -52,8 +50,6 @@ function parseArgs(argv, { createCliError } = {}) {
|
|
|
52
50
|
verbose: false,
|
|
53
51
|
json: false,
|
|
54
52
|
all: false,
|
|
55
|
-
abandoned: false,
|
|
56
|
-
completed: false,
|
|
57
53
|
concrete: false,
|
|
58
54
|
help: false,
|
|
59
55
|
inlineOptions: {}
|
|
@@ -113,14 +109,6 @@ function parseArgs(argv, { createCliError } = {}) {
|
|
|
113
109
|
options.all = true;
|
|
114
110
|
continue;
|
|
115
111
|
}
|
|
116
|
-
if (token === "--abandoned") {
|
|
117
|
-
options.abandoned = true;
|
|
118
|
-
continue;
|
|
119
|
-
}
|
|
120
|
-
if (token === "--completed") {
|
|
121
|
-
options.completed = true;
|
|
122
|
-
continue;
|
|
123
|
-
}
|
|
124
112
|
if (token === "--concrete") {
|
|
125
113
|
options.concrete = true;
|
|
126
114
|
continue;
|
|
@@ -9,9 +9,7 @@ const OPTION_FLAG_LABELS = Object.freeze({
|
|
|
9
9
|
checkDiLabels: "--check-di-labels",
|
|
10
10
|
verbose: "--verbose",
|
|
11
11
|
json: "--json",
|
|
12
|
-
all: "--all"
|
|
13
|
-
abandoned: "--abandoned",
|
|
14
|
-
completed: "--completed"
|
|
12
|
+
all: "--all"
|
|
15
13
|
});
|
|
16
14
|
|
|
17
15
|
const PARSED_BOOLEAN_OPTION_KEYS = Object.freeze(Object.keys(OPTION_FLAG_LABELS));
|
|
@@ -200,94 +198,6 @@ const COMMAND_DESCRIPTORS = Object.freeze({
|
|
|
200
198
|
allowedValueOptionNames: Object.freeze([]),
|
|
201
199
|
canDelegateInlineOptions: (positional = []) => Array.isArray(positional) && positional.length > 0
|
|
202
200
|
}),
|
|
203
|
-
session: Object.freeze({
|
|
204
|
-
command: "session",
|
|
205
|
-
aliases: Object.freeze([]),
|
|
206
|
-
showInOverview: true,
|
|
207
|
-
summary: "Run file-backed JSKIT issue sessions.",
|
|
208
|
-
minimalUse: "jskit session create",
|
|
209
|
-
parameters: Object.freeze([
|
|
210
|
-
Object.freeze({
|
|
211
|
-
name: "create | <sessionId>",
|
|
212
|
-
description: "Create a session, inspect a session, or run a session subcommand."
|
|
213
|
-
}),
|
|
214
|
-
Object.freeze({
|
|
215
|
-
name: "[step|create_worktree|run_npm_install|define_issue|create_issue_file|create_issue_on_gh|make_plan|execute_plan|run_deep_ui_check|run_automated_checks|update_blueprint|commit_changes|create_pull_request_file|create_pr_on_gh|prepare_for_merge|merge_pr|sync_main_checkout|finish_session|next|skip|deslop|resolve-deslop|diff|rewind|abandon|adopt-codex-thread]",
|
|
216
|
-
description: "Inspect, run an explicit step command, continue, skip, deslop, inspect a diff, rewind, abandon, or attach a Codex thread id."
|
|
217
|
-
})
|
|
218
|
-
]),
|
|
219
|
-
defaults: Object.freeze([
|
|
220
|
-
"Active session state lives in .jskit/sessions/active/<session_id> under the current target app.",
|
|
221
|
-
"Session worktrees live in .jskit/sessions/active/<session_id>/worktree.",
|
|
222
|
-
"The session id is timestamp-based and is the primary key.",
|
|
223
|
-
"Bare list output includes active sessions only; use --abandoned, --completed, or --all for archived sessions.",
|
|
224
|
-
"Use --json for the stable machine-readable contract consumed by JSKIT AI Studio.",
|
|
225
|
-
"Issue handoff is file-based: Codex writes issue.md under the session root, then the user runs next. Plans stay in the terminal.",
|
|
226
|
-
"Use step to inspect the current step without running its work.",
|
|
227
|
-
"Use explicit step commands such as create_worktree, run_npm_install, define_issue, create_issue_file, create_issue_on_gh, make_plan, execute_plan, run_deep_ui_check, run_automated_checks, update_blueprint, commit_changes, create_pull_request_file, create_pr_on_gh, prepare_for_merge, merge_pr, sync_main_checkout, and finish_session to do the current step's work.",
|
|
228
|
-
"Use next to move to the next step after the current step's required work/result exists.",
|
|
229
|
-
"Use skip to record the current step as skipped and move to the next step.",
|
|
230
|
-
"Use deslop to run the review/deslop prompt from either review/deslop step.",
|
|
231
|
-
"Use resolve-deslop to send the explicit resolve review/deslop prompt. Nothing advances automatically afterward.",
|
|
232
|
-
"Use rewind <step_id> or rewind --step <step_id> to delete a completed step and later JSKIT-owned session artifacts.",
|
|
233
|
-
"Use --rework-notes - with --user-check failed to stay on user check and decide where to rewind.",
|
|
234
|
-
"Use deslop instead of hand-writing --review-findings-remaining true.",
|
|
235
|
-
"Use next instead of hand-writing --review-findings-remaining false.",
|
|
236
|
-
"Use skip --skip-reason \"<reason>\" when a step is intentionally skipped.",
|
|
237
|
-
"Use prepare_for_merge at Merge PR to render the Codex prep prompt.",
|
|
238
|
-
"Use merge_pr at Merge PR to attempt the GitHub merge.",
|
|
239
|
-
"Use sync_main_checkout at Sync main checkout after a successful merge.",
|
|
240
|
-
"Use finish_session at Congratulations! to archive the completed session.",
|
|
241
|
-
"Run the blueprint step once to render its Codex prompt, then again after Codex updates .jskit/APP_BLUEPRINT.md."
|
|
242
|
-
]),
|
|
243
|
-
examples: Object.freeze([
|
|
244
|
-
Object.freeze({
|
|
245
|
-
label: "Manual CLI flow",
|
|
246
|
-
lines: Object.freeze([
|
|
247
|
-
"jskit session create",
|
|
248
|
-
"jskit session 2026-05-11_21-42-08 step",
|
|
249
|
-
"jskit session 2026-05-11_21-42-08 create_worktree",
|
|
250
|
-
"jskit session 2026-05-11_21-42-08 next",
|
|
251
|
-
"jskit session 2026-05-11_21-42-08 run_npm_install",
|
|
252
|
-
"jskit session 2026-05-11_21-42-08 next",
|
|
253
|
-
"jskit session 2026-05-11_21-42-08 define_issue --prompt \"Fix the customer filters\"",
|
|
254
|
-
"jskit session 2026-05-11_21-42-08 skip --skip-reason \"Handled outside JSKIT\"",
|
|
255
|
-
"jskit session 2026-05-11_21-42-08 create_issue_file",
|
|
256
|
-
"jskit session 2026-05-11_21-42-08 next",
|
|
257
|
-
"jskit session 2026-05-11_21-42-08 create_issue_on_gh",
|
|
258
|
-
"jskit session 2026-05-11_21-42-08 next",
|
|
259
|
-
"jskit session 2026-05-11_21-42-08 make_plan",
|
|
260
|
-
"jskit session 2026-05-11_21-42-08 next",
|
|
261
|
-
"jskit session 2026-05-11_21-42-08 execute_plan",
|
|
262
|
-
"jskit session 2026-05-11_21-42-08 deslop",
|
|
263
|
-
"jskit session 2026-05-11_21-42-08 resolve-deslop",
|
|
264
|
-
"jskit session 2026-05-11_21-42-08 commit_changes",
|
|
265
|
-
"jskit session 2026-05-11_21-42-08 next",
|
|
266
|
-
"jskit session 2026-05-11_21-42-08 create_pull_request_file",
|
|
267
|
-
"jskit session 2026-05-11_21-42-08 next",
|
|
268
|
-
"jskit session 2026-05-11_21-42-08 create_pr_on_gh",
|
|
269
|
-
"jskit session 2026-05-11_21-42-08 next",
|
|
270
|
-
"jskit session 2026-05-11_21-42-08 prepare_for_merge",
|
|
271
|
-
"jskit session 2026-05-11_21-42-08 merge_pr",
|
|
272
|
-
"jskit session 2026-05-11_21-42-08 next",
|
|
273
|
-
"jskit session 2026-05-11_21-42-08 sync_main_checkout",
|
|
274
|
-
"jskit session 2026-05-11_21-42-08 next",
|
|
275
|
-
"jskit session 2026-05-11_21-42-08 finish_session",
|
|
276
|
-
"jskit session 2026-05-11_21-42-08 step --user-check failed --rework-notes -",
|
|
277
|
-
"jskit session 2026-05-11_21-42-08 rewind plan_made --json",
|
|
278
|
-
"jskit session 2026-05-11_21-42-08 diff --json"
|
|
279
|
-
])
|
|
280
|
-
})
|
|
281
|
-
]),
|
|
282
|
-
fullUse:
|
|
283
|
-
"jskit session [create|new|<sessionId>] [step|create_worktree|run_npm_install|define_issue|create_issue_file|create_issue_on_gh|make_plan|execute_plan|run_deep_ui_check|run_automated_checks|update_blueprint|commit_changes|create_pull_request_file|create_pr_on_gh|prepare_for_merge|merge_pr|sync_main_checkout|finish_session|next|skip|deslop|resolve-deslop|diff|rewind|abandon|adopt-codex-thread] [step_id] [--step <step_id>] [--prompt <text>] [--review-findings-remaining true|--review-findings-remaining false] [--resolve-deslop true] [--skip-step true|skip --skip-reason <text>] [--user-check <passed|failed>] [--rework-notes <text>|--rework-notes-file <path>] [--codex-thread-id <id>] [--abandoned|--completed|--all] [--json]",
|
|
284
|
-
showHelpOnBareInvocation: false,
|
|
285
|
-
handlerName: "commandSession",
|
|
286
|
-
allowedFlagKeys: Object.freeze(["json", "abandoned", "completed", "all"]),
|
|
287
|
-
inlineOptionMode: "delegate",
|
|
288
|
-
allowedValueOptionNames: Object.freeze([]),
|
|
289
|
-
canDelegateInlineOptions: (positional = []) => Array.isArray(positional) && positional.length > 0
|
|
290
|
-
}),
|
|
291
201
|
blueprint: Object.freeze({
|
|
292
202
|
command: "blueprint",
|
|
293
203
|
aliases: Object.freeze([]),
|
|
@@ -301,7 +211,7 @@ const COMMAND_DESCRIPTORS = Object.freeze({
|
|
|
301
211
|
})
|
|
302
212
|
]),
|
|
303
213
|
defaults: Object.freeze([
|
|
304
|
-
"The app blueprint is product-level app state, not a feature
|
|
214
|
+
"The app blueprint is product-level app state, not a feature delivery step.",
|
|
305
215
|
"The saved blueprint lives at .jskit/APP_BLUEPRINT.md in the current target app.",
|
|
306
216
|
"Project prompt overrides live at .jskit/prompts/app_blueprint.md.",
|
|
307
217
|
"Use --json for a stable machine-readable response."
|
|
@@ -6,7 +6,6 @@ import { createAppCommands } from "../commandHandlers/app.js";
|
|
|
6
6
|
import { createMobileCommands } from "../commandHandlers/mobile.js";
|
|
7
7
|
import { createHealthCommands } from "../commandHandlers/health.js";
|
|
8
8
|
import { createCompletionCommands } from "../commandHandlers/completion.js";
|
|
9
|
-
import { createSessionCommands } from "../commandHandlers/session.js";
|
|
10
9
|
import { createBlueprintCommands } from "../commandHandlers/blueprint.js";
|
|
11
10
|
import { createHelperMapCommands } from "../commandHandlers/helperMap.js";
|
|
12
11
|
|
|
@@ -32,7 +31,6 @@ function createCommandHandlers(deps = {}) {
|
|
|
32
31
|
const { commandMobile } = createMobileCommands(commandContext, { commandAdd });
|
|
33
32
|
const { commandDoctor, commandLintDescriptors } = createHealthCommands(commandContext);
|
|
34
33
|
const { commandCompletion } = createCompletionCommands(commandContext);
|
|
35
|
-
const { commandSession } = createSessionCommands(commandContext);
|
|
36
34
|
const { commandBlueprint } = createBlueprintCommands(commandContext);
|
|
37
35
|
const { commandHelperMap } = createHelperMapCommands(commandContext);
|
|
38
36
|
|
|
@@ -53,7 +51,6 @@ function createCommandHandlers(deps = {}) {
|
|
|
53
51
|
commandRemove,
|
|
54
52
|
commandDoctor,
|
|
55
53
|
commandLintDescriptors,
|
|
56
|
-
commandSession,
|
|
57
54
|
commandBlueprint,
|
|
58
55
|
commandHelperMap
|
|
59
56
|
};
|
package/src/server/helperMap.js
CHANGED
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
import {
|
|
2
|
+
createHash
|
|
3
|
+
} from "node:crypto";
|
|
1
4
|
import {
|
|
2
5
|
mkdir,
|
|
3
6
|
readFile,
|
|
@@ -17,10 +20,11 @@ const {
|
|
|
17
20
|
ModuleKind,
|
|
18
21
|
ModuleResolutionKind,
|
|
19
22
|
Project,
|
|
20
|
-
ScriptTarget
|
|
23
|
+
ScriptTarget,
|
|
24
|
+
ts
|
|
21
25
|
} = tsMorph;
|
|
22
26
|
|
|
23
|
-
const HELPER_MAP_SCHEMA_VERSION =
|
|
27
|
+
const HELPER_MAP_SCHEMA_VERSION = 2;
|
|
24
28
|
const CODE_EXTENSIONS = new Set([".cjs", ".js", ".jsx", ".mjs", ".ts", ".tsx", ".vue"]);
|
|
25
29
|
const APP_SCAN_ROOTS = Object.freeze(["src", "packages", "config", "server", "scripts"]);
|
|
26
30
|
const EXCLUDED_DIR_NAMES = new Set([
|
|
@@ -50,6 +54,12 @@ async function readJsonFile(filePath) {
|
|
|
50
54
|
return JSON.parse(await readFile(filePath, "utf8"));
|
|
51
55
|
}
|
|
52
56
|
|
|
57
|
+
async function readFileHash(filePath) {
|
|
58
|
+
return createHash("sha256")
|
|
59
|
+
.update(await readFile(filePath))
|
|
60
|
+
.digest("hex");
|
|
61
|
+
}
|
|
62
|
+
|
|
53
63
|
function normalizePackageDependencies(packageJson = {}) {
|
|
54
64
|
const dependencies = {
|
|
55
65
|
...(packageJson.dependencies || {}),
|
|
@@ -89,37 +99,6 @@ function createExportAnalysisProject() {
|
|
|
89
99
|
});
|
|
90
100
|
}
|
|
91
101
|
|
|
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
102
|
function addSymbol(symbols, symbol) {
|
|
124
103
|
if (!symbol.name) {
|
|
125
104
|
return;
|
|
@@ -135,14 +114,116 @@ function addSymbol(symbols, symbol) {
|
|
|
135
114
|
});
|
|
136
115
|
}
|
|
137
116
|
|
|
117
|
+
function compilerNodeHasModifier(node, modifierKind) {
|
|
118
|
+
return Array.isArray(node?.modifiers) && node.modifiers.some((modifier) => modifier.kind === modifierKind);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function exportedDeclarationName(node) {
|
|
122
|
+
if (compilerNodeHasModifier(node, ts.SyntaxKind.DefaultKeyword)) {
|
|
123
|
+
return "default";
|
|
124
|
+
}
|
|
125
|
+
return String(node?.name?.text || node?.name?.escapedText || "").trim();
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function addExportedDeclarationSymbol(symbols, node, kind = "export") {
|
|
129
|
+
const name = exportedDeclarationName(node);
|
|
130
|
+
if (!name) {
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
addSymbol(symbols, {
|
|
134
|
+
kind: name === "default" ? "default" : kind,
|
|
135
|
+
name
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function bindingNameTexts(bindingName, names = []) {
|
|
140
|
+
if (!bindingName) {
|
|
141
|
+
return names;
|
|
142
|
+
}
|
|
143
|
+
if (ts.isIdentifier(bindingName)) {
|
|
144
|
+
names.push(String(bindingName.text || ""));
|
|
145
|
+
return names;
|
|
146
|
+
}
|
|
147
|
+
if (ts.isObjectBindingPattern(bindingName) || ts.isArrayBindingPattern(bindingName)) {
|
|
148
|
+
for (const element of bindingName.elements || []) {
|
|
149
|
+
if (element.name) {
|
|
150
|
+
bindingNameTexts(element.name, names);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
return names;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
function addVariableStatementExports(symbols, statement) {
|
|
158
|
+
if (!compilerNodeHasModifier(statement, ts.SyntaxKind.ExportKeyword)) {
|
|
159
|
+
return;
|
|
160
|
+
}
|
|
161
|
+
for (const declaration of statement.declarationList?.declarations || []) {
|
|
162
|
+
for (const name of bindingNameTexts(declaration.name)) {
|
|
163
|
+
addSymbol(symbols, {
|
|
164
|
+
kind: declaration.initializer &&
|
|
165
|
+
(ts.isArrowFunction(declaration.initializer) || ts.isFunctionExpression(declaration.initializer))
|
|
166
|
+
? "function"
|
|
167
|
+
: "value",
|
|
168
|
+
name
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
function addNamedExportSymbols(symbols, exportClause) {
|
|
175
|
+
if (!exportClause) {
|
|
176
|
+
return;
|
|
177
|
+
}
|
|
178
|
+
if (ts.isNamespaceExport(exportClause)) {
|
|
179
|
+
addSymbol(symbols, {
|
|
180
|
+
kind: "export",
|
|
181
|
+
name: String(exportClause.name?.text || "")
|
|
182
|
+
});
|
|
183
|
+
return;
|
|
184
|
+
}
|
|
185
|
+
if (!ts.isNamedExports(exportClause)) {
|
|
186
|
+
return;
|
|
187
|
+
}
|
|
188
|
+
for (const specifier of exportClause.elements || []) {
|
|
189
|
+
addSymbol(symbols, {
|
|
190
|
+
kind: "export",
|
|
191
|
+
name: String(specifier.name?.text || "")
|
|
192
|
+
});
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
138
196
|
function extractExportedSymbols(sourceFile) {
|
|
139
197
|
const symbols = new Map();
|
|
140
|
-
for (const
|
|
141
|
-
|
|
198
|
+
for (const statement of sourceFile.compilerNode.statements || []) {
|
|
199
|
+
if (ts.isExportDeclaration(statement)) {
|
|
200
|
+
addNamedExportSymbols(symbols, statement.exportClause);
|
|
201
|
+
continue;
|
|
202
|
+
}
|
|
203
|
+
if (ts.isExportAssignment(statement)) {
|
|
142
204
|
addSymbol(symbols, {
|
|
143
|
-
|
|
144
|
-
|
|
205
|
+
kind: "default",
|
|
206
|
+
name: "default"
|
|
145
207
|
});
|
|
208
|
+
continue;
|
|
209
|
+
}
|
|
210
|
+
if (ts.isVariableStatement(statement)) {
|
|
211
|
+
addVariableStatementExports(symbols, statement);
|
|
212
|
+
continue;
|
|
213
|
+
}
|
|
214
|
+
if (!compilerNodeHasModifier(statement, ts.SyntaxKind.ExportKeyword)) {
|
|
215
|
+
continue;
|
|
216
|
+
}
|
|
217
|
+
if (ts.isFunctionDeclaration(statement)) {
|
|
218
|
+
addExportedDeclarationSymbol(symbols, statement, "function");
|
|
219
|
+
} else if (ts.isClassDeclaration(statement)) {
|
|
220
|
+
addExportedDeclarationSymbol(symbols, statement, "class");
|
|
221
|
+
} else if (ts.isInterfaceDeclaration(statement)) {
|
|
222
|
+
addExportedDeclarationSymbol(symbols, statement, "interface");
|
|
223
|
+
} else if (ts.isTypeAliasDeclaration(statement)) {
|
|
224
|
+
addExportedDeclarationSymbol(symbols, statement, "type");
|
|
225
|
+
} else if (ts.isEnumDeclaration(statement)) {
|
|
226
|
+
addExportedDeclarationSymbol(symbols, statement, "enum");
|
|
146
227
|
}
|
|
147
228
|
}
|
|
148
229
|
|
|
@@ -277,11 +358,75 @@ function flattenPackageExports(exportsField) {
|
|
|
277
358
|
});
|
|
278
359
|
}
|
|
279
360
|
|
|
280
|
-
async function
|
|
361
|
+
async function packageExportTargetRecords(packageRoot, exportTargets = []) {
|
|
362
|
+
const records = [];
|
|
363
|
+
for (const exportTarget of exportTargets) {
|
|
364
|
+
const normalizedTarget = exportTarget.target.replace(/^\.\//u, "");
|
|
365
|
+
const targetPath = path.join(packageRoot, normalizedTarget);
|
|
366
|
+
const ext = path.extname(targetPath);
|
|
367
|
+
if (!CODE_EXTENSIONS.has(ext) || !await pathExists(targetPath)) {
|
|
368
|
+
continue;
|
|
369
|
+
}
|
|
370
|
+
records.push({
|
|
371
|
+
absolutePath: targetPath,
|
|
372
|
+
hash: await readFileHash(targetPath),
|
|
373
|
+
subpath: exportTarget.subpath,
|
|
374
|
+
target: normalizedTarget.split(path.sep).join("/")
|
|
375
|
+
});
|
|
376
|
+
}
|
|
377
|
+
return records;
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
function packageExportFingerprint({
|
|
381
|
+
installedPackageJson = {},
|
|
382
|
+
packageName = "",
|
|
383
|
+
targetRecords = []
|
|
384
|
+
} = {}) {
|
|
385
|
+
return createHash("sha256")
|
|
386
|
+
.update(JSON.stringify({
|
|
387
|
+
name: packageName,
|
|
388
|
+
version: installedPackageJson.version || "",
|
|
389
|
+
targets: targetRecords.map((record) => ({
|
|
390
|
+
hash: record.hash,
|
|
391
|
+
subpath: record.subpath,
|
|
392
|
+
target: record.target
|
|
393
|
+
}))
|
|
394
|
+
}))
|
|
395
|
+
.digest("hex");
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
function cachedPackageExports(previousPackagesByName = new Map(), packageName = "", fingerprint = "") {
|
|
399
|
+
const previous = previousPackagesByName.get(packageName);
|
|
400
|
+
if (
|
|
401
|
+
!previous ||
|
|
402
|
+
previous.installed !== true ||
|
|
403
|
+
previous.fingerprint !== fingerprint ||
|
|
404
|
+
!Array.isArray(previous.exports)
|
|
405
|
+
) {
|
|
406
|
+
return null;
|
|
407
|
+
}
|
|
408
|
+
return {
|
|
409
|
+
name: previous.name,
|
|
410
|
+
version: previous.version || "",
|
|
411
|
+
fingerprint: previous.fingerprint,
|
|
412
|
+
installed: true,
|
|
413
|
+
exports: previous.exports
|
|
414
|
+
};
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
function previousPackageMap(previousMap = null) {
|
|
418
|
+
return new Map(
|
|
419
|
+
(Array.isArray(previousMap?.jskitPackages) ? previousMap.jskitPackages : [])
|
|
420
|
+
.map((packageEntry) => [packageEntry.name, packageEntry])
|
|
421
|
+
.filter(([name]) => name)
|
|
422
|
+
);
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
async function collectJskitPackageExports(targetRoot, packageJson = {}, previousMap = null) {
|
|
281
426
|
const packageNames = normalizePackageDependencies(packageJson)
|
|
282
427
|
.filter((name) => name.startsWith("@jskit-ai/"));
|
|
283
428
|
const packages = [];
|
|
284
|
-
const
|
|
429
|
+
const previousPackagesByName = previousPackageMap(previousMap);
|
|
285
430
|
|
|
286
431
|
for (const packageName of packageNames) {
|
|
287
432
|
const packageRoot = path.join(targetRoot, "node_modules", ...packageName.split("/"));
|
|
@@ -297,21 +442,28 @@ async function collectJskitPackageExports(targetRoot, packageJson = {}) {
|
|
|
297
442
|
|
|
298
443
|
const installedPackageJson = await readJsonFile(packageJsonPath);
|
|
299
444
|
const exportTargets = flattenPackageExports(installedPackageJson.exports || {});
|
|
445
|
+
const targetRecords = await packageExportTargetRecords(packageRoot, exportTargets);
|
|
446
|
+
const fingerprint = packageExportFingerprint({
|
|
447
|
+
installedPackageJson,
|
|
448
|
+
packageName,
|
|
449
|
+
targetRecords
|
|
450
|
+
});
|
|
451
|
+
const cachedEntry = cachedPackageExports(previousPackagesByName, packageName, fingerprint);
|
|
452
|
+
if (cachedEntry) {
|
|
453
|
+
packages.push(cachedEntry);
|
|
454
|
+
continue;
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
const project = createExportAnalysisProject();
|
|
300
458
|
const exports = [];
|
|
301
|
-
for (const
|
|
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
|
-
}
|
|
459
|
+
for (const targetRecord of targetRecords) {
|
|
308
460
|
const sourceFile = await addCodeFileToProject(project, {
|
|
309
|
-
absolutePath:
|
|
310
|
-
relativePath:
|
|
461
|
+
absolutePath: targetRecord.absolutePath,
|
|
462
|
+
relativePath: targetRecord.target
|
|
311
463
|
});
|
|
312
464
|
exports.push({
|
|
313
|
-
subpath:
|
|
314
|
-
target:
|
|
465
|
+
subpath: targetRecord.subpath,
|
|
466
|
+
target: targetRecord.target,
|
|
315
467
|
exports: sourceFile ? extractExportedSymbols(sourceFile) : []
|
|
316
468
|
});
|
|
317
469
|
}
|
|
@@ -319,6 +471,7 @@ async function collectJskitPackageExports(targetRoot, packageJson = {}) {
|
|
|
319
471
|
packages.push({
|
|
320
472
|
name: packageName,
|
|
321
473
|
version: installedPackageJson.version || "",
|
|
474
|
+
fingerprint,
|
|
322
475
|
installed: true,
|
|
323
476
|
exports
|
|
324
477
|
});
|
|
@@ -383,7 +536,7 @@ function renderHelperMapMarkdown(map) {
|
|
|
383
536
|
return `${lines.join("\n").replace(/\n{3,}/gu, "\n\n").trimEnd()}\n`;
|
|
384
537
|
}
|
|
385
538
|
|
|
386
|
-
async function buildHelperMap({ targetRoot }) {
|
|
539
|
+
async function buildHelperMap({ targetRoot, previousMap = null }) {
|
|
387
540
|
const packageJsonPath = path.join(targetRoot, "package.json");
|
|
388
541
|
const packageJson = await readJsonFile(packageJsonPath);
|
|
389
542
|
const map = {
|
|
@@ -396,7 +549,7 @@ async function buildHelperMap({ targetRoot }) {
|
|
|
396
549
|
app: {
|
|
397
550
|
files: await collectAppExports(targetRoot)
|
|
398
551
|
},
|
|
399
|
-
jskitPackages: await collectJskitPackageExports(targetRoot, packageJson)
|
|
552
|
+
jskitPackages: await collectJskitPackageExports(targetRoot, packageJson, previousMap)
|
|
400
553
|
};
|
|
401
554
|
return {
|
|
402
555
|
ok: true,
|
|
@@ -430,12 +583,17 @@ async function readHelperMap({ targetRoot }) {
|
|
|
430
583
|
}
|
|
431
584
|
|
|
432
585
|
async function updateHelperMap({ targetRoot }) {
|
|
433
|
-
const
|
|
586
|
+
const helperMapJsonPath = path.join(targetRoot, HELPER_MAP_JSON_RELATIVE_PATH);
|
|
587
|
+
const currentJson = await pathExists(helperMapJsonPath)
|
|
588
|
+
? await readFile(helperMapJsonPath, "utf8")
|
|
589
|
+
: "";
|
|
590
|
+
const previousMap = currentJson ? JSON.parse(currentJson) : null;
|
|
591
|
+
const payload = await buildHelperMap({
|
|
592
|
+
previousMap,
|
|
593
|
+
targetRoot
|
|
594
|
+
});
|
|
434
595
|
const markdown = renderHelperMapMarkdown(payload.map);
|
|
435
596
|
const json = `${JSON.stringify(payload.map, null, 2)}\n`;
|
|
436
|
-
const currentJson = await pathExists(payload.helperMapJsonPath)
|
|
437
|
-
? await readFile(payload.helperMapJsonPath, "utf8")
|
|
438
|
-
: "";
|
|
439
597
|
const currentMarkdown = await pathExists(payload.helperMapMarkdownPath)
|
|
440
598
|
? await readFile(payload.helperMapMarkdownPath, "utf8")
|
|
441
599
|
: "";
|
package/src/server/index.js
CHANGED
|
@@ -4,7 +4,7 @@ App brief:
|
|
|
4
4
|
|
|
5
5
|
{{app_brief}}
|
|
6
6
|
|
|
7
|
-
Produce a concise but useful app-level blueprint. It must describe product intent, platform choices, and architectural boundaries. It must not become an implementation
|
|
7
|
+
Produce a concise but useful app-level blueprint. It must describe product intent, platform choices, and architectural boundaries. It must not become an implementation task list for one issue.
|
|
8
8
|
|
|
9
9
|
Before writing the blueprint, classify the app state if local files are available:
|
|
10
10
|
|