@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
|
@@ -0,0 +1,315 @@
|
|
|
1
|
+
import { mkdir, readdir } from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import {
|
|
4
|
+
SESSION_STATUS,
|
|
5
|
+
STEP_DEFINITION_BY_ID,
|
|
6
|
+
STEP_DEFINITIONS,
|
|
7
|
+
STEP_IDS,
|
|
8
|
+
STEP_LABEL_BY_ID
|
|
9
|
+
} from "./constants.js";
|
|
10
|
+
import {
|
|
11
|
+
fileExists,
|
|
12
|
+
normalizeText,
|
|
13
|
+
readTextIfExists,
|
|
14
|
+
readTrimmedFile,
|
|
15
|
+
timestampForReceipt,
|
|
16
|
+
writeTextFile
|
|
17
|
+
} from "./io.js";
|
|
18
|
+
import {
|
|
19
|
+
pathsForExistingSession
|
|
20
|
+
} from "./paths.js";
|
|
21
|
+
|
|
22
|
+
function createError({
|
|
23
|
+
code,
|
|
24
|
+
message,
|
|
25
|
+
repairCommand = ""
|
|
26
|
+
}) {
|
|
27
|
+
return Object.freeze({
|
|
28
|
+
code: normalizeText(code),
|
|
29
|
+
message: normalizeText(message),
|
|
30
|
+
repairCommand: normalizeText(repairCommand)
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function createPrecondition({
|
|
35
|
+
id,
|
|
36
|
+
ok,
|
|
37
|
+
message
|
|
38
|
+
}) {
|
|
39
|
+
return Object.freeze({
|
|
40
|
+
id: normalizeText(id),
|
|
41
|
+
ok: ok === true,
|
|
42
|
+
message: normalizeText(message)
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
async function readCompletedSteps(sessionRoot) {
|
|
47
|
+
const stepsRoot = path.join(sessionRoot, "steps");
|
|
48
|
+
try {
|
|
49
|
+
const entries = await readdir(stepsRoot, { withFileTypes: true });
|
|
50
|
+
return entries
|
|
51
|
+
.filter((entry) => entry.isFile())
|
|
52
|
+
.map((entry) => entry.name)
|
|
53
|
+
.filter((entry) => STEP_IDS.includes(entry))
|
|
54
|
+
.sort((left, right) => STEP_IDS.indexOf(left) - STEP_IDS.indexOf(right));
|
|
55
|
+
} catch {
|
|
56
|
+
return [];
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
async function readReceiptSteps(paths) {
|
|
61
|
+
const stepsRoot = path.join(paths.sessionRoot, "steps");
|
|
62
|
+
try {
|
|
63
|
+
const entries = await readdir(stepsRoot, { withFileTypes: true });
|
|
64
|
+
const stepNames = entries
|
|
65
|
+
.filter((entry) => entry.isFile())
|
|
66
|
+
.map((entry) => entry.name)
|
|
67
|
+
.sort((left, right) => {
|
|
68
|
+
const leftIndex = STEP_IDS.indexOf(left);
|
|
69
|
+
const rightIndex = STEP_IDS.indexOf(right);
|
|
70
|
+
if (leftIndex >= 0 && rightIndex >= 0) {
|
|
71
|
+
return leftIndex - rightIndex;
|
|
72
|
+
}
|
|
73
|
+
if (leftIndex >= 0) {
|
|
74
|
+
return -1;
|
|
75
|
+
}
|
|
76
|
+
if (rightIndex >= 0) {
|
|
77
|
+
return 1;
|
|
78
|
+
}
|
|
79
|
+
return left.localeCompare(right);
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
return Promise.all(stepNames.map(async (stepId) => ({
|
|
83
|
+
label: STEP_LABEL_BY_ID[stepId] || stepId,
|
|
84
|
+
receipt: (await readTextIfExists(path.join(stepsRoot, stepId))).trim(),
|
|
85
|
+
stepId
|
|
86
|
+
})));
|
|
87
|
+
} catch {
|
|
88
|
+
return [];
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function resolveNextStep(completedSteps = []) {
|
|
93
|
+
const completed = new Set(completedSteps);
|
|
94
|
+
return STEP_IDS.find((stepId) => !completed.has(stepId)) || "";
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function cloneContractValue(value) {
|
|
98
|
+
if (!value || typeof value !== "object") {
|
|
99
|
+
return value;
|
|
100
|
+
}
|
|
101
|
+
if (Array.isArray(value)) {
|
|
102
|
+
return value.map((entry) => cloneContractValue(entry));
|
|
103
|
+
}
|
|
104
|
+
return Object.fromEntries(
|
|
105
|
+
Object.entries(value).map(([key, entry]) => [key, cloneContractValue(entry)])
|
|
106
|
+
);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function publicStepDefinition(step, index) {
|
|
110
|
+
return {
|
|
111
|
+
description: step.description,
|
|
112
|
+
id: step.id,
|
|
113
|
+
index,
|
|
114
|
+
input: cloneContractValue(step.input),
|
|
115
|
+
kind: step.kind,
|
|
116
|
+
label: step.label
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function buildStepDefinitions() {
|
|
121
|
+
return STEP_DEFINITIONS.map((step, index) => publicStepDefinition(step, index));
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function buildCurrentStepAction(stepId) {
|
|
125
|
+
const step = STEP_DEFINITION_BY_ID[stepId];
|
|
126
|
+
if (!step) {
|
|
127
|
+
return null;
|
|
128
|
+
}
|
|
129
|
+
return {
|
|
130
|
+
buttonLabel: step.buttonLabel,
|
|
131
|
+
description: step.description,
|
|
132
|
+
index: STEP_IDS.indexOf(step.id),
|
|
133
|
+
input: cloneContractValue(step.input),
|
|
134
|
+
kind: step.kind,
|
|
135
|
+
stepId: step.id
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function buildCodexHandoff(stepId) {
|
|
140
|
+
const step = STEP_DEFINITION_BY_ID[stepId];
|
|
141
|
+
return step?.codex ? cloneContractValue(step.codex) : null;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
async function readSessionArtifacts(paths) {
|
|
145
|
+
const [status, currentStep, issueUrl, prUrl, prompt, issueText, codexThreadId] = await Promise.all([
|
|
146
|
+
readTrimmedFile(path.join(paths.sessionRoot, "status")),
|
|
147
|
+
readTrimmedFile(path.join(paths.sessionRoot, "current_step")),
|
|
148
|
+
readTrimmedFile(path.join(paths.sessionRoot, "issue_url")),
|
|
149
|
+
readTrimmedFile(path.join(paths.sessionRoot, "pr_url")),
|
|
150
|
+
readTextIfExists(path.join(paths.sessionRoot, "prompt.md")),
|
|
151
|
+
readTextIfExists(path.join(paths.sessionRoot, "issue.md")),
|
|
152
|
+
readTrimmedFile(path.join(paths.sessionRoot, "codex_thread_id"))
|
|
153
|
+
]);
|
|
154
|
+
const completedSteps = await readCompletedSteps(paths.sessionRoot);
|
|
155
|
+
const nextStep = resolveNextStep(completedSteps);
|
|
156
|
+
|
|
157
|
+
return {
|
|
158
|
+
codexThreadId,
|
|
159
|
+
completedSteps,
|
|
160
|
+
currentStep: currentStep || nextStep,
|
|
161
|
+
issueText: issueText.trim(),
|
|
162
|
+
issueUrl,
|
|
163
|
+
nextStep,
|
|
164
|
+
prUrl,
|
|
165
|
+
prompt: prompt.trim(),
|
|
166
|
+
status: status || SESSION_STATUS.PENDING
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
function buildNextCommand(sessionId, stepId) {
|
|
171
|
+
if (!stepId) {
|
|
172
|
+
return "";
|
|
173
|
+
}
|
|
174
|
+
const template = STEP_DEFINITION_BY_ID[stepId]?.nextCommandTemplate || "jskit session {{session_id}} step";
|
|
175
|
+
return template.replaceAll("{{session_id}}", sessionId);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
async function buildSessionResponse(paths, {
|
|
179
|
+
ok = true,
|
|
180
|
+
errors = [],
|
|
181
|
+
preconditions = [],
|
|
182
|
+
prompt = undefined,
|
|
183
|
+
status = undefined
|
|
184
|
+
} = {}) {
|
|
185
|
+
const responsePaths = paths.sessionId ? await pathsForExistingSession(paths) : paths;
|
|
186
|
+
const artifacts = responsePaths.sessionRoot ? await readSessionArtifacts(responsePaths) : {};
|
|
187
|
+
const resolvedStatus = status || artifacts.status || (ok ? SESSION_STATUS.PENDING : SESSION_STATUS.BLOCKED);
|
|
188
|
+
const currentStep = artifacts.currentStep || artifacts.nextStep || "";
|
|
189
|
+
const responsePrompt = typeof prompt === "string" ? prompt : artifacts.prompt || "";
|
|
190
|
+
|
|
191
|
+
return {
|
|
192
|
+
ok: ok === true,
|
|
193
|
+
sessionId: paths.sessionId || "",
|
|
194
|
+
status: resolvedStatus,
|
|
195
|
+
currentStep,
|
|
196
|
+
completedSteps: artifacts.completedSteps || [],
|
|
197
|
+
stepDefinitions: buildStepDefinitions(),
|
|
198
|
+
currentStepAction: buildCurrentStepAction(currentStep),
|
|
199
|
+
codex: buildCodexHandoff(currentStep),
|
|
200
|
+
prompt: responsePrompt,
|
|
201
|
+
nextCommand: buildNextCommand(paths.sessionId || "", currentStep),
|
|
202
|
+
issueUrl: artifacts.issueUrl || "",
|
|
203
|
+
prUrl: artifacts.prUrl || "",
|
|
204
|
+
preconditions,
|
|
205
|
+
errors,
|
|
206
|
+
archive: responsePaths.archive || (resolvedStatus === SESSION_STATUS.FINISHED ? "completed" : resolvedStatus === SESSION_STATUS.ABANDONED ? "abandoned" : "active"),
|
|
207
|
+
sessionRoot: responsePaths.sessionRoot || "",
|
|
208
|
+
worktree: paths.worktree || "",
|
|
209
|
+
branch: paths.branch || "",
|
|
210
|
+
codexThreadId: artifacts.codexThreadId || ""
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
function buildSessionErrorResponse({
|
|
215
|
+
targetRoot = process.cwd(),
|
|
216
|
+
sessionId = "",
|
|
217
|
+
code,
|
|
218
|
+
message,
|
|
219
|
+
repairCommand = "",
|
|
220
|
+
status = SESSION_STATUS.BLOCKED,
|
|
221
|
+
preconditions = [],
|
|
222
|
+
errors = undefined
|
|
223
|
+
} = {}) {
|
|
224
|
+
const normalizedTargetRoot = path.resolve(normalizeText(targetRoot) || process.cwd());
|
|
225
|
+
const errorList = Array.isArray(errors)
|
|
226
|
+
? errors
|
|
227
|
+
: [
|
|
228
|
+
createError({
|
|
229
|
+
code,
|
|
230
|
+
message,
|
|
231
|
+
repairCommand
|
|
232
|
+
})
|
|
233
|
+
];
|
|
234
|
+
|
|
235
|
+
return {
|
|
236
|
+
ok: false,
|
|
237
|
+
sessionId: normalizeText(sessionId),
|
|
238
|
+
status,
|
|
239
|
+
currentStep: "",
|
|
240
|
+
completedSteps: [],
|
|
241
|
+
stepDefinitions: buildStepDefinitions(),
|
|
242
|
+
currentStepAction: null,
|
|
243
|
+
codex: null,
|
|
244
|
+
prompt: "",
|
|
245
|
+
nextCommand: "",
|
|
246
|
+
issueUrl: "",
|
|
247
|
+
prUrl: "",
|
|
248
|
+
preconditions,
|
|
249
|
+
errors: errorList,
|
|
250
|
+
archive: "",
|
|
251
|
+
sessionRoot: "",
|
|
252
|
+
worktree: "",
|
|
253
|
+
branch: "",
|
|
254
|
+
codexThreadId: "",
|
|
255
|
+
targetRoot: normalizedTargetRoot
|
|
256
|
+
};
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
async function markStatus(paths, status) {
|
|
260
|
+
await writeTextFile(path.join(paths.sessionRoot, "status"), status);
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
async function markCurrentStep(paths, stepId) {
|
|
264
|
+
await writeTextFile(path.join(paths.sessionRoot, "current_step"), stepId);
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
async function writeReceipt(paths, stepId, message) {
|
|
268
|
+
await mkdir(path.join(paths.sessionRoot, "steps"), { recursive: true });
|
|
269
|
+
await writeTextFile(
|
|
270
|
+
path.join(paths.sessionRoot, "steps", stepId),
|
|
271
|
+
`${timestampForReceipt()}\n${normalizeText(message) || STEP_LABEL_BY_ID[stepId] || stepId}`
|
|
272
|
+
);
|
|
273
|
+
const completedSteps = await readCompletedSteps(paths.sessionRoot);
|
|
274
|
+
await markCurrentStep(paths, resolveNextStep(completedSteps));
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
async function failSession(paths, {
|
|
278
|
+
code,
|
|
279
|
+
message,
|
|
280
|
+
repairCommand = "",
|
|
281
|
+
preconditions = [],
|
|
282
|
+
status = SESSION_STATUS.BLOCKED,
|
|
283
|
+
prompt = ""
|
|
284
|
+
}) {
|
|
285
|
+
if (paths.sessionRoot && await fileExists(paths.sessionRoot)) {
|
|
286
|
+
await markStatus(paths, status);
|
|
287
|
+
}
|
|
288
|
+
return buildSessionResponse(paths, {
|
|
289
|
+
ok: false,
|
|
290
|
+
status,
|
|
291
|
+
prompt,
|
|
292
|
+
preconditions,
|
|
293
|
+
errors: [
|
|
294
|
+
createError({
|
|
295
|
+
code,
|
|
296
|
+
message,
|
|
297
|
+
repairCommand
|
|
298
|
+
})
|
|
299
|
+
]
|
|
300
|
+
});
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
export {
|
|
304
|
+
buildSessionErrorResponse,
|
|
305
|
+
buildSessionResponse,
|
|
306
|
+
buildStepDefinitions,
|
|
307
|
+
createError,
|
|
308
|
+
createPrecondition,
|
|
309
|
+
failSession,
|
|
310
|
+
markCurrentStep,
|
|
311
|
+
markStatus,
|
|
312
|
+
readReceiptSteps,
|
|
313
|
+
readSessionArtifacts,
|
|
314
|
+
writeReceipt
|
|
315
|
+
};
|