@jskit-ai/jskit-cli 0.2.79 → 0.2.80
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 +126 -0
- package/src/server/commandHandlers/blueprint.js +151 -0
- package/src/server/commandHandlers/session.js +237 -0
- package/src/server/core/argParser.js +2 -2
- package/src/server/core/commandCatalog.js +83 -0
- package/src/server/core/createCommandHandlers.js +7 -1
- package/src/server/index.js +2 -0
- package/src/server/sessionRuntime/constants.js +296 -0
- package/src/server/sessionRuntime/io.js +97 -0
- package/src/server/sessionRuntime/paths.js +165 -0
- package/src/server/sessionRuntime/preconditions.js +372 -0
- package/src/server/sessionRuntime/promptRenderer.js +41 -0
- package/src/server/sessionRuntime/prompts/app_blueprint.md +28 -0
- package/src/server/sessionRuntime/prompts/doctor_failure.md +15 -0
- package/src/server/sessionRuntime/prompts/final_comment.md +8 -0
- package/src/server/sessionRuntime/prompts/implement_issue.md +25 -0
- package/src/server/sessionRuntime/prompts/new_issue.md +13 -0
- package/src/server/sessionRuntime/prompts/pr_failure.md +15 -0
- package/src/server/sessionRuntime/prompts/review_changes.md +22 -0
- package/src/server/sessionRuntime/prompts/user_check.md +9 -0
- package/src/server/sessionRuntime/responses.js +315 -0
- package/src/server/sessionRuntime.js +927 -0
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.80",
|
|
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.79",
|
|
24
|
+
"@jskit-ai/kernel": "0.1.71",
|
|
25
|
+
"@jskit-ai/shell-web": "0.1.70"
|
|
26
26
|
},
|
|
27
27
|
"engines": {
|
|
28
28
|
"node": ">=20 <23"
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import { mkdir, readFile } from "node:fs/promises";
|
|
2
|
+
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";
|
|
15
|
+
|
|
16
|
+
const APP_BLUEPRINT_RELATIVE_PATH = ".jskit/APP_BLUEPRINT.md";
|
|
17
|
+
const APP_PROMPT_OVERRIDE_RELATIVE_ROOT = ".jskit/prompts";
|
|
18
|
+
|
|
19
|
+
function resolveAppBlueprintPaths(targetRoot = process.cwd()) {
|
|
20
|
+
const normalizedTargetRoot = path.resolve(normalizeText(targetRoot) || process.cwd());
|
|
21
|
+
return Object.freeze({
|
|
22
|
+
appBlueprintPath: path.join(normalizedTargetRoot, APP_BLUEPRINT_RELATIVE_PATH),
|
|
23
|
+
promptOverrideRoot: path.join(normalizedTargetRoot, APP_PROMPT_OVERRIDE_RELATIVE_ROOT),
|
|
24
|
+
targetRoot: normalizedTargetRoot
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function extractAppBlueprintText(value = "") {
|
|
29
|
+
const text = normalizeText(value);
|
|
30
|
+
const match = /\[app_blueprint\]([\s\S]*?)\[\/app_blueprint\]/u.exec(text);
|
|
31
|
+
return normalizeText(match ? match[1] : text);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
async function readAppPromptTemplate(paths, templateName) {
|
|
35
|
+
const normalizedName = normalizeText(templateName);
|
|
36
|
+
const overridePath = path.join(paths.promptOverrideRoot, normalizedName);
|
|
37
|
+
if (await fileExists(overridePath)) {
|
|
38
|
+
return readTextIfExists(overridePath);
|
|
39
|
+
}
|
|
40
|
+
return readTextIfExists(path.join(PROMPT_DIRECTORY, normalizedName));
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
async function renderAppBlueprintPrompt({ targetRoot = process.cwd(), appBrief = "" } = {}) {
|
|
44
|
+
const paths = resolveAppBlueprintPaths(targetRoot);
|
|
45
|
+
const normalizedBrief = normalizeText(appBrief);
|
|
46
|
+
if (!normalizedBrief) {
|
|
47
|
+
return {
|
|
48
|
+
ok: false,
|
|
49
|
+
appBlueprintPath: paths.appBlueprintPath,
|
|
50
|
+
errors: [
|
|
51
|
+
{
|
|
52
|
+
code: "app_brief_required",
|
|
53
|
+
message: "jskit blueprint prompt requires --brief, --brief-file, or --brief -.",
|
|
54
|
+
repairCommand: "jskit blueprint prompt --brief \"<what app are we building>\""
|
|
55
|
+
}
|
|
56
|
+
],
|
|
57
|
+
prompt: ""
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
const template = await readAppPromptTemplate(paths, "app_blueprint.md");
|
|
61
|
+
return {
|
|
62
|
+
ok: true,
|
|
63
|
+
appBlueprintPath: paths.appBlueprintPath,
|
|
64
|
+
errors: [],
|
|
65
|
+
prompt: renderTemplate(template, {
|
|
66
|
+
app_brief: normalizedBrief
|
|
67
|
+
}).trim()
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
async function readAppBlueprint({ targetRoot = process.cwd() } = {}) {
|
|
72
|
+
const paths = resolveAppBlueprintPaths(targetRoot);
|
|
73
|
+
const blueprintText = await readTextIfExists(paths.appBlueprintPath);
|
|
74
|
+
return {
|
|
75
|
+
ok: true,
|
|
76
|
+
appBlueprintPath: paths.appBlueprintPath,
|
|
77
|
+
blueprintText: blueprintText.trim(),
|
|
78
|
+
exists: Boolean(blueprintText.trim()),
|
|
79
|
+
errors: []
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
async function writeAppBlueprint({ targetRoot = process.cwd(), appBlueprint = "" } = {}) {
|
|
84
|
+
const paths = resolveAppBlueprintPaths(targetRoot);
|
|
85
|
+
const blueprintText = extractAppBlueprintText(appBlueprint);
|
|
86
|
+
if (!blueprintText) {
|
|
87
|
+
return {
|
|
88
|
+
ok: false,
|
|
89
|
+
appBlueprintPath: paths.appBlueprintPath,
|
|
90
|
+
blueprintText: "",
|
|
91
|
+
exists: false,
|
|
92
|
+
errors: [
|
|
93
|
+
{
|
|
94
|
+
code: "app_blueprint_required",
|
|
95
|
+
message: "jskit blueprint set requires --blueprint, --blueprint-file, or --blueprint -.",
|
|
96
|
+
repairCommand: "jskit blueprint set --blueprint -"
|
|
97
|
+
}
|
|
98
|
+
]
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
await mkdir(path.dirname(paths.appBlueprintPath), { recursive: true });
|
|
102
|
+
await writeTextFile(paths.appBlueprintPath, blueprintText);
|
|
103
|
+
return {
|
|
104
|
+
ok: true,
|
|
105
|
+
appBlueprintPath: paths.appBlueprintPath,
|
|
106
|
+
blueprintText,
|
|
107
|
+
exists: true,
|
|
108
|
+
errors: []
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
async function readTextInputFile(cwd, inputPath) {
|
|
113
|
+
const resolvedPath = path.resolve(cwd, normalizeText(inputPath));
|
|
114
|
+
return readFile(resolvedPath, "utf8");
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
export {
|
|
118
|
+
APP_BLUEPRINT_RELATIVE_PATH,
|
|
119
|
+
APP_PROMPT_OVERRIDE_RELATIVE_ROOT,
|
|
120
|
+
extractAppBlueprintText,
|
|
121
|
+
readAppBlueprint,
|
|
122
|
+
readTextInputFile,
|
|
123
|
+
renderAppBlueprintPrompt,
|
|
124
|
+
resolveAppBlueprintPaths,
|
|
125
|
+
writeAppBlueprint
|
|
126
|
+
};
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
import {
|
|
2
|
+
readAppBlueprint,
|
|
3
|
+
readTextInputFile,
|
|
4
|
+
renderAppBlueprintPrompt,
|
|
5
|
+
writeAppBlueprint
|
|
6
|
+
} from "../appBlueprint.js";
|
|
7
|
+
|
|
8
|
+
function writeJson(stdout, payload) {
|
|
9
|
+
stdout.write(`${JSON.stringify(payload, null, 2)}\n`);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function writeBlueprintText(stdout, payload) {
|
|
13
|
+
if (payload.prompt) {
|
|
14
|
+
stdout.write(`${payload.prompt}\n`);
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
17
|
+
if (payload.blueprintText) {
|
|
18
|
+
stdout.write(`${payload.blueprintText}\n`);
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
stdout.write(`No app blueprint set at ${payload.appBlueprintPath}.\n`);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
async function readStream(stream) {
|
|
25
|
+
const chunks = [];
|
|
26
|
+
for await (const chunk of stream) {
|
|
27
|
+
chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(String(chunk)));
|
|
28
|
+
}
|
|
29
|
+
return Buffer.concat(chunks).toString("utf8");
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
async function resolveTextInput({
|
|
33
|
+
cwd,
|
|
34
|
+
fileOption,
|
|
35
|
+
inlineOptions = {},
|
|
36
|
+
io = {},
|
|
37
|
+
stdinOption = "-",
|
|
38
|
+
textOption
|
|
39
|
+
}) {
|
|
40
|
+
if (Object.hasOwn(inlineOptions, fileOption)) {
|
|
41
|
+
const inputFile = String(inlineOptions[fileOption] || "").trim();
|
|
42
|
+
return inputFile ? readTextInputFile(cwd, inputFile) : "";
|
|
43
|
+
}
|
|
44
|
+
if (Object.hasOwn(inlineOptions, textOption)) {
|
|
45
|
+
const textValue = String(inlineOptions[textOption] ?? "");
|
|
46
|
+
return textValue === stdinOption ? readStream(io.stdin) : textValue;
|
|
47
|
+
}
|
|
48
|
+
return "";
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function createBlueprintCommands(ctx = {}) {
|
|
52
|
+
const { resolveAppRootFromCwd } = ctx;
|
|
53
|
+
|
|
54
|
+
async function commandBlueprint({
|
|
55
|
+
positional = [],
|
|
56
|
+
options = {},
|
|
57
|
+
cwd,
|
|
58
|
+
stdout,
|
|
59
|
+
io = {}
|
|
60
|
+
} = {}) {
|
|
61
|
+
const appRoot = await resolveAppRootFromCwd(cwd);
|
|
62
|
+
const inlineOptions = options.inlineOptions || {};
|
|
63
|
+
const subcommand = String(positional[0] || "").trim();
|
|
64
|
+
let payload;
|
|
65
|
+
|
|
66
|
+
try {
|
|
67
|
+
if (positional.length > 1) {
|
|
68
|
+
payload = {
|
|
69
|
+
ok: false,
|
|
70
|
+
appBlueprintPath: "",
|
|
71
|
+
errors: [
|
|
72
|
+
{
|
|
73
|
+
code: "unexpected_blueprint_argument",
|
|
74
|
+
message: `Unexpected blueprint argument: ${positional.slice(1).join(" ")}`,
|
|
75
|
+
repairCommand: "jskit blueprint"
|
|
76
|
+
}
|
|
77
|
+
]
|
|
78
|
+
};
|
|
79
|
+
} else if (!subcommand) {
|
|
80
|
+
payload = await readAppBlueprint({ targetRoot: appRoot });
|
|
81
|
+
} else if (subcommand === "prompt") {
|
|
82
|
+
const appBrief = await resolveTextInput({
|
|
83
|
+
cwd,
|
|
84
|
+
fileOption: "brief-file",
|
|
85
|
+
inlineOptions,
|
|
86
|
+
io,
|
|
87
|
+
textOption: "brief"
|
|
88
|
+
});
|
|
89
|
+
payload = await renderAppBlueprintPrompt({
|
|
90
|
+
targetRoot: appRoot,
|
|
91
|
+
appBrief
|
|
92
|
+
});
|
|
93
|
+
} else if (subcommand === "set") {
|
|
94
|
+
const appBlueprint = await resolveTextInput({
|
|
95
|
+
cwd,
|
|
96
|
+
fileOption: "blueprint-file",
|
|
97
|
+
inlineOptions,
|
|
98
|
+
io,
|
|
99
|
+
textOption: "blueprint"
|
|
100
|
+
});
|
|
101
|
+
payload = await writeAppBlueprint({
|
|
102
|
+
targetRoot: appRoot,
|
|
103
|
+
appBlueprint
|
|
104
|
+
});
|
|
105
|
+
} else {
|
|
106
|
+
payload = {
|
|
107
|
+
ok: false,
|
|
108
|
+
appBlueprintPath: "",
|
|
109
|
+
errors: [
|
|
110
|
+
{
|
|
111
|
+
code: "unknown_blueprint_subcommand",
|
|
112
|
+
message: `Unknown blueprint subcommand: ${subcommand}`,
|
|
113
|
+
repairCommand: "jskit blueprint"
|
|
114
|
+
}
|
|
115
|
+
]
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
} catch (error) {
|
|
119
|
+
payload = {
|
|
120
|
+
ok: false,
|
|
121
|
+
appBlueprintPath: "",
|
|
122
|
+
errors: [
|
|
123
|
+
{
|
|
124
|
+
code: "blueprint_input_read_failed",
|
|
125
|
+
message: String(error?.message || error),
|
|
126
|
+
repairCommand: "jskit blueprint"
|
|
127
|
+
}
|
|
128
|
+
]
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
if (options.json) {
|
|
133
|
+
writeJson(stdout, payload);
|
|
134
|
+
} else if (payload.ok === false) {
|
|
135
|
+
for (const error of payload.errors || []) {
|
|
136
|
+
stdout.write(`[${error.code}] ${error.message}\n`);
|
|
137
|
+
if (error.repairCommand) {
|
|
138
|
+
stdout.write(`Repair: ${error.repairCommand}\n`);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
} else {
|
|
142
|
+
writeBlueprintText(stdout, payload);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
return payload.ok === false ? 1 : 0;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
return { commandBlueprint };
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
export { createBlueprintCommands };
|
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
import { readFile } from "node:fs/promises";
|
|
2
|
+
import {
|
|
3
|
+
abandonSession,
|
|
4
|
+
adoptCodexThreadId,
|
|
5
|
+
buildSessionErrorResponse,
|
|
6
|
+
createSession,
|
|
7
|
+
inspectSessionDetails,
|
|
8
|
+
listSessions,
|
|
9
|
+
runSessionStep
|
|
10
|
+
} from "../sessionRuntime.js";
|
|
11
|
+
|
|
12
|
+
function writeJson(stdout, payload) {
|
|
13
|
+
stdout.write(`${JSON.stringify(payload, null, 2)}\n`);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function writeSessionText(stdout, payload) {
|
|
17
|
+
if (payload.sessions) {
|
|
18
|
+
stdout.write("JSKIT sessions\n");
|
|
19
|
+
if (payload.sessions.length < 1) {
|
|
20
|
+
stdout.write("No sessions found.\n");
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
for (const session of payload.sessions) {
|
|
24
|
+
stdout.write(`- ${session.sessionId} ${session.status} ${session.currentStep || "done"}\n`);
|
|
25
|
+
}
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
stdout.write(`Session: ${payload.sessionId || "unknown"}\n`);
|
|
30
|
+
stdout.write(`Status: ${payload.status || "unknown"}\n`);
|
|
31
|
+
stdout.write(`Current step: ${payload.currentStep || "done"}\n`);
|
|
32
|
+
if (payload.issueUrl) {
|
|
33
|
+
stdout.write(`Issue: ${payload.issueUrl}\n`);
|
|
34
|
+
}
|
|
35
|
+
if (payload.prUrl) {
|
|
36
|
+
stdout.write(`PR: ${payload.prUrl}\n`);
|
|
37
|
+
}
|
|
38
|
+
if (payload.branch) {
|
|
39
|
+
stdout.write(`Branch: ${payload.branch}\n`);
|
|
40
|
+
}
|
|
41
|
+
if (payload.worktree) {
|
|
42
|
+
stdout.write(`Worktree: ${payload.worktree}\n`);
|
|
43
|
+
}
|
|
44
|
+
if (payload.completedSteps?.length) {
|
|
45
|
+
stdout.write("Done steps:\n");
|
|
46
|
+
for (const step of payload.completedSteps) {
|
|
47
|
+
stdout.write(`- ${step}\n`);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
if (payload.prompt) {
|
|
51
|
+
stdout.write("\n");
|
|
52
|
+
stdout.write(payload.prompt);
|
|
53
|
+
stdout.write("\n");
|
|
54
|
+
}
|
|
55
|
+
if (payload.errors?.length) {
|
|
56
|
+
stdout.write("Errors:\n");
|
|
57
|
+
for (const error of payload.errors) {
|
|
58
|
+
stdout.write(`- [${error.code}] ${error.message}\n`);
|
|
59
|
+
if (error.repairCommand) {
|
|
60
|
+
stdout.write(` Repair: ${error.repairCommand}\n`);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
if (payload.nextCommand) {
|
|
65
|
+
stdout.write(`Next: ${payload.nextCommand}\n`);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
async function readStream(stream) {
|
|
70
|
+
const chunks = [];
|
|
71
|
+
for await (const chunk of stream) {
|
|
72
|
+
chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(String(chunk)));
|
|
73
|
+
}
|
|
74
|
+
return Buffer.concat(chunks).toString("utf8");
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function resolveInputFilePath(cwd, filePath) {
|
|
78
|
+
return filePath.startsWith("/") ? filePath : `${cwd}/${filePath}`;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
async function resolveTextInput({
|
|
82
|
+
codePrefix,
|
|
83
|
+
fileOption,
|
|
84
|
+
inlineOptions = {},
|
|
85
|
+
io = {},
|
|
86
|
+
repairCommand,
|
|
87
|
+
cwd,
|
|
88
|
+
stdinOption,
|
|
89
|
+
textOption,
|
|
90
|
+
sessionId
|
|
91
|
+
}) {
|
|
92
|
+
if (Object.hasOwn(inlineOptions, fileOption)) {
|
|
93
|
+
const inputFile = String(inlineOptions[fileOption] || "").trim();
|
|
94
|
+
if (!inputFile) {
|
|
95
|
+
return { ok: true, value: "" };
|
|
96
|
+
}
|
|
97
|
+
const resolvedInputFile = resolveInputFilePath(cwd, inputFile);
|
|
98
|
+
try {
|
|
99
|
+
return {
|
|
100
|
+
ok: true,
|
|
101
|
+
value: await readFile(resolvedInputFile, "utf8")
|
|
102
|
+
};
|
|
103
|
+
} catch (error) {
|
|
104
|
+
return {
|
|
105
|
+
ok: false,
|
|
106
|
+
payload: buildSessionErrorResponse({
|
|
107
|
+
targetRoot: cwd,
|
|
108
|
+
sessionId,
|
|
109
|
+
code: `${codePrefix}_file_read_failed`,
|
|
110
|
+
message: `Could not read ${codePrefix.replaceAll("_", " ")} file ${resolvedInputFile}: ${error.message}`,
|
|
111
|
+
repairCommand
|
|
112
|
+
})
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
if (Object.hasOwn(inlineOptions, textOption)) {
|
|
117
|
+
const textValue = String(inlineOptions[textOption] ?? "");
|
|
118
|
+
if (textValue === stdinOption) {
|
|
119
|
+
return {
|
|
120
|
+
ok: true,
|
|
121
|
+
value: await readStream(io.stdin)
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
return {
|
|
125
|
+
ok: true,
|
|
126
|
+
value: textValue
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
return { ok: true, value: "" };
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
async function resolveStepInputs({
|
|
133
|
+
inlineOptions = {},
|
|
134
|
+
io = {},
|
|
135
|
+
cwd,
|
|
136
|
+
sessionId
|
|
137
|
+
}) {
|
|
138
|
+
const issue = await resolveTextInput({
|
|
139
|
+
codePrefix: "issue",
|
|
140
|
+
fileOption: "issue-file",
|
|
141
|
+
inlineOptions,
|
|
142
|
+
io,
|
|
143
|
+
repairCommand: `jskit session ${sessionId} step --issue -`,
|
|
144
|
+
cwd,
|
|
145
|
+
sessionId,
|
|
146
|
+
stdinOption: "-",
|
|
147
|
+
textOption: "issue"
|
|
148
|
+
});
|
|
149
|
+
if (issue.ok === false) {
|
|
150
|
+
return issue;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
return {
|
|
154
|
+
issue: issue.value,
|
|
155
|
+
ok: true
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
function normalizeStepOptions(inlineOptions = {}) {
|
|
160
|
+
return {
|
|
161
|
+
...inlineOptions,
|
|
162
|
+
prompt: inlineOptions.prompt,
|
|
163
|
+
userCheck: inlineOptions["user-check"] || inlineOptions.userCheck
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
function createSessionCommands() {
|
|
168
|
+
return {
|
|
169
|
+
async commandSession({
|
|
170
|
+
positional = [],
|
|
171
|
+
options = {},
|
|
172
|
+
cwd,
|
|
173
|
+
stdout,
|
|
174
|
+
io = {}
|
|
175
|
+
} = {}) {
|
|
176
|
+
const [first, second] = positional;
|
|
177
|
+
const inlineOptions = options.inlineOptions || {};
|
|
178
|
+
let payload;
|
|
179
|
+
|
|
180
|
+
if (!first) {
|
|
181
|
+
payload = await listSessions({ targetRoot: cwd });
|
|
182
|
+
} else if (first === "create") {
|
|
183
|
+
payload = await createSession({ targetRoot: cwd });
|
|
184
|
+
} else if (second === "step") {
|
|
185
|
+
const stepInputs = await resolveStepInputs({
|
|
186
|
+
inlineOptions,
|
|
187
|
+
io,
|
|
188
|
+
cwd,
|
|
189
|
+
sessionId: first
|
|
190
|
+
});
|
|
191
|
+
payload = stepInputs.ok === false
|
|
192
|
+
? stepInputs.payload
|
|
193
|
+
: await runSessionStep({
|
|
194
|
+
targetRoot: cwd,
|
|
195
|
+
sessionId: first,
|
|
196
|
+
options: {
|
|
197
|
+
...normalizeStepOptions(inlineOptions),
|
|
198
|
+
issue: stepInputs.issue
|
|
199
|
+
}
|
|
200
|
+
});
|
|
201
|
+
} else if (second === "abandon") {
|
|
202
|
+
payload = await abandonSession({
|
|
203
|
+
targetRoot: cwd,
|
|
204
|
+
sessionId: first
|
|
205
|
+
});
|
|
206
|
+
} else if (second === "adopt-codex-thread") {
|
|
207
|
+
payload = await adoptCodexThreadId({
|
|
208
|
+
targetRoot: cwd,
|
|
209
|
+
sessionId: first,
|
|
210
|
+
codexThreadId: inlineOptions["codex-thread-id"] || inlineOptions.codexThreadId
|
|
211
|
+
});
|
|
212
|
+
} else if (!second) {
|
|
213
|
+
payload = await inspectSessionDetails({
|
|
214
|
+
targetRoot: cwd,
|
|
215
|
+
sessionId: first
|
|
216
|
+
});
|
|
217
|
+
} else {
|
|
218
|
+
payload = buildSessionErrorResponse({
|
|
219
|
+
targetRoot: cwd,
|
|
220
|
+
sessionId: first,
|
|
221
|
+
code: "unknown_session_subcommand",
|
|
222
|
+
message: `Unknown session subcommand: ${second}`,
|
|
223
|
+
repairCommand: `jskit session ${first}`
|
|
224
|
+
});
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
if (options.json) {
|
|
228
|
+
writeJson(stdout, payload);
|
|
229
|
+
} else {
|
|
230
|
+
writeSessionText(stdout, payload);
|
|
231
|
+
}
|
|
232
|
+
return payload.ok === false ? 1 : 0;
|
|
233
|
+
}
|
|
234
|
+
};
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
export { createSessionCommands };
|
|
@@ -143,7 +143,7 @@ function parseArgs(argv, { createCliError } = {}) {
|
|
|
143
143
|
} else {
|
|
144
144
|
const hasNextStringToken = typeof args[0] === "string";
|
|
145
145
|
const nextToken = hasNextStringToken ? String(args[0]) : "";
|
|
146
|
-
if (hasNextStringToken && !nextToken.startsWith("-")) {
|
|
146
|
+
if (hasNextStringToken && (!nextToken.startsWith("-") || nextToken === "-")) {
|
|
147
147
|
optionValueRaw = args.shift();
|
|
148
148
|
}
|
|
149
149
|
}
|
|
@@ -153,7 +153,7 @@ function parseArgs(argv, { createCliError } = {}) {
|
|
|
153
153
|
}
|
|
154
154
|
if (typeof optionValueRaw === "string") {
|
|
155
155
|
const optionValue = optionValueRaw.trim();
|
|
156
|
-
if (!hasInlineValue && optionValue.startsWith("-")) {
|
|
156
|
+
if (!hasInlineValue && optionValue.startsWith("-") && optionValue !== "-") {
|
|
157
157
|
throw createCliError(`--${optionName} requires a value.`, { showUsage: true });
|
|
158
158
|
}
|
|
159
159
|
options.inlineOptions[optionName] = optionValue;
|
|
@@ -198,6 +198,89 @@ const COMMAND_DESCRIPTORS = Object.freeze({
|
|
|
198
198
|
allowedValueOptionNames: Object.freeze([]),
|
|
199
199
|
canDelegateInlineOptions: (positional = []) => Array.isArray(positional) && positional.length > 0
|
|
200
200
|
}),
|
|
201
|
+
session: Object.freeze({
|
|
202
|
+
command: "session",
|
|
203
|
+
aliases: Object.freeze([]),
|
|
204
|
+
showInOverview: true,
|
|
205
|
+
summary: "Run file-backed JSKIT issue sessions.",
|
|
206
|
+
minimalUse: "jskit session create",
|
|
207
|
+
parameters: Object.freeze([
|
|
208
|
+
Object.freeze({
|
|
209
|
+
name: "create | <sessionId>",
|
|
210
|
+
description: "Create a session, inspect a session, or run a session subcommand."
|
|
211
|
+
}),
|
|
212
|
+
Object.freeze({
|
|
213
|
+
name: "[step|abandon|adopt-codex-thread]",
|
|
214
|
+
description: "Run the next step, abandon a session, or attach a Codex thread id."
|
|
215
|
+
})
|
|
216
|
+
]),
|
|
217
|
+
defaults: Object.freeze([
|
|
218
|
+
"Active session state lives in .jskit/sessions/active/<session_id> under the current target app.",
|
|
219
|
+
"Session worktrees live in .jskit/sessions/worktrees/<session_id>.",
|
|
220
|
+
"The session id is timestamp-based and is the primary key.",
|
|
221
|
+
"Use --json for the stable machine-readable contract consumed by JSKIT AI Studio.",
|
|
222
|
+
"Use --issue - to read approved issue text from stdin."
|
|
223
|
+
]),
|
|
224
|
+
examples: Object.freeze([
|
|
225
|
+
Object.freeze({
|
|
226
|
+
label: "Manual CLI flow",
|
|
227
|
+
lines: Object.freeze([
|
|
228
|
+
"jskit session create",
|
|
229
|
+
"jskit session 2026-05-11_21-42-08 step",
|
|
230
|
+
"jskit session 2026-05-11_21-42-08 step --prompt \"Fix the customer filters\"",
|
|
231
|
+
"jskit session 2026-05-11_21-42-08 step --issue -"
|
|
232
|
+
])
|
|
233
|
+
})
|
|
234
|
+
]),
|
|
235
|
+
fullUse:
|
|
236
|
+
"jskit session [create|<sessionId>] [step|abandon|adopt-codex-thread] [--prompt <text>] [--issue <text>|--issue-file <path>] [--user-check <passed|failed>] [--codex-thread-id <id>] [--json]",
|
|
237
|
+
showHelpOnBareInvocation: false,
|
|
238
|
+
handlerName: "commandSession",
|
|
239
|
+
allowedFlagKeys: Object.freeze(["json"]),
|
|
240
|
+
inlineOptionMode: "delegate",
|
|
241
|
+
allowedValueOptionNames: Object.freeze([]),
|
|
242
|
+
canDelegateInlineOptions: (positional = []) => Array.isArray(positional) && positional.length > 0
|
|
243
|
+
}),
|
|
244
|
+
blueprint: Object.freeze({
|
|
245
|
+
command: "blueprint",
|
|
246
|
+
aliases: Object.freeze([]),
|
|
247
|
+
showInOverview: true,
|
|
248
|
+
summary: "Read, prompt, or set the app-level JSKIT blueprint.",
|
|
249
|
+
minimalUse: "jskit blueprint",
|
|
250
|
+
parameters: Object.freeze([
|
|
251
|
+
Object.freeze({
|
|
252
|
+
name: "[prompt|set]",
|
|
253
|
+
description: "Without a subcommand, prints the saved app blueprint. prompt renders the AI prompt; set writes the approved blueprint."
|
|
254
|
+
})
|
|
255
|
+
]),
|
|
256
|
+
defaults: Object.freeze([
|
|
257
|
+
"The app blueprint is product-level app state, not a feature-session step.",
|
|
258
|
+
"The saved blueprint lives at .jskit/APP_BLUEPRINT.md in the current target app.",
|
|
259
|
+
"Project prompt overrides live at .jskit/prompts/app_blueprint.md.",
|
|
260
|
+
"Use --json for a stable machine-readable response."
|
|
261
|
+
]),
|
|
262
|
+
examples: Object.freeze([
|
|
263
|
+
Object.freeze({
|
|
264
|
+
label: "Manual blueprint flow",
|
|
265
|
+
lines: Object.freeze([
|
|
266
|
+
"jskit blueprint prompt --brief \"A field app for customer visits\"",
|
|
267
|
+
"jskit blueprint set --blueprint -",
|
|
268
|
+
"jskit blueprint --json"
|
|
269
|
+
])
|
|
270
|
+
})
|
|
271
|
+
]),
|
|
272
|
+
fullUse:
|
|
273
|
+
"jskit blueprint [prompt|set] [--brief <text>|--brief-file <path>] [--blueprint <text>|--blueprint-file <path>] [--json]",
|
|
274
|
+
showHelpOnBareInvocation: false,
|
|
275
|
+
handlerName: "commandBlueprint",
|
|
276
|
+
allowedFlagKeys: Object.freeze(["json"]),
|
|
277
|
+
inlineOptionMode: "delegate",
|
|
278
|
+
allowedValueOptionNames: Object.freeze([]),
|
|
279
|
+
canDelegateInlineOptions: (positional = []) => {
|
|
280
|
+
const subcommand = String(Array.isArray(positional) ? positional[0] || "" : "").trim();
|
|
281
|
+
return subcommand === "prompt" || subcommand === "set";
|
|
282
|
+
}
|
|
283
|
+
}),
|
|
201
284
|
add: Object.freeze({
|
|
202
285
|
command: "add",
|
|
203
286
|
aliases: Object.freeze([]),
|
|
@@ -6,6 +6,8 @@ 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
|
+
import { createBlueprintCommands } from "../commandHandlers/blueprint.js";
|
|
9
11
|
|
|
10
12
|
function createCommandHandlers(deps = {}) {
|
|
11
13
|
const shared = createCommandHandlerShared(deps);
|
|
@@ -29,6 +31,8 @@ function createCommandHandlers(deps = {}) {
|
|
|
29
31
|
const { commandMobile } = createMobileCommands(commandContext, { commandAdd });
|
|
30
32
|
const { commandDoctor, commandLintDescriptors } = createHealthCommands(commandContext);
|
|
31
33
|
const { commandCompletion } = createCompletionCommands(commandContext);
|
|
34
|
+
const { commandSession } = createSessionCommands(commandContext);
|
|
35
|
+
const { commandBlueprint } = createBlueprintCommands(commandContext);
|
|
32
36
|
|
|
33
37
|
return {
|
|
34
38
|
commandList,
|
|
@@ -46,7 +50,9 @@ function createCommandHandlers(deps = {}) {
|
|
|
46
50
|
commandUpdate,
|
|
47
51
|
commandRemove,
|
|
48
52
|
commandDoctor,
|
|
49
|
-
commandLintDescriptors
|
|
53
|
+
commandLintDescriptors,
|
|
54
|
+
commandSession,
|
|
55
|
+
commandBlueprint
|
|
50
56
|
};
|
|
51
57
|
}
|
|
52
58
|
|
package/src/server/index.js
CHANGED