@jskit-ai/jskit-cli 0.2.80 → 0.2.81
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/commandHandlers/session.js +71 -3
- package/src/server/core/argParser.js +12 -0
- package/src/server/core/commandCatalog.js +14 -7
- package/src/server/sessionRuntime/constants.js +150 -126
- package/src/server/sessionRuntime/paths.js +2 -4
- package/src/server/sessionRuntime/preconditions.js +25 -35
- package/src/server/sessionRuntime/prompts/app_blueprint.md +26 -2
- package/src/server/sessionRuntime/prompts/doctor_failure.md +12 -1
- package/src/server/sessionRuntime/prompts/execute_plan.md +35 -0
- package/src/server/sessionRuntime/prompts/new_issue.md +21 -3
- package/src/server/sessionRuntime/prompts/plan_issue.md +50 -0
- package/src/server/sessionRuntime/prompts/pr_failure.md +14 -1
- package/src/server/sessionRuntime/prompts/review_changes.md +36 -15
- package/src/server/sessionRuntime/prompts/user_check.md +7 -3
- package/src/server/sessionRuntime/responses.js +146 -19
- package/src/server/sessionRuntime/worktrees.js +31 -0
- package/src/server/sessionRuntime.js +419 -128
- package/src/server/sessionRuntime/prompts/implement_issue.md +0 -25
|
@@ -18,6 +18,9 @@ import {
|
|
|
18
18
|
import {
|
|
19
19
|
pathsForExistingSession
|
|
20
20
|
} from "./paths.js";
|
|
21
|
+
import {
|
|
22
|
+
hasWorktree
|
|
23
|
+
} from "./worktrees.js";
|
|
21
24
|
|
|
22
25
|
function createError({
|
|
23
26
|
code,
|
|
@@ -43,15 +46,83 @@ function createPrecondition({
|
|
|
43
46
|
});
|
|
44
47
|
}
|
|
45
48
|
|
|
49
|
+
const LEGACY_CURRENT_STEP_ID_ALIASES = Object.freeze({
|
|
50
|
+
implementation_changes_detected: "implementation_changes_accepted",
|
|
51
|
+
review_changes_detected: "review_changes_accepted"
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
const LEGACY_COMPLETED_STEP_ID_ALIASES = Object.freeze({
|
|
55
|
+
implementation_changes_detected: Object.freeze(["implementation_changes_accepted"]),
|
|
56
|
+
review_changes_detected: Object.freeze(["review_changes_accepted", "review_changes_committed"])
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
const LEGACY_RECEIPT_STEP_ID_ALIASES = Object.freeze({
|
|
60
|
+
implementation_changes_detected: "implementation_changes_accepted",
|
|
61
|
+
review_changes_detected: "review_changes_committed"
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
function normalizeStepId(stepId) {
|
|
65
|
+
const normalized = normalizeText(stepId);
|
|
66
|
+
return LEGACY_CURRENT_STEP_ID_ALIASES[normalized] || normalized;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function completedStepIdsForReceipt(stepId) {
|
|
70
|
+
const normalized = normalizeText(stepId);
|
|
71
|
+
return LEGACY_COMPLETED_STEP_ID_ALIASES[normalized] || [normalized];
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function receiptStepId(stepId) {
|
|
75
|
+
const normalized = normalizeText(stepId);
|
|
76
|
+
return LEGACY_RECEIPT_STEP_ID_ALIASES[normalized] || normalizeStepId(normalized);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function stepIndex(stepId) {
|
|
80
|
+
return STEP_IDS.indexOf(normalizeStepId(stepId));
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function normalizeKnownStepIds(stepIds = []) {
|
|
84
|
+
return Array.from(
|
|
85
|
+
new Set(
|
|
86
|
+
stepIds
|
|
87
|
+
.flatMap((stepId) => completedStepIdsForReceipt(stepId))
|
|
88
|
+
.map((stepId) => normalizeText(stepId))
|
|
89
|
+
.filter((stepId) => STEP_IDS.includes(stepId))
|
|
90
|
+
)
|
|
91
|
+
).sort((left, right) => STEP_IDS.indexOf(left) - STEP_IDS.indexOf(right));
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function stepCanExposeStoredPrompt(stepId) {
|
|
95
|
+
const step = STEP_DEFINITION_BY_ID[normalizeStepId(stepId)];
|
|
96
|
+
return Boolean(step?.codex || step?.kind === "human_input");
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const PROMPT_ARTIFACT_BY_STEP_ID = Object.freeze({
|
|
100
|
+
issue_drafted: "issue_draft.md",
|
|
101
|
+
plan_made: "plan_request.md",
|
|
102
|
+
user_check_completed: "user_check.md"
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
async function readPromptForStep(paths, stepId) {
|
|
106
|
+
if (!stepCanExposeStoredPrompt(stepId)) {
|
|
107
|
+
return "";
|
|
108
|
+
}
|
|
109
|
+
const promptArtifact = PROMPT_ARTIFACT_BY_STEP_ID[normalizeStepId(stepId)];
|
|
110
|
+
if (promptArtifact) {
|
|
111
|
+
const prompt = await readTextIfExists(path.join(paths.sessionRoot, "prompts", promptArtifact));
|
|
112
|
+
if (prompt) {
|
|
113
|
+
return prompt;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
return readTextIfExists(path.join(paths.sessionRoot, "prompt.md"));
|
|
117
|
+
}
|
|
118
|
+
|
|
46
119
|
async function readCompletedSteps(sessionRoot) {
|
|
47
120
|
const stepsRoot = path.join(sessionRoot, "steps");
|
|
48
121
|
try {
|
|
49
122
|
const entries = await readdir(stepsRoot, { withFileTypes: true });
|
|
50
|
-
return entries
|
|
123
|
+
return normalizeKnownStepIds(entries
|
|
51
124
|
.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));
|
|
125
|
+
.map((entry) => entry.name));
|
|
55
126
|
} catch {
|
|
56
127
|
return [];
|
|
57
128
|
}
|
|
@@ -61,12 +132,32 @@ async function readReceiptSteps(paths) {
|
|
|
61
132
|
const stepsRoot = path.join(paths.sessionRoot, "steps");
|
|
62
133
|
try {
|
|
63
134
|
const entries = await readdir(stepsRoot, { withFileTypes: true });
|
|
64
|
-
const
|
|
135
|
+
const knownStepRows = new Map();
|
|
136
|
+
const unknownStepRows = [];
|
|
137
|
+
entries
|
|
65
138
|
.filter((entry) => entry.isFile())
|
|
66
139
|
.map((entry) => entry.name)
|
|
140
|
+
.forEach((receiptName) => {
|
|
141
|
+
const stepId = receiptStepId(receiptName);
|
|
142
|
+
if (STEP_IDS.includes(stepId)) {
|
|
143
|
+
if (!knownStepRows.has(stepId) || receiptName === stepId) {
|
|
144
|
+
knownStepRows.set(stepId, {
|
|
145
|
+
receiptName,
|
|
146
|
+
stepId
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
unknownStepRows.push({
|
|
152
|
+
receiptName,
|
|
153
|
+
stepId
|
|
154
|
+
});
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
const stepRows = [...knownStepRows.values(), ...unknownStepRows]
|
|
67
158
|
.sort((left, right) => {
|
|
68
|
-
const leftIndex =
|
|
69
|
-
const rightIndex =
|
|
159
|
+
const leftIndex = stepIndex(left.stepId);
|
|
160
|
+
const rightIndex = stepIndex(right.stepId);
|
|
70
161
|
if (leftIndex >= 0 && rightIndex >= 0) {
|
|
71
162
|
return leftIndex - rightIndex;
|
|
72
163
|
}
|
|
@@ -76,12 +167,12 @@ async function readReceiptSteps(paths) {
|
|
|
76
167
|
if (rightIndex >= 0) {
|
|
77
168
|
return 1;
|
|
78
169
|
}
|
|
79
|
-
return left.localeCompare(right);
|
|
170
|
+
return left.stepId.localeCompare(right.stepId);
|
|
80
171
|
});
|
|
81
172
|
|
|
82
|
-
return Promise.all(
|
|
173
|
+
return Promise.all(stepRows.map(async ({ receiptName, stepId }) => ({
|
|
83
174
|
label: STEP_LABEL_BY_ID[stepId] || stepId,
|
|
84
|
-
receipt: (await readTextIfExists(path.join(stepsRoot,
|
|
175
|
+
receipt: (await readTextIfExists(path.join(stepsRoot, receiptName))).trim(),
|
|
85
176
|
stepId
|
|
86
177
|
})));
|
|
87
178
|
} catch {
|
|
@@ -113,7 +204,8 @@ function publicStepDefinition(step, index) {
|
|
|
113
204
|
index,
|
|
114
205
|
input: cloneContractValue(step.input),
|
|
115
206
|
kind: step.kind,
|
|
116
|
-
label: step.label
|
|
207
|
+
label: step.label,
|
|
208
|
+
utilityActions: cloneContractValue(step.utilityActions || [])
|
|
117
209
|
};
|
|
118
210
|
}
|
|
119
211
|
|
|
@@ -132,7 +224,8 @@ function buildCurrentStepAction(stepId) {
|
|
|
132
224
|
index: STEP_IDS.indexOf(step.id),
|
|
133
225
|
input: cloneContractValue(step.input),
|
|
134
226
|
kind: step.kind,
|
|
135
|
-
stepId: step.id
|
|
227
|
+
stepId: step.id,
|
|
228
|
+
utilityActions: cloneContractValue(step.utilityActions || [])
|
|
136
229
|
};
|
|
137
230
|
}
|
|
138
231
|
|
|
@@ -142,28 +235,51 @@ function buildCodexHandoff(stepId) {
|
|
|
142
235
|
}
|
|
143
236
|
|
|
144
237
|
async function readSessionArtifacts(paths) {
|
|
145
|
-
const [status,
|
|
238
|
+
const [status, rawCurrentStep, issueUrl, prUrl, issueText, issueTitle, planText, codexThreadId] = await Promise.all([
|
|
146
239
|
readTrimmedFile(path.join(paths.sessionRoot, "status")),
|
|
147
240
|
readTrimmedFile(path.join(paths.sessionRoot, "current_step")),
|
|
148
241
|
readTrimmedFile(path.join(paths.sessionRoot, "issue_url")),
|
|
149
242
|
readTrimmedFile(path.join(paths.sessionRoot, "pr_url")),
|
|
150
|
-
readTextIfExists(path.join(paths.sessionRoot, "prompt.md")),
|
|
151
243
|
readTextIfExists(path.join(paths.sessionRoot, "issue.md")),
|
|
244
|
+
readTrimmedFile(path.join(paths.sessionRoot, "issue_title")),
|
|
245
|
+
readTextIfExists(path.join(paths.sessionRoot, "plan.md")),
|
|
152
246
|
readTrimmedFile(path.join(paths.sessionRoot, "codex_thread_id"))
|
|
153
247
|
]);
|
|
154
|
-
const
|
|
248
|
+
const currentStep = normalizeStepId(rawCurrentStep);
|
|
249
|
+
const worktreeReady = await hasWorktree(paths);
|
|
250
|
+
let completedSteps = await readCompletedSteps(paths.sessionRoot);
|
|
251
|
+
const worktreeRemovalCompleted = completedSteps.includes("pr_merged") ||
|
|
252
|
+
completedSteps.includes("worktree_removed");
|
|
253
|
+
const worktreeReceiptInvalid = !worktreeReady &&
|
|
254
|
+
completedSteps.includes("worktree_created") &&
|
|
255
|
+
!worktreeRemovalCompleted &&
|
|
256
|
+
status !== SESSION_STATUS.FINISHED &&
|
|
257
|
+
status !== SESSION_STATUS.ABANDONED;
|
|
258
|
+
if (worktreeReceiptInvalid) {
|
|
259
|
+
completedSteps = completedSteps.filter((stepId) => !["worktree_created", "dependencies_installed"].includes(stepId));
|
|
260
|
+
}
|
|
155
261
|
const nextStep = resolveNextStep(completedSteps);
|
|
262
|
+
const currentStepIndex = stepIndex(currentStep);
|
|
263
|
+
const nextStepIndex = stepIndex(nextStep);
|
|
264
|
+
const effectiveCurrentStep = nextStep &&
|
|
265
|
+
(completedSteps.includes(currentStep) || currentStepIndex < 0 || currentStepIndex > nextStepIndex)
|
|
266
|
+
? nextStep
|
|
267
|
+
: currentStep || nextStep;
|
|
268
|
+
const prompt = await readPromptForStep(paths, effectiveCurrentStep);
|
|
156
269
|
|
|
157
270
|
return {
|
|
158
271
|
codexThreadId,
|
|
159
272
|
completedSteps,
|
|
160
|
-
currentStep:
|
|
273
|
+
currentStep: effectiveCurrentStep,
|
|
274
|
+
issueTitle,
|
|
161
275
|
issueText: issueText.trim(),
|
|
162
276
|
issueUrl,
|
|
163
277
|
nextStep,
|
|
164
278
|
prUrl,
|
|
279
|
+
planText: planText.trim(),
|
|
165
280
|
prompt: prompt.trim(),
|
|
166
|
-
status: status || SESSION_STATUS.PENDING
|
|
281
|
+
status: status || SESSION_STATUS.PENDING,
|
|
282
|
+
worktreeReady
|
|
167
283
|
};
|
|
168
284
|
}
|
|
169
285
|
|
|
@@ -176,6 +292,7 @@ function buildNextCommand(sessionId, stepId) {
|
|
|
176
292
|
}
|
|
177
293
|
|
|
178
294
|
async function buildSessionResponse(paths, {
|
|
295
|
+
codex = undefined,
|
|
179
296
|
ok = true,
|
|
180
297
|
errors = [],
|
|
181
298
|
preconditions = [],
|
|
@@ -186,7 +303,9 @@ async function buildSessionResponse(paths, {
|
|
|
186
303
|
const artifacts = responsePaths.sessionRoot ? await readSessionArtifacts(responsePaths) : {};
|
|
187
304
|
const resolvedStatus = status || artifacts.status || (ok ? SESSION_STATUS.PENDING : SESSION_STATUS.BLOCKED);
|
|
188
305
|
const currentStep = artifacts.currentStep || artifacts.nextStep || "";
|
|
189
|
-
const responsePrompt = typeof prompt === "string"
|
|
306
|
+
const responsePrompt = typeof prompt === "string"
|
|
307
|
+
? prompt
|
|
308
|
+
: stepCanExposeStoredPrompt(currentStep) ? artifacts.prompt || "" : "";
|
|
190
309
|
|
|
191
310
|
return {
|
|
192
311
|
ok: ok === true,
|
|
@@ -196,16 +315,20 @@ async function buildSessionResponse(paths, {
|
|
|
196
315
|
completedSteps: artifacts.completedSteps || [],
|
|
197
316
|
stepDefinitions: buildStepDefinitions(),
|
|
198
317
|
currentStepAction: buildCurrentStepAction(currentStep),
|
|
199
|
-
codex: buildCodexHandoff(currentStep),
|
|
318
|
+
codex: codex === undefined ? buildCodexHandoff(currentStep) : cloneContractValue(codex),
|
|
200
319
|
prompt: responsePrompt,
|
|
201
320
|
nextCommand: buildNextCommand(paths.sessionId || "", currentStep),
|
|
202
321
|
issueUrl: artifacts.issueUrl || "",
|
|
322
|
+
issueTitle: artifacts.issueTitle || "",
|
|
323
|
+
issueText: artifacts.issueText || "",
|
|
324
|
+
planText: artifacts.planText || "",
|
|
203
325
|
prUrl: artifacts.prUrl || "",
|
|
204
326
|
preconditions,
|
|
205
327
|
errors,
|
|
206
328
|
archive: responsePaths.archive || (resolvedStatus === SESSION_STATUS.FINISHED ? "completed" : resolvedStatus === SESSION_STATUS.ABANDONED ? "abandoned" : "active"),
|
|
207
329
|
sessionRoot: responsePaths.sessionRoot || "",
|
|
208
330
|
worktree: paths.worktree || "",
|
|
331
|
+
worktreeReady: artifacts.worktreeReady === true,
|
|
209
332
|
branch: paths.branch || "",
|
|
210
333
|
codexThreadId: artifacts.codexThreadId || ""
|
|
211
334
|
};
|
|
@@ -243,6 +366,9 @@ function buildSessionErrorResponse({
|
|
|
243
366
|
codex: null,
|
|
244
367
|
prompt: "",
|
|
245
368
|
nextCommand: "",
|
|
369
|
+
issueTitle: "",
|
|
370
|
+
issueText: "",
|
|
371
|
+
planText: "",
|
|
246
372
|
issueUrl: "",
|
|
247
373
|
prUrl: "",
|
|
248
374
|
preconditions,
|
|
@@ -250,6 +376,7 @@ function buildSessionErrorResponse({
|
|
|
250
376
|
archive: "",
|
|
251
377
|
sessionRoot: "",
|
|
252
378
|
worktree: "",
|
|
379
|
+
worktreeReady: false,
|
|
253
380
|
branch: "",
|
|
254
381
|
codexThreadId: "",
|
|
255
382
|
targetRoot: normalizedTargetRoot
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import {
|
|
3
|
+
runGit
|
|
4
|
+
} from "./io.js";
|
|
5
|
+
|
|
6
|
+
function parseGitWorktreeList(output = "") {
|
|
7
|
+
return String(output || "")
|
|
8
|
+
.split(/\r?\n/u)
|
|
9
|
+
.filter((line) => line.startsWith("worktree "))
|
|
10
|
+
.map((line) => path.resolve(line.slice("worktree ".length).trim()))
|
|
11
|
+
.filter(Boolean);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
async function hasWorktree(paths = {}) {
|
|
15
|
+
if (!paths.targetRoot || !paths.worktree) {
|
|
16
|
+
return false;
|
|
17
|
+
}
|
|
18
|
+
const result = await runGit(paths.targetRoot, ["worktree", "list", "--porcelain"], {
|
|
19
|
+
timeout: 10000
|
|
20
|
+
});
|
|
21
|
+
if (!result.ok) {
|
|
22
|
+
return false;
|
|
23
|
+
}
|
|
24
|
+
const expectedWorktree = path.resolve(paths.worktree);
|
|
25
|
+
return parseGitWorktreeList(result.stdout).includes(expectedWorktree);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export {
|
|
29
|
+
hasWorktree,
|
|
30
|
+
parseGitWorktreeList
|
|
31
|
+
};
|