@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
|
@@ -1,1481 +0,0 @@
|
|
|
1
|
-
import { mkdir, readdir } from "node:fs/promises";
|
|
2
|
-
import path from "node:path";
|
|
3
|
-
import {
|
|
4
|
-
CYCLE_STEP_IDS,
|
|
5
|
-
DEPENDENCIES_INSTALL_RESULT_FILE,
|
|
6
|
-
ISSUE_FILE_CODEX_HANDOFF,
|
|
7
|
-
ISSUE_DEFINITION_CODEX_HANDOFF,
|
|
8
|
-
JSKIT_CLI_SHELL_COMMAND,
|
|
9
|
-
REVIEW_EXECUTION_CODEX_HANDOFF,
|
|
10
|
-
REVIEW_PASS_LIMIT,
|
|
11
|
-
SESSION_WORKFLOW_VERSION,
|
|
12
|
-
SESSION_STATUS,
|
|
13
|
-
STEP_DEFINITION_BY_ID,
|
|
14
|
-
STEP_DEFINITIONS,
|
|
15
|
-
STEP_IDS,
|
|
16
|
-
STEP_LABEL_BY_ID
|
|
17
|
-
} from "./constants.js";
|
|
18
|
-
import {
|
|
19
|
-
fileExists,
|
|
20
|
-
normalizeText,
|
|
21
|
-
readTextIfExists,
|
|
22
|
-
readTrimmedFile,
|
|
23
|
-
runGitInWorktree,
|
|
24
|
-
timestampForStepRecord,
|
|
25
|
-
writeTextFile
|
|
26
|
-
} from "./io.js";
|
|
27
|
-
import {
|
|
28
|
-
pathsForExistingSession
|
|
29
|
-
} from "./paths.js";
|
|
30
|
-
import {
|
|
31
|
-
hasWorktree
|
|
32
|
-
} from "./worktrees.js";
|
|
33
|
-
import {
|
|
34
|
-
inspectReadyJskitAppRoot
|
|
35
|
-
} from "./appReadiness.js";
|
|
36
|
-
|
|
37
|
-
function createError({
|
|
38
|
-
code,
|
|
39
|
-
message,
|
|
40
|
-
repairCommand = ""
|
|
41
|
-
}) {
|
|
42
|
-
return Object.freeze({
|
|
43
|
-
code: normalizeText(code),
|
|
44
|
-
message: normalizeText(message),
|
|
45
|
-
repairCommand: normalizeText(repairCommand)
|
|
46
|
-
});
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
function createPrecondition({
|
|
50
|
-
id,
|
|
51
|
-
ok,
|
|
52
|
-
message
|
|
53
|
-
}) {
|
|
54
|
-
return Object.freeze({
|
|
55
|
-
id: normalizeText(id),
|
|
56
|
-
ok: ok === true,
|
|
57
|
-
message: normalizeText(message)
|
|
58
|
-
});
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
function createWarning({
|
|
62
|
-
code,
|
|
63
|
-
message,
|
|
64
|
-
repairCommand = ""
|
|
65
|
-
}) {
|
|
66
|
-
return createError({ code, message, repairCommand });
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
const ACCEPTED_CHANGES_NOOP_WARNING = createWarning({
|
|
70
|
-
code: "accepted_changes_noop",
|
|
71
|
-
message: "No accepted worktree changes were found; continuing without a new commit."
|
|
72
|
-
});
|
|
73
|
-
|
|
74
|
-
function issueNumberFromUrl(issueUrl = "") {
|
|
75
|
-
const match = /\/issues\/(\d+)(?:\b|$)/u.exec(String(issueUrl || ""));
|
|
76
|
-
return match ? match[1] : "";
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
function normalizeStepId(stepId) {
|
|
80
|
-
return normalizeText(stepId);
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
function stepIndex(stepId) {
|
|
84
|
-
return STEP_IDS.indexOf(normalizeStepId(stepId));
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
function normalizeKnownStepIds(stepIds = []) {
|
|
88
|
-
return Array.from(
|
|
89
|
-
new Set(
|
|
90
|
-
stepIds
|
|
91
|
-
.map((stepId) => normalizeText(stepId))
|
|
92
|
-
.filter((stepId) => STEP_IDS.includes(stepId))
|
|
93
|
-
)
|
|
94
|
-
).sort((left, right) => STEP_IDS.indexOf(left) - STEP_IDS.indexOf(right));
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
function stepCanExposeStoredPrompt(stepId) {
|
|
98
|
-
const normalizedStepId = normalizeStepId(stepId);
|
|
99
|
-
const step = STEP_DEFINITION_BY_ID[normalizedStepId];
|
|
100
|
-
return Boolean(
|
|
101
|
-
normalizedStepId === "review_changes_accepted" ||
|
|
102
|
-
step?.codex ||
|
|
103
|
-
step?.kind === "codex_prompt" ||
|
|
104
|
-
step?.kind === "human_input"
|
|
105
|
-
);
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
const DEFAULT_ACTIVE_CYCLE = "001";
|
|
109
|
-
const DEFAULT_REVIEW_PASS = "001";
|
|
110
|
-
|
|
111
|
-
function normalizeCycleNumber(value = "") {
|
|
112
|
-
const normalized = normalizeText(value).replace(/^cycle_/u, "");
|
|
113
|
-
if (!/^\d+$/u.test(normalized)) {
|
|
114
|
-
return DEFAULT_ACTIVE_CYCLE;
|
|
115
|
-
}
|
|
116
|
-
return String(Number.parseInt(normalized, 10)).padStart(3, "0");
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
function cycleDirectoryName(cycle = DEFAULT_ACTIVE_CYCLE) {
|
|
120
|
-
return `cycle_${normalizeCycleNumber(cycle)}`;
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
function isCycleStepId(stepId = "") {
|
|
124
|
-
return CYCLE_STEP_IDS.includes(normalizeStepId(stepId));
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
async function readWorkflowVersion(paths) {
|
|
128
|
-
return readTrimmedFile(path.join(paths.sessionRoot, "workflow_version"));
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
async function readActiveCycle(paths) {
|
|
132
|
-
const cycle = await readTrimmedFile(path.join(paths.sessionRoot, "active_cycle"));
|
|
133
|
-
return normalizeCycleNumber(cycle || DEFAULT_ACTIVE_CYCLE);
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
function cycleStepsRoot(paths, cycle) {
|
|
137
|
-
return path.join(paths.sessionRoot, "steps", cycleDirectoryName(cycle));
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
function cycleRoot(paths, cycle) {
|
|
141
|
-
return path.join(paths.sessionRoot, "cycles", cycleDirectoryName(cycle));
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
function normalizeReviewPassNumber(value = "") {
|
|
145
|
-
const normalized = normalizeText(value).replace(/^pass_/u, "");
|
|
146
|
-
if (!/^\d+$/u.test(normalized)) {
|
|
147
|
-
return DEFAULT_REVIEW_PASS;
|
|
148
|
-
}
|
|
149
|
-
return String(Number.parseInt(normalized, 10)).padStart(3, "0");
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
function reviewPassDirectoryName(pass = DEFAULT_REVIEW_PASS) {
|
|
153
|
-
return `pass_${normalizeReviewPassNumber(pass)}`;
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
function reviewPassRoot(paths, pass) {
|
|
157
|
-
return path.join(paths.sessionRoot, "review_passes", reviewPassDirectoryName(pass));
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
async function parseJsonFileIfExists(filePath) {
|
|
161
|
-
const source = await readTextIfExists(filePath);
|
|
162
|
-
if (!source) {
|
|
163
|
-
return null;
|
|
164
|
-
}
|
|
165
|
-
try {
|
|
166
|
-
const parsed = JSON.parse(source);
|
|
167
|
-
return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : null;
|
|
168
|
-
} catch {
|
|
169
|
-
return null;
|
|
170
|
-
}
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
async function readReviewPassNumbers(paths) {
|
|
174
|
-
try {
|
|
175
|
-
const entries = await readdir(path.join(paths.sessionRoot, "review_passes"), { withFileTypes: true });
|
|
176
|
-
return entries
|
|
177
|
-
.filter((entry) => entry.isDirectory() && /^pass_\d+$/u.test(entry.name))
|
|
178
|
-
.map((entry) => normalizeReviewPassNumber(entry.name))
|
|
179
|
-
.sort((left, right) => Number.parseInt(left, 10) - Number.parseInt(right, 10));
|
|
180
|
-
} catch {
|
|
181
|
-
return [];
|
|
182
|
-
}
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
async function readReviewPassInfo(paths, pass) {
|
|
186
|
-
const normalizedPass = normalizeReviewPassNumber(pass);
|
|
187
|
-
const root = reviewPassRoot(paths, normalizedPass);
|
|
188
|
-
const [prompt, accepted] = await Promise.all([
|
|
189
|
-
parseJsonFileIfExists(path.join(root, "prompt.json")),
|
|
190
|
-
parseJsonFileIfExists(path.join(root, "accepted.json"))
|
|
191
|
-
]);
|
|
192
|
-
const status = accepted?.status || prompt?.status || "unknown";
|
|
193
|
-
const changedFiles = Array.isArray(accepted?.changedFiles) ? accepted.changedFiles : [];
|
|
194
|
-
return {
|
|
195
|
-
pass: normalizedPass,
|
|
196
|
-
label: reviewPassDirectoryName(normalizedPass),
|
|
197
|
-
status,
|
|
198
|
-
promptPath: prompt?.promptPath || path.join(root, "review_prompt_rendered"),
|
|
199
|
-
acceptedAt: accepted?.acceptedAt || "",
|
|
200
|
-
changedFiles,
|
|
201
|
-
commit: "",
|
|
202
|
-
committedAt: "",
|
|
203
|
-
findingsRemaining: accepted?.findingsRemaining === true,
|
|
204
|
-
maxPasses: REVIEW_PASS_LIMIT
|
|
205
|
-
};
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
async function readReviewPasses(paths) {
|
|
209
|
-
const passes = await readReviewPassNumbers(paths);
|
|
210
|
-
return Promise.all(passes.map((pass) => readReviewPassInfo(paths, pass)));
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
const REVIEW_STEP_IDS = Object.freeze([
|
|
214
|
-
"review_prompt_rendered",
|
|
215
|
-
"review_changes_accepted"
|
|
216
|
-
]);
|
|
217
|
-
|
|
218
|
-
function latestReviewPass(artifacts = {}) {
|
|
219
|
-
const passes = Array.isArray(artifacts.reviewPasses) ? artifacts.reviewPasses : [];
|
|
220
|
-
return passes.at(-1) || null;
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
function latestReviewPassIsPrompted(artifacts = {}) {
|
|
224
|
-
return latestReviewPass(artifacts)?.status === "prompted";
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
async function readPromptFromAbsolutePath(filePath = "") {
|
|
228
|
-
return filePath ? readTextIfExists(filePath) : "";
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
async function readReviewPromptForStep(paths, artifacts = {}) {
|
|
232
|
-
const latestPass = latestReviewPass(artifacts);
|
|
233
|
-
if (latestPass?.status === "prompted") {
|
|
234
|
-
const prompt = await readPromptFromAbsolutePath(latestPass.promptPath);
|
|
235
|
-
if (prompt) {
|
|
236
|
-
return prompt;
|
|
237
|
-
}
|
|
238
|
-
}
|
|
239
|
-
return "";
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
async function readPromptForStep(paths, stepId, artifacts = {}) {
|
|
243
|
-
if (!stepCanExposeStoredPrompt(stepId)) {
|
|
244
|
-
return "";
|
|
245
|
-
}
|
|
246
|
-
if (REVIEW_STEP_IDS.includes(normalizeStepId(stepId))) {
|
|
247
|
-
return readReviewPromptForStep(paths, artifacts);
|
|
248
|
-
}
|
|
249
|
-
return "";
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
async function readStepFileNames(stepsRoot) {
|
|
253
|
-
try {
|
|
254
|
-
const entries = await readdir(stepsRoot, { withFileTypes: true });
|
|
255
|
-
return entries
|
|
256
|
-
.filter((entry) => entry.isFile())
|
|
257
|
-
.map((entry) => entry.name);
|
|
258
|
-
} catch {
|
|
259
|
-
return [];
|
|
260
|
-
}
|
|
261
|
-
}
|
|
262
|
-
|
|
263
|
-
async function readCompletedSteps(paths) {
|
|
264
|
-
const stepsRoot = path.join(paths.sessionRoot, "steps");
|
|
265
|
-
const globalStepIds = normalizeKnownStepIds(
|
|
266
|
-
(await readStepFileNames(stepsRoot)).filter((stepId) => !isCycleStepId(stepId))
|
|
267
|
-
);
|
|
268
|
-
const cycleStepIds = [];
|
|
269
|
-
try {
|
|
270
|
-
const entries = await readdir(stepsRoot, { withFileTypes: true });
|
|
271
|
-
for (const entry of entries.filter((item) => item.isDirectory() && /^cycle_\d+$/u.test(item.name)).sort((left, right) => left.name.localeCompare(right.name))) {
|
|
272
|
-
cycleStepIds.push(...await readStepFileNames(path.join(stepsRoot, entry.name)));
|
|
273
|
-
}
|
|
274
|
-
} catch {
|
|
275
|
-
// Legacy sessions may not have cycle record directories.
|
|
276
|
-
}
|
|
277
|
-
return applyReviewPassCompletionOverlay(paths, normalizeKnownStepIds([...globalStepIds, ...cycleStepIds]));
|
|
278
|
-
}
|
|
279
|
-
|
|
280
|
-
async function applyReviewPassCompletionOverlay(paths, completedSteps = []) {
|
|
281
|
-
const completed = new Set(completedSteps);
|
|
282
|
-
if (!REVIEW_STEP_IDS.some((stepId) => completed.has(stepId))) {
|
|
283
|
-
return normalizeKnownStepIds([...completed]);
|
|
284
|
-
}
|
|
285
|
-
const reviewPasses = await readReviewPasses(paths);
|
|
286
|
-
const latestPass = reviewPasses.at(-1);
|
|
287
|
-
if (!latestPass) {
|
|
288
|
-
return normalizeKnownStepIds([...completed]);
|
|
289
|
-
}
|
|
290
|
-
const latestPassAccepted = latestPass.status === "accepted" || latestPass.status === "no_changes";
|
|
291
|
-
const anotherPassRequired = latestPassAccepted && latestPass.findingsRemaining === true;
|
|
292
|
-
if (anotherPassRequired) {
|
|
293
|
-
REVIEW_STEP_IDS.forEach((stepId) => completed.delete(stepId));
|
|
294
|
-
return normalizeKnownStepIds([...completed]);
|
|
295
|
-
}
|
|
296
|
-
if (latestPass.status === "accepted" || latestPass.status === "no_changes") {
|
|
297
|
-
REVIEW_STEP_IDS.forEach((stepId) => completed.add(stepId));
|
|
298
|
-
}
|
|
299
|
-
return normalizeKnownStepIds([...completed]);
|
|
300
|
-
}
|
|
301
|
-
|
|
302
|
-
async function readCycleInfo(paths, cycle) {
|
|
303
|
-
const normalizedCycle = normalizeCycleNumber(cycle);
|
|
304
|
-
const root = cycleRoot(paths, normalizedCycle);
|
|
305
|
-
const userCheckPassed = await readTextIfExists(path.join(cycleStepsRoot(paths, normalizedCycle), "user_check_completed"));
|
|
306
|
-
const userCheckFailed = await readTextIfExists(path.join(cycleStepsRoot(paths, normalizedCycle), "user_check_failed"));
|
|
307
|
-
const reworkRequestPath = path.join(root, "rework_request");
|
|
308
|
-
const reworkRequest = await readTextIfExists(reworkRequestPath);
|
|
309
|
-
return {
|
|
310
|
-
cycle: normalizedCycle,
|
|
311
|
-
label: cycleDirectoryName(normalizedCycle),
|
|
312
|
-
reworkRequest: reworkRequest.trim(),
|
|
313
|
-
reworkRequestPath: reworkRequest ? reworkRequestPath : "",
|
|
314
|
-
status: userCheckPassed ? "passed" : userCheckFailed ? "failed" : "active",
|
|
315
|
-
userCheckResult: userCheckPassed ? "passed" : userCheckFailed ? "failed" : "",
|
|
316
|
-
userCheckRecord: (userCheckPassed || userCheckFailed).trim()
|
|
317
|
-
};
|
|
318
|
-
}
|
|
319
|
-
|
|
320
|
-
async function readCycles(paths, activeCycle) {
|
|
321
|
-
const cycles = new Set([normalizeCycleNumber(activeCycle || DEFAULT_ACTIVE_CYCLE)]);
|
|
322
|
-
for (const root of [path.join(paths.sessionRoot, "steps"), path.join(paths.sessionRoot, "cycles")]) {
|
|
323
|
-
try {
|
|
324
|
-
const entries = await readdir(root, { withFileTypes: true });
|
|
325
|
-
for (const entry of entries) {
|
|
326
|
-
if (entry.isDirectory() && /^cycle_\d+$/u.test(entry.name)) {
|
|
327
|
-
cycles.add(normalizeCycleNumber(entry.name));
|
|
328
|
-
}
|
|
329
|
-
}
|
|
330
|
-
} catch {
|
|
331
|
-
// No cycle directory exists until a session enters a repeatable work cycle.
|
|
332
|
-
}
|
|
333
|
-
}
|
|
334
|
-
return Promise.all([...cycles]
|
|
335
|
-
.sort((left, right) => Number.parseInt(left, 10) - Number.parseInt(right, 10))
|
|
336
|
-
.map((cycle) => readCycleInfo(paths, cycle)));
|
|
337
|
-
}
|
|
338
|
-
|
|
339
|
-
async function readStructuredChecks(paths) {
|
|
340
|
-
const checksRoot = path.join(paths.sessionRoot, "checks");
|
|
341
|
-
try {
|
|
342
|
-
const entries = await readdir(checksRoot, { withFileTypes: true });
|
|
343
|
-
const checks = [];
|
|
344
|
-
for (const entry of entries.filter((item) => item.isFile() && item.name.endsWith(".json")).sort((left, right) => left.name.localeCompare(right.name))) {
|
|
345
|
-
const source = await readTextIfExists(path.join(checksRoot, entry.name));
|
|
346
|
-
try {
|
|
347
|
-
const parsed = JSON.parse(source);
|
|
348
|
-
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
|
|
349
|
-
checks.push(parsed);
|
|
350
|
-
}
|
|
351
|
-
} catch {
|
|
352
|
-
// Ignore malformed check metadata; the raw log remains on disk.
|
|
353
|
-
}
|
|
354
|
-
}
|
|
355
|
-
return checks;
|
|
356
|
-
} catch {
|
|
357
|
-
return [];
|
|
358
|
-
}
|
|
359
|
-
}
|
|
360
|
-
|
|
361
|
-
async function readStructuredUiChecks(paths) {
|
|
362
|
-
const uiChecksRoot = path.join(paths.sessionRoot, "ui_checks");
|
|
363
|
-
try {
|
|
364
|
-
const entries = await readdir(uiChecksRoot, { withFileTypes: true });
|
|
365
|
-
const checks = [];
|
|
366
|
-
for (const entry of entries.filter((item) => item.isFile() && item.name.endsWith(".json")).sort((left, right) => left.name.localeCompare(right.name))) {
|
|
367
|
-
const source = await readTextIfExists(path.join(uiChecksRoot, entry.name));
|
|
368
|
-
try {
|
|
369
|
-
const parsed = JSON.parse(source);
|
|
370
|
-
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
|
|
371
|
-
checks.push(parsed);
|
|
372
|
-
}
|
|
373
|
-
} catch {
|
|
374
|
-
// Ignore malformed UI check metadata; the raw prompt/log remains on disk.
|
|
375
|
-
}
|
|
376
|
-
}
|
|
377
|
-
return checks;
|
|
378
|
-
} catch {
|
|
379
|
-
return [];
|
|
380
|
-
}
|
|
381
|
-
}
|
|
382
|
-
|
|
383
|
-
async function readWorktreeStatus(paths, worktreeReady) {
|
|
384
|
-
if (!worktreeReady) {
|
|
385
|
-
return {
|
|
386
|
-
changedFiles: [],
|
|
387
|
-
dirty: false,
|
|
388
|
-
ok: true,
|
|
389
|
-
status: "missing",
|
|
390
|
-
statusText: ""
|
|
391
|
-
};
|
|
392
|
-
}
|
|
393
|
-
const result = await runGitInWorktree(paths.worktree, ["status", "--porcelain=v1"], {
|
|
394
|
-
timeout: 15000
|
|
395
|
-
});
|
|
396
|
-
if (!result.ok) {
|
|
397
|
-
return {
|
|
398
|
-
changedFiles: [],
|
|
399
|
-
dirty: false,
|
|
400
|
-
ok: false,
|
|
401
|
-
status: "unknown",
|
|
402
|
-
statusText: result.output
|
|
403
|
-
};
|
|
404
|
-
}
|
|
405
|
-
const changedFiles = result.stdout
|
|
406
|
-
.split(/\r?\n/u)
|
|
407
|
-
.map((line) => line.trim())
|
|
408
|
-
.filter(Boolean);
|
|
409
|
-
return {
|
|
410
|
-
changedFiles,
|
|
411
|
-
dirty: changedFiles.length > 0,
|
|
412
|
-
ok: true,
|
|
413
|
-
status: changedFiles.length > 0 ? "dirty" : "clean",
|
|
414
|
-
statusText: result.stdout
|
|
415
|
-
};
|
|
416
|
-
}
|
|
417
|
-
|
|
418
|
-
async function readStepRecords(paths) {
|
|
419
|
-
const stepsRoot = path.join(paths.sessionRoot, "steps");
|
|
420
|
-
try {
|
|
421
|
-
const entries = await readdir(stepsRoot, { withFileTypes: true });
|
|
422
|
-
const knownStepRows = new Map();
|
|
423
|
-
const unknownStepRows = [];
|
|
424
|
-
entries
|
|
425
|
-
.filter((entry) => entry.isFile())
|
|
426
|
-
.map((entry) => entry.name)
|
|
427
|
-
.forEach((recordName) => {
|
|
428
|
-
const stepId = normalizeStepId(recordName);
|
|
429
|
-
if (STEP_IDS.includes(stepId)) {
|
|
430
|
-
if (!knownStepRows.has(stepId) || recordName === stepId) {
|
|
431
|
-
knownStepRows.set(stepId, {
|
|
432
|
-
recordName,
|
|
433
|
-
stepId
|
|
434
|
-
});
|
|
435
|
-
}
|
|
436
|
-
return;
|
|
437
|
-
}
|
|
438
|
-
unknownStepRows.push({
|
|
439
|
-
recordName,
|
|
440
|
-
stepId
|
|
441
|
-
});
|
|
442
|
-
});
|
|
443
|
-
|
|
444
|
-
const stepRows = [...knownStepRows.values(), ...unknownStepRows]
|
|
445
|
-
.sort((left, right) => {
|
|
446
|
-
const leftIndex = stepIndex(left.stepId);
|
|
447
|
-
const rightIndex = stepIndex(right.stepId);
|
|
448
|
-
if (leftIndex >= 0 && rightIndex >= 0) {
|
|
449
|
-
return leftIndex - rightIndex;
|
|
450
|
-
}
|
|
451
|
-
if (leftIndex >= 0) {
|
|
452
|
-
return -1;
|
|
453
|
-
}
|
|
454
|
-
if (rightIndex >= 0) {
|
|
455
|
-
return 1;
|
|
456
|
-
}
|
|
457
|
-
return left.stepId.localeCompare(right.stepId);
|
|
458
|
-
});
|
|
459
|
-
|
|
460
|
-
const globalRecords = await Promise.all(stepRows.map(async ({ recordName, stepId }) => ({
|
|
461
|
-
cycle: "",
|
|
462
|
-
details: (await readTextIfExists(path.join(stepsRoot, recordName))).trim(),
|
|
463
|
-
label: STEP_LABEL_BY_ID[stepId] || stepId,
|
|
464
|
-
stepId
|
|
465
|
-
})));
|
|
466
|
-
|
|
467
|
-
const cycleRecords = [];
|
|
468
|
-
const cycleDirectories = entries
|
|
469
|
-
.filter((entry) => entry.isDirectory() && /^cycle_\d+$/u.test(entry.name))
|
|
470
|
-
.map((entry) => entry.name)
|
|
471
|
-
.sort();
|
|
472
|
-
for (const cycleDirectory of cycleDirectories) {
|
|
473
|
-
const cycle = normalizeCycleNumber(cycleDirectory);
|
|
474
|
-
const cycleRootPath = path.join(stepsRoot, cycleDirectory);
|
|
475
|
-
const cycleStepIds = await readStepFileNames(cycleRootPath);
|
|
476
|
-
for (const recordName of cycleStepIds) {
|
|
477
|
-
const stepId = normalizeStepId(recordName);
|
|
478
|
-
cycleRecords.push({
|
|
479
|
-
cycle,
|
|
480
|
-
details: (await readTextIfExists(path.join(cycleRootPath, recordName))).trim(),
|
|
481
|
-
label: STEP_LABEL_BY_ID[stepId] || stepId,
|
|
482
|
-
stepId
|
|
483
|
-
});
|
|
484
|
-
}
|
|
485
|
-
}
|
|
486
|
-
|
|
487
|
-
return [...globalRecords, ...cycleRecords];
|
|
488
|
-
} catch {
|
|
489
|
-
return [];
|
|
490
|
-
}
|
|
491
|
-
}
|
|
492
|
-
|
|
493
|
-
function resolveNextStep(completedSteps = []) {
|
|
494
|
-
const completed = new Set(completedSteps);
|
|
495
|
-
return STEP_IDS.find((stepId) => !completed.has(stepId)) || "";
|
|
496
|
-
}
|
|
497
|
-
|
|
498
|
-
function cloneContractValue(value) {
|
|
499
|
-
if (!value || typeof value !== "object") {
|
|
500
|
-
return value;
|
|
501
|
-
}
|
|
502
|
-
if (Array.isArray(value)) {
|
|
503
|
-
return value.map((entry) => cloneContractValue(entry));
|
|
504
|
-
}
|
|
505
|
-
return Object.fromEntries(
|
|
506
|
-
Object.entries(value).map(([key, entry]) => [key, cloneContractValue(entry)])
|
|
507
|
-
);
|
|
508
|
-
}
|
|
509
|
-
|
|
510
|
-
function normalizeWarning(warning) {
|
|
511
|
-
if (typeof warning === "string") {
|
|
512
|
-
return createWarning({
|
|
513
|
-
code: "session_warning",
|
|
514
|
-
message: warning
|
|
515
|
-
});
|
|
516
|
-
}
|
|
517
|
-
if (!warning || typeof warning !== "object" || Array.isArray(warning)) {
|
|
518
|
-
return null;
|
|
519
|
-
}
|
|
520
|
-
return createWarning({
|
|
521
|
-
code: warning.code || "session_warning",
|
|
522
|
-
message: warning.message || "",
|
|
523
|
-
repairCommand: warning.repairCommand || ""
|
|
524
|
-
});
|
|
525
|
-
}
|
|
526
|
-
|
|
527
|
-
function mergeWarnings(...warningLists) {
|
|
528
|
-
const merged = [];
|
|
529
|
-
const seen = new Set();
|
|
530
|
-
for (const warnings of warningLists) {
|
|
531
|
-
for (const warning of Array.isArray(warnings) ? warnings : []) {
|
|
532
|
-
const normalized = normalizeWarning(warning);
|
|
533
|
-
if (!normalized?.message) {
|
|
534
|
-
continue;
|
|
535
|
-
}
|
|
536
|
-
const key = `${normalized.code}\n${normalized.message}`;
|
|
537
|
-
if (seen.has(key)) {
|
|
538
|
-
continue;
|
|
539
|
-
}
|
|
540
|
-
seen.add(key);
|
|
541
|
-
merged.push(normalized);
|
|
542
|
-
}
|
|
543
|
-
}
|
|
544
|
-
return merged;
|
|
545
|
-
}
|
|
546
|
-
|
|
547
|
-
async function publicCodexContract(codex = null) {
|
|
548
|
-
if (!codex || typeof codex !== "object" || Array.isArray(codex)) {
|
|
549
|
-
return null;
|
|
550
|
-
}
|
|
551
|
-
return cloneContractValue(codex);
|
|
552
|
-
}
|
|
553
|
-
|
|
554
|
-
function stepRepeatabilityContract(stepId) {
|
|
555
|
-
return {
|
|
556
|
-
repeatable: false,
|
|
557
|
-
repeatableGroupId: "",
|
|
558
|
-
repeatableGroupLabel: "",
|
|
559
|
-
repeatableLabel: ""
|
|
560
|
-
};
|
|
561
|
-
}
|
|
562
|
-
|
|
563
|
-
function publicStepDefinition(step, index) {
|
|
564
|
-
return {
|
|
565
|
-
automation: cloneContractValue(step.automation || { mode: "manual" }),
|
|
566
|
-
...(step.codex ? { codex: cloneContractValue(step.codex) } : {}),
|
|
567
|
-
description: step.description,
|
|
568
|
-
displayGroupId: step.displayGroupId || "",
|
|
569
|
-
displayGroupLabel: step.displayGroupLabel || "",
|
|
570
|
-
id: step.id,
|
|
571
|
-
index,
|
|
572
|
-
input: cloneContractValue(step.input),
|
|
573
|
-
kind: step.kind,
|
|
574
|
-
label: step.label,
|
|
575
|
-
...stepRepeatabilityContract(step.id),
|
|
576
|
-
requiresExplicitRun: step.requiresExplicitRun === true,
|
|
577
|
-
submitOptions: cloneContractValue(step.submitOptions || {}),
|
|
578
|
-
utilityActions: cloneContractValue(step.utilityActions || [])
|
|
579
|
-
};
|
|
580
|
-
}
|
|
581
|
-
|
|
582
|
-
function buildStepDefinitions() {
|
|
583
|
-
return STEP_DEFINITIONS.map((step, index) => publicStepDefinition(step, index));
|
|
584
|
-
}
|
|
585
|
-
|
|
586
|
-
function stepIsRetryableWhenBlocked(stepId) {
|
|
587
|
-
return [
|
|
588
|
-
"automated_checks_run",
|
|
589
|
-
"deep_ui_check_run",
|
|
590
|
-
"main_checkout_synced"
|
|
591
|
-
].includes(normalizeStepId(stepId));
|
|
592
|
-
}
|
|
593
|
-
|
|
594
|
-
function uiCheckPromptedForStep(artifacts = {}, stepId = "") {
|
|
595
|
-
const normalizedStepId = normalizeStepId(stepId);
|
|
596
|
-
return (artifacts.uiChecks || []).some((entry) => {
|
|
597
|
-
return normalizeStepId(entry?.stepId || "") === normalizedStepId &&
|
|
598
|
-
normalizeText(entry?.status || "") === "prompted";
|
|
599
|
-
});
|
|
600
|
-
}
|
|
601
|
-
|
|
602
|
-
function skipReasonForStep(stepId, artifacts = {}) {
|
|
603
|
-
void stepId;
|
|
604
|
-
void artifacts;
|
|
605
|
-
return "";
|
|
606
|
-
}
|
|
607
|
-
|
|
608
|
-
function buildCurrentStepAction(stepId, artifacts = {}) {
|
|
609
|
-
const step = STEP_DEFINITION_BY_ID[stepId];
|
|
610
|
-
if (!step) {
|
|
611
|
-
return null;
|
|
612
|
-
}
|
|
613
|
-
const planExecutionPrompted = artifacts.planExecution?.prompted === true;
|
|
614
|
-
const planExecutionSubmitted = artifacts.planExecution?.submitted === true;
|
|
615
|
-
const issueDefinitionPrompted = step.id === "issue_prompt_rendered" && artifacts.issueDefinitionRequested === true;
|
|
616
|
-
const issueFilePromptAction = step.id === "issue_created";
|
|
617
|
-
const issueSubmissionAction = step.id === "issue_submitted";
|
|
618
|
-
const pullRequestFileAction = step.id === "final_report_created";
|
|
619
|
-
const prSubmissionAction = step.id === "pr_created";
|
|
620
|
-
const deepUiCheckPrompted = step.id === "deep_ui_check_run" && uiCheckPromptedForStep(artifacts, "deep_ui_check_run");
|
|
621
|
-
const automatedChecksPrompted = step.id === "automated_checks_run" && (artifacts.checks || []).some((entry) => {
|
|
622
|
-
return normalizeStepId(entry?.stepId || "") === "automated_checks_run" &&
|
|
623
|
-
normalizeText(entry?.status || "") === "prompted";
|
|
624
|
-
});
|
|
625
|
-
const alternateActions = [];
|
|
626
|
-
if (step.id === "review_changes_accepted") {
|
|
627
|
-
alternateActions.push({
|
|
628
|
-
id: "request_another_review_pass",
|
|
629
|
-
helpText: "Run another explicit deslop prompt before continuing.",
|
|
630
|
-
input: {
|
|
631
|
-
type: "none"
|
|
632
|
-
},
|
|
633
|
-
label: "Run deslop",
|
|
634
|
-
presentation: "secondary",
|
|
635
|
-
submitOptions: {
|
|
636
|
-
reviewFindingsRemaining: true
|
|
637
|
-
},
|
|
638
|
-
targetStep: "review_prompt_rendered"
|
|
639
|
-
});
|
|
640
|
-
}
|
|
641
|
-
const dynamicButtonLabel = (() => {
|
|
642
|
-
if (step.id === "issue_created" && !artifacts.issueText) {
|
|
643
|
-
return "Create issue file";
|
|
644
|
-
}
|
|
645
|
-
if (step.id === "issue_submitted") {
|
|
646
|
-
return "Create issue on GH";
|
|
647
|
-
}
|
|
648
|
-
if (step.id === "pr_created") {
|
|
649
|
-
return "Create PR on GH";
|
|
650
|
-
}
|
|
651
|
-
if (step.id === "pr_merge_prepared") {
|
|
652
|
-
return "Merge";
|
|
653
|
-
}
|
|
654
|
-
if (step.id === "main_checkout_synced" && artifacts.prOutcome?.outcome && artifacts.prOutcome.outcome !== "merged") {
|
|
655
|
-
return "Sync main checkout";
|
|
656
|
-
}
|
|
657
|
-
return step.buttonLabel;
|
|
658
|
-
})();
|
|
659
|
-
const dynamicDescription = (() => {
|
|
660
|
-
if (issueDefinitionPrompted) {
|
|
661
|
-
return "Codex has the issue-definition prompt. Continue after the issue is scoped clearly enough.";
|
|
662
|
-
}
|
|
663
|
-
if (issueFilePromptAction && artifacts.issueFileRequested) {
|
|
664
|
-
return "Codex has the issue-file prompt. Review issue.md and issue_title, then continue when ready.";
|
|
665
|
-
}
|
|
666
|
-
if (issueSubmissionAction && artifacts.issueUrl) {
|
|
667
|
-
return "The GitHub issue has been created. Continue when ready.";
|
|
668
|
-
}
|
|
669
|
-
if (pullRequestFileAction && artifacts.pullRequestFileRequested) {
|
|
670
|
-
return "Codex has the PR-file prompt. Review pull_request.md, then continue when ready.";
|
|
671
|
-
}
|
|
672
|
-
if (prSubmissionAction && artifacts.prUrl) {
|
|
673
|
-
return "The GitHub pull request has been created. Continue when ready.";
|
|
674
|
-
}
|
|
675
|
-
if (step.id === "pr_merge_prepared" && artifacts.prOutcome?.outcome === "merged") {
|
|
676
|
-
return "The pull request was merged. Use Next when ready.";
|
|
677
|
-
}
|
|
678
|
-
if (step.id === "plan_executed" && planExecutionPrompted && !planExecutionSubmitted) {
|
|
679
|
-
return "Codex has the execution prompt. Review the result, then use Next when ready.";
|
|
680
|
-
}
|
|
681
|
-
if (step.id === "deep_ui_check_run" && deepUiCheckPrompted) {
|
|
682
|
-
return "Codex has the run deep UI check prompt. Review the result, then use Next when ready.";
|
|
683
|
-
}
|
|
684
|
-
if (step.id === "automated_checks_run" && automatedChecksPrompted) {
|
|
685
|
-
return "Codex has the run automated checks prompt. Review the result, then use Next when ready.";
|
|
686
|
-
}
|
|
687
|
-
if (step.id === "main_checkout_synced" && artifacts.prOutcome?.outcome && artifacts.prOutcome.outcome !== "merged") {
|
|
688
|
-
return "Main checkout sync is only available after a successful merge. Use Next to continue.";
|
|
689
|
-
}
|
|
690
|
-
if (step.id === "main_checkout_synced" && artifacts.mainCheckoutSync?.status === "synced") {
|
|
691
|
-
return "The main checkout has been synced. Use Next when ready.";
|
|
692
|
-
}
|
|
693
|
-
return step.description;
|
|
694
|
-
})();
|
|
695
|
-
const dynamicUtilityActions = (() => {
|
|
696
|
-
if (step.id === "review_prompt_rendered" || step.id === "review_changes_accepted") {
|
|
697
|
-
return [
|
|
698
|
-
{
|
|
699
|
-
id: "resolve_deslop",
|
|
700
|
-
helpText: "Send Codex the explicit resolve deslop prompt. Nothing advances automatically after it finishes.",
|
|
701
|
-
kind: "codex_prompt",
|
|
702
|
-
label: "Resolve deslop",
|
|
703
|
-
submitOptions: {
|
|
704
|
-
actionCommand: "resolve_deslop"
|
|
705
|
-
}
|
|
706
|
-
},
|
|
707
|
-
...(step.utilityActions || [])
|
|
708
|
-
];
|
|
709
|
-
}
|
|
710
|
-
return step.utilityActions || [];
|
|
711
|
-
})();
|
|
712
|
-
return {
|
|
713
|
-
alternateActions,
|
|
714
|
-
buttonLabel: dynamicButtonLabel,
|
|
715
|
-
description: dynamicDescription,
|
|
716
|
-
displayGroupId: step.displayGroupId,
|
|
717
|
-
displayGroupLabel: step.displayGroupLabel,
|
|
718
|
-
index: STEP_IDS.indexOf(step.id),
|
|
719
|
-
input: cloneContractValue(issueDefinitionPrompted || issueFilePromptAction || pullRequestFileAction ? { type: "none" } : step.input),
|
|
720
|
-
kind: issueDefinitionPrompted || issueFilePromptAction || pullRequestFileAction ? "codex_prompt" : step.kind,
|
|
721
|
-
label: dynamicButtonLabel,
|
|
722
|
-
automation: cloneContractValue(issueDefinitionPrompted || issueFilePromptAction || pullRequestFileAction ? { mode: "codex_prompt" } : step.automation || { mode: "manual" }),
|
|
723
|
-
...stepRepeatabilityContract(step.id),
|
|
724
|
-
requiredInput: cloneContractValue(issueDefinitionPrompted || issueFilePromptAction || pullRequestFileAction ? { type: "none" } : step.input),
|
|
725
|
-
requiresExplicitRun: step.requiresExplicitRun === true,
|
|
726
|
-
retryable: artifacts.status === SESSION_STATUS.BLOCKED && stepIsRetryableWhenBlocked(step.id),
|
|
727
|
-
skipReason: skipReasonForStep(step.id, artifacts),
|
|
728
|
-
stepId: step.id,
|
|
729
|
-
submitOptions: cloneContractValue(step.submitOptions || {}),
|
|
730
|
-
utilityActions: cloneContractValue(dynamicUtilityActions)
|
|
731
|
-
};
|
|
732
|
-
}
|
|
733
|
-
|
|
734
|
-
function rawCodexHandoff(stepId, artifacts = {}) {
|
|
735
|
-
if (normalizeStepId(stepId) === "issue_prompt_rendered" && artifacts.issueDefinitionRequested) {
|
|
736
|
-
return cloneContractValue(ISSUE_DEFINITION_CODEX_HANDOFF);
|
|
737
|
-
}
|
|
738
|
-
if (normalizeStepId(stepId) === "issue_created" && artifacts.issueFileRequested) {
|
|
739
|
-
return cloneContractValue(ISSUE_FILE_CODEX_HANDOFF);
|
|
740
|
-
}
|
|
741
|
-
if (normalizeStepId(stepId) === "review_changes_accepted" && latestReviewPassIsPrompted(artifacts)) {
|
|
742
|
-
return cloneContractValue(REVIEW_EXECUTION_CODEX_HANDOFF);
|
|
743
|
-
}
|
|
744
|
-
const step = STEP_DEFINITION_BY_ID[stepId];
|
|
745
|
-
return step?.codex ? cloneContractValue(step.codex) : null;
|
|
746
|
-
}
|
|
747
|
-
|
|
748
|
-
async function buildCodexHandoff(stepId, artifacts = {}) {
|
|
749
|
-
return publicCodexContract(rawCodexHandoff(stepId, artifacts));
|
|
750
|
-
}
|
|
751
|
-
|
|
752
|
-
async function readSessionArtifacts(paths) {
|
|
753
|
-
const activeCycle = await readActiveCycle(paths);
|
|
754
|
-
const globalPlanExecutionRecordPath = path.join(paths.sessionRoot, "steps", "plan_executed");
|
|
755
|
-
const legacyPlanExecutionRecordPath = path.join(cycleStepsRoot(paths, activeCycle), "plan_executed");
|
|
756
|
-
const [
|
|
757
|
-
status,
|
|
758
|
-
rawCurrentStep,
|
|
759
|
-
issueUrl,
|
|
760
|
-
issueNumber,
|
|
761
|
-
prUrl,
|
|
762
|
-
issueText,
|
|
763
|
-
issueTitle,
|
|
764
|
-
finalReportText,
|
|
765
|
-
pullRequestText,
|
|
766
|
-
githubCommentsText,
|
|
767
|
-
codexThreadId,
|
|
768
|
-
workflowVersion,
|
|
769
|
-
baseBranch,
|
|
770
|
-
baseCommit,
|
|
771
|
-
planExecutionRecord,
|
|
772
|
-
issueDefinitionRequested,
|
|
773
|
-
issueFileRequested,
|
|
774
|
-
pullRequestFileRequested,
|
|
775
|
-
makePlanRequested,
|
|
776
|
-
blueprintUpdateRequested,
|
|
777
|
-
executePlanRequested,
|
|
778
|
-
prOutcomeText,
|
|
779
|
-
mainCheckoutSyncText,
|
|
780
|
-
changesCommittedText
|
|
781
|
-
] = await Promise.all([
|
|
782
|
-
readTrimmedFile(path.join(paths.sessionRoot, "status")),
|
|
783
|
-
readTrimmedFile(path.join(paths.sessionRoot, "current_step")),
|
|
784
|
-
readTrimmedFile(path.join(paths.sessionRoot, "issue_url")),
|
|
785
|
-
readTrimmedFile(path.join(paths.sessionRoot, "metadata", "issue_number")),
|
|
786
|
-
readTrimmedFile(path.join(paths.sessionRoot, "pr_url")),
|
|
787
|
-
readTextIfExists(path.join(paths.sessionRoot, "issue.md")),
|
|
788
|
-
readTrimmedFile(path.join(paths.sessionRoot, "issue_title")),
|
|
789
|
-
readTextIfExists(path.join(paths.sessionRoot, "final_report")),
|
|
790
|
-
readTextIfExists(path.join(paths.sessionRoot, "pull_request.md")),
|
|
791
|
-
readTextIfExists(path.join(paths.sessionRoot, "github_comments.json")),
|
|
792
|
-
readTrimmedFile(path.join(paths.sessionRoot, "codex_thread_id")),
|
|
793
|
-
readWorkflowVersion(paths),
|
|
794
|
-
readTrimmedFile(path.join(paths.sessionRoot, "base_branch")),
|
|
795
|
-
readTrimmedFile(path.join(paths.sessionRoot, "base_commit")),
|
|
796
|
-
readTextIfExists(globalPlanExecutionRecordPath).then(async (text) => text || await readTextIfExists(legacyPlanExecutionRecordPath)),
|
|
797
|
-
readTrimmedFile(path.join(paths.sessionRoot, "metadata", "issue_prompt_rendered_requested")),
|
|
798
|
-
readTrimmedFile(path.join(paths.sessionRoot, "metadata", "issue_created_requested")),
|
|
799
|
-
readTrimmedFile(path.join(paths.sessionRoot, "metadata", "pull_request_file_requested")),
|
|
800
|
-
readTrimmedFile(path.join(paths.sessionRoot, "metadata", "make_plan_requested")),
|
|
801
|
-
readTrimmedFile(path.join(paths.sessionRoot, "metadata", "blueprint_updated_requested")),
|
|
802
|
-
readTrimmedFile(path.join(paths.sessionRoot, "metadata", "execute_plan_requested")),
|
|
803
|
-
readTextIfExists(path.join(paths.sessionRoot, "pr_outcome.json")),
|
|
804
|
-
readTextIfExists(path.join(paths.sessionRoot, "main_checkout_sync.json")),
|
|
805
|
-
readTextIfExists(path.join(paths.sessionRoot, "changes_committed.json"))
|
|
806
|
-
]);
|
|
807
|
-
let githubComments = {};
|
|
808
|
-
if (githubCommentsText) {
|
|
809
|
-
try {
|
|
810
|
-
const parsed = JSON.parse(githubCommentsText);
|
|
811
|
-
githubComments = parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : {};
|
|
812
|
-
} catch {
|
|
813
|
-
githubComments = {};
|
|
814
|
-
}
|
|
815
|
-
}
|
|
816
|
-
let prOutcome = null;
|
|
817
|
-
if (prOutcomeText) {
|
|
818
|
-
try {
|
|
819
|
-
const parsed = JSON.parse(prOutcomeText);
|
|
820
|
-
prOutcome = parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : null;
|
|
821
|
-
} catch {
|
|
822
|
-
prOutcome = null;
|
|
823
|
-
}
|
|
824
|
-
}
|
|
825
|
-
let mainCheckoutSync = null;
|
|
826
|
-
if (mainCheckoutSyncText) {
|
|
827
|
-
try {
|
|
828
|
-
const parsed = JSON.parse(mainCheckoutSyncText);
|
|
829
|
-
mainCheckoutSync = parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : null;
|
|
830
|
-
} catch {
|
|
831
|
-
mainCheckoutSync = null;
|
|
832
|
-
}
|
|
833
|
-
}
|
|
834
|
-
let acceptedChangesCommit = null;
|
|
835
|
-
if (changesCommittedText) {
|
|
836
|
-
try {
|
|
837
|
-
const parsed = JSON.parse(changesCommittedText);
|
|
838
|
-
acceptedChangesCommit = parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : null;
|
|
839
|
-
} catch {
|
|
840
|
-
acceptedChangesCommit = null;
|
|
841
|
-
}
|
|
842
|
-
}
|
|
843
|
-
const warnings = acceptedChangesCommit?.noChanges === true
|
|
844
|
-
? [ACCEPTED_CHANGES_NOOP_WARNING]
|
|
845
|
-
: [];
|
|
846
|
-
const cycles = await readCycles(paths, activeCycle);
|
|
847
|
-
const checks = await readStructuredChecks(paths);
|
|
848
|
-
const uiChecks = await readStructuredUiChecks(paths);
|
|
849
|
-
const reviewPasses = await readReviewPasses(paths);
|
|
850
|
-
const worktreeReady = await hasWorktree(paths);
|
|
851
|
-
const worktreeStatus = await readWorktreeStatus(paths, worktreeReady);
|
|
852
|
-
const commandLogPath = path.join(paths.sessionRoot, "command_log.jsonl");
|
|
853
|
-
const dependencyInstallRecord = await readTextIfExists(path.join(paths.sessionRoot, "steps", "dependencies_installed"));
|
|
854
|
-
const dependencyInstallResult = await readTextIfExists(path.join(paths.sessionRoot, DEPENDENCIES_INSTALL_RESULT_FILE));
|
|
855
|
-
const appRootForArtifacts = worktreeReady ? paths.worktree : paths.targetRoot;
|
|
856
|
-
const appReady = await inspectReadyJskitAppRoot(appRootForArtifacts);
|
|
857
|
-
const blueprintPath = path.join(appRootForArtifacts, ".jskit", "APP_BLUEPRINT.md");
|
|
858
|
-
const helperMapPath = path.join(appRootForArtifacts, ".jskit", "helper-map.md");
|
|
859
|
-
const currentStep = normalizeStepId(rawCurrentStep);
|
|
860
|
-
let completedSteps = await readCompletedSteps(paths);
|
|
861
|
-
const worktreeRemovalCompleted = completedSteps.includes("session_finished");
|
|
862
|
-
const worktreeStepRecordInvalid = !worktreeReady &&
|
|
863
|
-
completedSteps.includes("worktree_created") &&
|
|
864
|
-
!worktreeRemovalCompleted &&
|
|
865
|
-
status !== SESSION_STATUS.FINISHED &&
|
|
866
|
-
status !== SESSION_STATUS.ABANDONED;
|
|
867
|
-
if (worktreeStepRecordInvalid) {
|
|
868
|
-
completedSteps = completedSteps.filter((stepId) => !["worktree_created", "dependencies_installed"].includes(stepId));
|
|
869
|
-
}
|
|
870
|
-
const nextStep = resolveNextStep(completedSteps);
|
|
871
|
-
const currentStepIndex = stepIndex(currentStep);
|
|
872
|
-
const nextStepIndex = stepIndex(nextStep);
|
|
873
|
-
const effectiveCurrentStep = nextStep &&
|
|
874
|
-
(completedSteps.includes(currentStep) || currentStepIndex < 0 || currentStepIndex > nextStepIndex)
|
|
875
|
-
? nextStep
|
|
876
|
-
: currentStep || nextStep;
|
|
877
|
-
const prompt = await readPromptForStep(paths, effectiveCurrentStep, { reviewPasses });
|
|
878
|
-
const dependencyInstallDetails = dependencyInstallRecord.trim() || dependencyInstallResult.trim();
|
|
879
|
-
const dependencyInstallReady = Boolean(dependencyInstallRecord.trim() || dependencyInstallResult.trim());
|
|
880
|
-
|
|
881
|
-
return {
|
|
882
|
-
codexThreadId,
|
|
883
|
-
completedSteps,
|
|
884
|
-
currentStep: effectiveCurrentStep,
|
|
885
|
-
activeCycle,
|
|
886
|
-
appReady,
|
|
887
|
-
baseBranch,
|
|
888
|
-
baseCommit,
|
|
889
|
-
blueprintExists: await fileExists(blueprintPath),
|
|
890
|
-
blueprintPath,
|
|
891
|
-
cycles,
|
|
892
|
-
checks,
|
|
893
|
-
uiChecks,
|
|
894
|
-
reviewPasses,
|
|
895
|
-
currentReviewPass: reviewPasses.at(-1)?.pass || "",
|
|
896
|
-
commandLogExists: await fileExists(commandLogPath),
|
|
897
|
-
commandLogPath,
|
|
898
|
-
dependencyInstall: {
|
|
899
|
-
installed: Boolean(dependencyInstallRecord.trim()),
|
|
900
|
-
ready: dependencyInstallReady,
|
|
901
|
-
details: dependencyInstallDetails,
|
|
902
|
-
status: dependencyInstallRecord.trim()
|
|
903
|
-
? "installed"
|
|
904
|
-
: dependencyInstallResult.trim() ? "ready_to_advance"
|
|
905
|
-
: worktreeReady ? "pending" : "waiting_for_worktree"
|
|
906
|
-
},
|
|
907
|
-
helperMapExists: await fileExists(helperMapPath),
|
|
908
|
-
helperMapPath,
|
|
909
|
-
githubComments,
|
|
910
|
-
issueNumber: issueNumber || issueNumberFromUrl(issueUrl),
|
|
911
|
-
issueTitle,
|
|
912
|
-
issueText: issueText.trim(),
|
|
913
|
-
issueUrl,
|
|
914
|
-
nextStep,
|
|
915
|
-
pullRequestPath: path.join(paths.sessionRoot, "pull_request.md"),
|
|
916
|
-
pullRequestText: pullRequestText.trim(),
|
|
917
|
-
prUrl,
|
|
918
|
-
prOutcome,
|
|
919
|
-
mainCheckoutSync,
|
|
920
|
-
acceptedChangesCommit,
|
|
921
|
-
issueDefinitionRequested: Boolean(issueDefinitionRequested.trim()),
|
|
922
|
-
issueFileRequested: Boolean(issueFileRequested.trim()),
|
|
923
|
-
pullRequestFileRequested: Boolean(pullRequestFileRequested.trim()),
|
|
924
|
-
makePlanRequested: Boolean(makePlanRequested.trim()),
|
|
925
|
-
blueprintUpdateRequested: Boolean(blueprintUpdateRequested.trim()),
|
|
926
|
-
executePlanRequested: Boolean(executePlanRequested.trim()),
|
|
927
|
-
planExecution: {
|
|
928
|
-
prompted: Boolean(executePlanRequested.trim()),
|
|
929
|
-
promptPath: "",
|
|
930
|
-
details: planExecutionRecord.trim(),
|
|
931
|
-
submitted: Boolean(planExecutionRecord.trim())
|
|
932
|
-
},
|
|
933
|
-
finalReportText: finalReportText.trim(),
|
|
934
|
-
prompt: prompt.trim(),
|
|
935
|
-
status: status || SESSION_STATUS.PENDING,
|
|
936
|
-
warnings,
|
|
937
|
-
workflowVersion,
|
|
938
|
-
worktreeReady,
|
|
939
|
-
worktreeStatus
|
|
940
|
-
};
|
|
941
|
-
}
|
|
942
|
-
|
|
943
|
-
function stepCanExposeNextCommand(stepId, artifacts = {}) {
|
|
944
|
-
if (!stepId) {
|
|
945
|
-
return false;
|
|
946
|
-
}
|
|
947
|
-
if (stepId === "worktree_created") {
|
|
948
|
-
return artifacts.worktreeReady === true;
|
|
949
|
-
}
|
|
950
|
-
if (stepId === "dependencies_installed") {
|
|
951
|
-
return artifacts.dependencyInstall?.ready === true;
|
|
952
|
-
}
|
|
953
|
-
if (stepId === "issue_prompt_rendered") {
|
|
954
|
-
return Boolean(artifacts.issueText);
|
|
955
|
-
}
|
|
956
|
-
if (stepId === "issue_created") {
|
|
957
|
-
return Boolean(artifacts.issueText);
|
|
958
|
-
}
|
|
959
|
-
if (stepId === "issue_submitted") {
|
|
960
|
-
return Boolean(artifacts.issueUrl);
|
|
961
|
-
}
|
|
962
|
-
if (stepId === "changes_committed") {
|
|
963
|
-
return Boolean(artifacts.acceptedChangesCommit?.commit);
|
|
964
|
-
}
|
|
965
|
-
if (stepId === "final_report_created") {
|
|
966
|
-
return Boolean(artifacts.pullRequestText);
|
|
967
|
-
}
|
|
968
|
-
if (stepId === "session_finished") {
|
|
969
|
-
return false;
|
|
970
|
-
}
|
|
971
|
-
if (stepId === "plan_made") {
|
|
972
|
-
return artifacts.makePlanRequested === true;
|
|
973
|
-
}
|
|
974
|
-
if (stepId === "plan_executed") {
|
|
975
|
-
return artifacts.executePlanRequested === true;
|
|
976
|
-
}
|
|
977
|
-
return true;
|
|
978
|
-
}
|
|
979
|
-
|
|
980
|
-
function buildNextCommand(sessionId, stepId, artifacts = {}) {
|
|
981
|
-
if (!stepCanExposeNextCommand(stepId, artifacts)) {
|
|
982
|
-
return "";
|
|
983
|
-
}
|
|
984
|
-
const template = STEP_DEFINITION_BY_ID[stepId]?.nextCommandTemplate || `${JSKIT_CLI_SHELL_COMMAND} session {{session_id}} next`;
|
|
985
|
-
return template.replaceAll("{{session_id}}", sessionId);
|
|
986
|
-
}
|
|
987
|
-
|
|
988
|
-
function buildStepActionCommands(sessionId, stepId, artifacts = {}) {
|
|
989
|
-
const commandBase = `${JSKIT_CLI_SHELL_COMMAND} session ${sessionId}`;
|
|
990
|
-
if (stepId === "worktree_created") {
|
|
991
|
-
return artifacts.worktreeReady === true
|
|
992
|
-
? []
|
|
993
|
-
: [
|
|
994
|
-
{
|
|
995
|
-
command: `${commandBase} create_worktree`,
|
|
996
|
-
id: "create_worktree",
|
|
997
|
-
label: "Create worktree"
|
|
998
|
-
}
|
|
999
|
-
];
|
|
1000
|
-
}
|
|
1001
|
-
if (stepId === "dependencies_installed") {
|
|
1002
|
-
return artifacts.dependencyInstall?.ready === true
|
|
1003
|
-
? []
|
|
1004
|
-
: [
|
|
1005
|
-
{
|
|
1006
|
-
command: `${commandBase} run_npm_install`,
|
|
1007
|
-
id: "run_npm_install",
|
|
1008
|
-
label: "Run npm install"
|
|
1009
|
-
}
|
|
1010
|
-
];
|
|
1011
|
-
}
|
|
1012
|
-
if (stepId === "issue_prompt_rendered") {
|
|
1013
|
-
return [
|
|
1014
|
-
{
|
|
1015
|
-
command: `${commandBase} define_issue --prompt "<what should change>"`,
|
|
1016
|
-
id: "define_issue",
|
|
1017
|
-
label: "Define issue"
|
|
1018
|
-
},
|
|
1019
|
-
...(artifacts.issueDefinitionRequested
|
|
1020
|
-
? [
|
|
1021
|
-
{
|
|
1022
|
-
command: `${commandBase} create_issue_file`,
|
|
1023
|
-
id: "create_issue_file",
|
|
1024
|
-
label: "Create issue file"
|
|
1025
|
-
}
|
|
1026
|
-
]
|
|
1027
|
-
: [])
|
|
1028
|
-
];
|
|
1029
|
-
}
|
|
1030
|
-
if (stepId === "issue_created") {
|
|
1031
|
-
return [
|
|
1032
|
-
{
|
|
1033
|
-
command: `${commandBase} create_issue_file`,
|
|
1034
|
-
id: "create_issue_file",
|
|
1035
|
-
label: "Create issue file"
|
|
1036
|
-
}
|
|
1037
|
-
];
|
|
1038
|
-
}
|
|
1039
|
-
if (stepId === "issue_submitted") {
|
|
1040
|
-
return artifacts.issueUrl
|
|
1041
|
-
? []
|
|
1042
|
-
: [
|
|
1043
|
-
{
|
|
1044
|
-
command: `${commandBase} create_issue_on_gh`,
|
|
1045
|
-
id: "create_issue_on_gh",
|
|
1046
|
-
label: "Create issue on GH"
|
|
1047
|
-
}
|
|
1048
|
-
];
|
|
1049
|
-
}
|
|
1050
|
-
if (stepId === "plan_made") {
|
|
1051
|
-
return [
|
|
1052
|
-
{
|
|
1053
|
-
command: `${commandBase} make_plan`,
|
|
1054
|
-
id: "make_plan",
|
|
1055
|
-
label: "Make plan"
|
|
1056
|
-
}
|
|
1057
|
-
];
|
|
1058
|
-
}
|
|
1059
|
-
if (stepId === "plan_executed") {
|
|
1060
|
-
return [
|
|
1061
|
-
{
|
|
1062
|
-
command: `${commandBase} execute_plan`,
|
|
1063
|
-
id: "execute_plan",
|
|
1064
|
-
label: "Execute plan"
|
|
1065
|
-
}
|
|
1066
|
-
];
|
|
1067
|
-
}
|
|
1068
|
-
if (stepId === "deep_ui_check_run") {
|
|
1069
|
-
return [
|
|
1070
|
-
{
|
|
1071
|
-
command: `${commandBase} run_deep_ui_check`,
|
|
1072
|
-
id: "run_deep_ui_check",
|
|
1073
|
-
label: "Run deep UI check"
|
|
1074
|
-
}
|
|
1075
|
-
];
|
|
1076
|
-
}
|
|
1077
|
-
if (stepId === "automated_checks_run") {
|
|
1078
|
-
return [
|
|
1079
|
-
{
|
|
1080
|
-
command: `${commandBase} run_automated_checks`,
|
|
1081
|
-
id: "run_automated_checks",
|
|
1082
|
-
label: "Run automated checks"
|
|
1083
|
-
}
|
|
1084
|
-
];
|
|
1085
|
-
}
|
|
1086
|
-
if (stepId === "review_prompt_rendered") {
|
|
1087
|
-
return [
|
|
1088
|
-
{
|
|
1089
|
-
command: `${commandBase} deslop`,
|
|
1090
|
-
id: "deslop",
|
|
1091
|
-
label: "Run deslop"
|
|
1092
|
-
},
|
|
1093
|
-
{
|
|
1094
|
-
command: `${commandBase} resolve-deslop`,
|
|
1095
|
-
id: "resolve_deslop",
|
|
1096
|
-
label: "Resolve deslop"
|
|
1097
|
-
}
|
|
1098
|
-
];
|
|
1099
|
-
}
|
|
1100
|
-
if (stepId === "blueprint_updated") {
|
|
1101
|
-
return [
|
|
1102
|
-
{
|
|
1103
|
-
command: `${commandBase} update_blueprint`,
|
|
1104
|
-
id: "update_blueprint",
|
|
1105
|
-
label: "Update blueprint"
|
|
1106
|
-
}
|
|
1107
|
-
];
|
|
1108
|
-
}
|
|
1109
|
-
if (stepId === "changes_committed") {
|
|
1110
|
-
return artifacts.acceptedChangesCommit?.commit
|
|
1111
|
-
? []
|
|
1112
|
-
: [
|
|
1113
|
-
{
|
|
1114
|
-
command: `${commandBase} commit_changes`,
|
|
1115
|
-
id: "commit_changes",
|
|
1116
|
-
label: "Commit changes"
|
|
1117
|
-
}
|
|
1118
|
-
];
|
|
1119
|
-
}
|
|
1120
|
-
if (stepId === "final_report_created") {
|
|
1121
|
-
return artifacts.pullRequestText
|
|
1122
|
-
? []
|
|
1123
|
-
: [
|
|
1124
|
-
{
|
|
1125
|
-
command: `${commandBase} create_pull_request_file`,
|
|
1126
|
-
id: "create_pull_request_file",
|
|
1127
|
-
label: "Create PR file"
|
|
1128
|
-
}
|
|
1129
|
-
];
|
|
1130
|
-
}
|
|
1131
|
-
if (stepId === "pr_created") {
|
|
1132
|
-
return artifacts.prUrl
|
|
1133
|
-
? []
|
|
1134
|
-
: [
|
|
1135
|
-
{
|
|
1136
|
-
command: `${commandBase} create_pr_on_gh`,
|
|
1137
|
-
id: "create_pr_on_gh",
|
|
1138
|
-
label: "Create PR on GH"
|
|
1139
|
-
}
|
|
1140
|
-
];
|
|
1141
|
-
}
|
|
1142
|
-
if (stepId === "pr_merge_prepared") {
|
|
1143
|
-
return artifacts.prOutcome?.outcome === "merged" || !artifacts.prUrl
|
|
1144
|
-
? []
|
|
1145
|
-
: [
|
|
1146
|
-
{
|
|
1147
|
-
command: `${commandBase} prepare_for_merge`,
|
|
1148
|
-
id: "prepare_for_merge",
|
|
1149
|
-
label: "Prepare for merge"
|
|
1150
|
-
},
|
|
1151
|
-
{
|
|
1152
|
-
command: `${commandBase} merge_pr`,
|
|
1153
|
-
id: "merge_pr",
|
|
1154
|
-
label: "Merge"
|
|
1155
|
-
}
|
|
1156
|
-
];
|
|
1157
|
-
}
|
|
1158
|
-
if (stepId === "main_checkout_synced") {
|
|
1159
|
-
return artifacts.prUrl && artifacts.prOutcome?.outcome === "merged" && !artifacts.mainCheckoutSync?.status
|
|
1160
|
-
? [
|
|
1161
|
-
{
|
|
1162
|
-
command: `${commandBase} sync_main_checkout`,
|
|
1163
|
-
id: "sync_main_checkout",
|
|
1164
|
-
label: "Sync main checkout"
|
|
1165
|
-
}
|
|
1166
|
-
]
|
|
1167
|
-
: [];
|
|
1168
|
-
}
|
|
1169
|
-
if (stepId === "session_finished") {
|
|
1170
|
-
return [
|
|
1171
|
-
{
|
|
1172
|
-
command: `${commandBase} finish_session`,
|
|
1173
|
-
id: "finish_session",
|
|
1174
|
-
label: "Finish"
|
|
1175
|
-
}
|
|
1176
|
-
];
|
|
1177
|
-
}
|
|
1178
|
-
return [];
|
|
1179
|
-
}
|
|
1180
|
-
|
|
1181
|
-
async function buildSessionResponse(paths, {
|
|
1182
|
-
codex = undefined,
|
|
1183
|
-
ok = true,
|
|
1184
|
-
errors = [],
|
|
1185
|
-
preconditions = [],
|
|
1186
|
-
prompt = undefined,
|
|
1187
|
-
status = undefined,
|
|
1188
|
-
warnings = []
|
|
1189
|
-
} = {}) {
|
|
1190
|
-
const responsePaths = paths.sessionId ? await pathsForExistingSession(paths) : paths;
|
|
1191
|
-
const artifacts = responsePaths.sessionRoot ? await readSessionArtifacts(responsePaths) : {};
|
|
1192
|
-
const resolvedStatus = status || artifacts.status || (ok ? SESSION_STATUS.PENDING : SESSION_STATUS.BLOCKED);
|
|
1193
|
-
if (responsePaths.sessionRoot && await fileExists(responsePaths.sessionRoot) && artifacts.workflowVersion !== SESSION_WORKFLOW_VERSION) {
|
|
1194
|
-
return {
|
|
1195
|
-
ok: false,
|
|
1196
|
-
sessionId: paths.sessionId || "",
|
|
1197
|
-
status: SESSION_STATUS.BLOCKED,
|
|
1198
|
-
currentStep: "",
|
|
1199
|
-
completedSteps: artifacts.completedSteps || [],
|
|
1200
|
-
workflowVersion: artifacts.workflowVersion || "",
|
|
1201
|
-
baseBranch: artifacts.baseBranch || "",
|
|
1202
|
-
baseCommit: artifacts.baseCommit || "",
|
|
1203
|
-
blueprintPath: artifacts.blueprintPath || "",
|
|
1204
|
-
blueprintExists: artifacts.blueprintExists === true,
|
|
1205
|
-
activeCycle: artifacts.activeCycle || "",
|
|
1206
|
-
appReady: cloneContractValue(artifacts.appReady || null),
|
|
1207
|
-
cycles: cloneContractValue(artifacts.cycles || []),
|
|
1208
|
-
checks: cloneContractValue(artifacts.checks || []),
|
|
1209
|
-
dependencyInstall: cloneContractValue(artifacts.dependencyInstall || null),
|
|
1210
|
-
uiChecks: cloneContractValue(artifacts.uiChecks || []),
|
|
1211
|
-
reviewPasses: cloneContractValue(artifacts.reviewPasses || []),
|
|
1212
|
-
currentReviewPass: artifacts.currentReviewPass || "",
|
|
1213
|
-
commandLogExists: artifacts.commandLogExists === true,
|
|
1214
|
-
commandLogPath: artifacts.commandLogPath || "",
|
|
1215
|
-
stepDefinitions: buildStepDefinitions(),
|
|
1216
|
-
currentStepAction: null,
|
|
1217
|
-
codex: null,
|
|
1218
|
-
prompt: "",
|
|
1219
|
-
nextCommand: "",
|
|
1220
|
-
issueDefinitionRequested: artifacts.issueDefinitionRequested === true,
|
|
1221
|
-
issueFileRequested: artifacts.issueFileRequested === true,
|
|
1222
|
-
issueNumber: artifacts.issueNumber || "",
|
|
1223
|
-
issueUrl: artifacts.issueUrl || "",
|
|
1224
|
-
issueTitle: artifacts.issueTitle || "",
|
|
1225
|
-
issueText: artifacts.issueText || "",
|
|
1226
|
-
pullRequestFileRequested: artifacts.pullRequestFileRequested === true,
|
|
1227
|
-
pullRequestPath: artifacts.pullRequestPath || "",
|
|
1228
|
-
pullRequestText: artifacts.pullRequestText || "",
|
|
1229
|
-
githubComments: cloneContractValue(artifacts.githubComments || {}),
|
|
1230
|
-
makePlanRequested: artifacts.makePlanRequested === true,
|
|
1231
|
-
blueprintUpdateRequested: artifacts.blueprintUpdateRequested === true,
|
|
1232
|
-
executePlanRequested: artifacts.executePlanRequested === true,
|
|
1233
|
-
planExecution: cloneContractValue(artifacts.planExecution || null),
|
|
1234
|
-
finalReportPath: artifacts.finalReportText ? path.join(responsePaths.sessionRoot, "final_report") : "",
|
|
1235
|
-
finalReportText: artifacts.finalReportText || "",
|
|
1236
|
-
helperMapPath: artifacts.helperMapPath || "",
|
|
1237
|
-
helperMapExists: artifacts.helperMapExists === true,
|
|
1238
|
-
prUrl: artifacts.prUrl || "",
|
|
1239
|
-
prOutcome: cloneContractValue(artifacts.prOutcome || null),
|
|
1240
|
-
mainCheckoutSync: cloneContractValue(artifacts.mainCheckoutSync || null),
|
|
1241
|
-
acceptedChangesCommit: cloneContractValue(artifacts.acceptedChangesCommit || null),
|
|
1242
|
-
preconditions,
|
|
1243
|
-
errors: [
|
|
1244
|
-
createError({
|
|
1245
|
-
code: "unsupported_workflow_version",
|
|
1246
|
-
message: `Session ${paths.sessionId || ""} uses workflow version ${artifacts.workflowVersion || "missing"}, but this JSKIT runtime expects ${SESSION_WORKFLOW_VERSION}.`
|
|
1247
|
-
})
|
|
1248
|
-
],
|
|
1249
|
-
warnings: [],
|
|
1250
|
-
archive: responsePaths.archive || "active",
|
|
1251
|
-
sessionRoot: responsePaths.sessionRoot || "",
|
|
1252
|
-
worktree: paths.worktree || "",
|
|
1253
|
-
worktreeReady: artifacts.worktreeReady === true,
|
|
1254
|
-
worktreeStatus: cloneContractValue(artifacts.worktreeStatus || null),
|
|
1255
|
-
branch: paths.branch || "",
|
|
1256
|
-
codexThreadId: artifacts.codexThreadId || ""
|
|
1257
|
-
};
|
|
1258
|
-
}
|
|
1259
|
-
const currentStep = artifacts.currentStep || artifacts.nextStep || "";
|
|
1260
|
-
const responsePrompt = typeof prompt === "string"
|
|
1261
|
-
? prompt
|
|
1262
|
-
: stepCanExposeStoredPrompt(currentStep) ? artifacts.prompt || "" : "";
|
|
1263
|
-
const responseWarnings = mergeWarnings(artifacts.warnings || [], warnings);
|
|
1264
|
-
|
|
1265
|
-
return {
|
|
1266
|
-
ok: ok === true,
|
|
1267
|
-
sessionId: paths.sessionId || "",
|
|
1268
|
-
status: resolvedStatus,
|
|
1269
|
-
currentStep,
|
|
1270
|
-
completedSteps: artifacts.completedSteps || [],
|
|
1271
|
-
workflowVersion: artifacts.workflowVersion || "",
|
|
1272
|
-
baseBranch: artifacts.baseBranch || "",
|
|
1273
|
-
baseCommit: artifacts.baseCommit || "",
|
|
1274
|
-
blueprintPath: artifacts.blueprintPath || "",
|
|
1275
|
-
blueprintExists: artifacts.blueprintExists === true,
|
|
1276
|
-
activeCycle: artifacts.activeCycle || "",
|
|
1277
|
-
appReady: cloneContractValue(artifacts.appReady || null),
|
|
1278
|
-
cycles: cloneContractValue(artifacts.cycles || []),
|
|
1279
|
-
checks: cloneContractValue(artifacts.checks || []),
|
|
1280
|
-
dependencyInstall: cloneContractValue(artifacts.dependencyInstall || null),
|
|
1281
|
-
uiChecks: cloneContractValue(artifacts.uiChecks || []),
|
|
1282
|
-
reviewPasses: cloneContractValue(artifacts.reviewPasses || []),
|
|
1283
|
-
currentReviewPass: artifacts.currentReviewPass || "",
|
|
1284
|
-
commandLogExists: artifacts.commandLogExists === true,
|
|
1285
|
-
commandLogPath: artifacts.commandLogPath || "",
|
|
1286
|
-
stepDefinitions: buildStepDefinitions(),
|
|
1287
|
-
currentStepAction: buildCurrentStepAction(currentStep, artifacts),
|
|
1288
|
-
actionCommands: buildStepActionCommands(paths.sessionId || "", currentStep, artifacts),
|
|
1289
|
-
codex: codex === undefined ? await buildCodexHandoff(currentStep, artifacts) : await publicCodexContract(codex),
|
|
1290
|
-
prompt: responsePrompt,
|
|
1291
|
-
nextCommand: buildNextCommand(paths.sessionId || "", currentStep, artifacts),
|
|
1292
|
-
issueDefinitionRequested: artifacts.issueDefinitionRequested === true,
|
|
1293
|
-
issueFileRequested: artifacts.issueFileRequested === true,
|
|
1294
|
-
issueNumber: artifacts.issueNumber || "",
|
|
1295
|
-
issueUrl: artifacts.issueUrl || "",
|
|
1296
|
-
issueTitle: artifacts.issueTitle || "",
|
|
1297
|
-
issueText: artifacts.issueText || "",
|
|
1298
|
-
pullRequestFileRequested: artifacts.pullRequestFileRequested === true,
|
|
1299
|
-
pullRequestPath: artifacts.pullRequestPath || "",
|
|
1300
|
-
pullRequestText: artifacts.pullRequestText || "",
|
|
1301
|
-
githubComments: cloneContractValue(artifacts.githubComments || {}),
|
|
1302
|
-
makePlanRequested: artifacts.makePlanRequested === true,
|
|
1303
|
-
blueprintUpdateRequested: artifacts.blueprintUpdateRequested === true,
|
|
1304
|
-
executePlanRequested: artifacts.executePlanRequested === true,
|
|
1305
|
-
planExecution: cloneContractValue(artifacts.planExecution || null),
|
|
1306
|
-
finalReportPath: artifacts.finalReportText ? path.join(responsePaths.sessionRoot, "final_report") : "",
|
|
1307
|
-
finalReportText: artifacts.finalReportText || "",
|
|
1308
|
-
helperMapPath: artifacts.helperMapPath || "",
|
|
1309
|
-
helperMapExists: artifacts.helperMapExists === true,
|
|
1310
|
-
prUrl: artifacts.prUrl || "",
|
|
1311
|
-
prOutcome: cloneContractValue(artifacts.prOutcome || null),
|
|
1312
|
-
mainCheckoutSync: cloneContractValue(artifacts.mainCheckoutSync || null),
|
|
1313
|
-
acceptedChangesCommit: cloneContractValue(artifacts.acceptedChangesCommit || null),
|
|
1314
|
-
preconditions,
|
|
1315
|
-
errors,
|
|
1316
|
-
warnings: responseWarnings,
|
|
1317
|
-
archive: responsePaths.archive || (resolvedStatus === SESSION_STATUS.FINISHED ? "completed" : resolvedStatus === SESSION_STATUS.ABANDONED ? "abandoned" : "active"),
|
|
1318
|
-
sessionRoot: responsePaths.sessionRoot || "",
|
|
1319
|
-
worktree: paths.worktree || "",
|
|
1320
|
-
worktreeReady: artifacts.worktreeReady === true,
|
|
1321
|
-
worktreeStatus: cloneContractValue(artifacts.worktreeStatus || null),
|
|
1322
|
-
branch: paths.branch || "",
|
|
1323
|
-
codexThreadId: artifacts.codexThreadId || ""
|
|
1324
|
-
};
|
|
1325
|
-
}
|
|
1326
|
-
|
|
1327
|
-
function buildSessionErrorResponse({
|
|
1328
|
-
targetRoot = process.cwd(),
|
|
1329
|
-
sessionId = "",
|
|
1330
|
-
code,
|
|
1331
|
-
message,
|
|
1332
|
-
repairCommand = "",
|
|
1333
|
-
status = SESSION_STATUS.BLOCKED,
|
|
1334
|
-
preconditions = [],
|
|
1335
|
-
errors = undefined
|
|
1336
|
-
} = {}) {
|
|
1337
|
-
const normalizedTargetRoot = path.resolve(normalizeText(targetRoot) || process.cwd());
|
|
1338
|
-
const errorList = Array.isArray(errors)
|
|
1339
|
-
? errors
|
|
1340
|
-
: [
|
|
1341
|
-
createError({
|
|
1342
|
-
code,
|
|
1343
|
-
message,
|
|
1344
|
-
repairCommand
|
|
1345
|
-
})
|
|
1346
|
-
];
|
|
1347
|
-
|
|
1348
|
-
return {
|
|
1349
|
-
ok: false,
|
|
1350
|
-
sessionId: normalizeText(sessionId),
|
|
1351
|
-
status,
|
|
1352
|
-
currentStep: "",
|
|
1353
|
-
completedSteps: [],
|
|
1354
|
-
workflowVersion: "",
|
|
1355
|
-
baseBranch: "",
|
|
1356
|
-
baseCommit: "",
|
|
1357
|
-
blueprintPath: "",
|
|
1358
|
-
blueprintExists: false,
|
|
1359
|
-
activeCycle: "",
|
|
1360
|
-
appReady: null,
|
|
1361
|
-
cycles: [],
|
|
1362
|
-
checks: [],
|
|
1363
|
-
dependencyInstall: null,
|
|
1364
|
-
uiChecks: [],
|
|
1365
|
-
reviewPasses: [],
|
|
1366
|
-
currentReviewPass: "",
|
|
1367
|
-
commandLogExists: false,
|
|
1368
|
-
commandLogPath: "",
|
|
1369
|
-
stepDefinitions: buildStepDefinitions(),
|
|
1370
|
-
currentStepAction: null,
|
|
1371
|
-
codex: null,
|
|
1372
|
-
prompt: "",
|
|
1373
|
-
nextCommand: "",
|
|
1374
|
-
issueTitle: "",
|
|
1375
|
-
issueText: "",
|
|
1376
|
-
pullRequestFileRequested: false,
|
|
1377
|
-
pullRequestPath: "",
|
|
1378
|
-
pullRequestText: "",
|
|
1379
|
-
githubComments: {},
|
|
1380
|
-
planExecution: null,
|
|
1381
|
-
finalReportPath: "",
|
|
1382
|
-
finalReportText: "",
|
|
1383
|
-
helperMapPath: "",
|
|
1384
|
-
helperMapExists: false,
|
|
1385
|
-
issueUrl: "",
|
|
1386
|
-
prUrl: "",
|
|
1387
|
-
prOutcome: null,
|
|
1388
|
-
preconditions,
|
|
1389
|
-
errors: errorList,
|
|
1390
|
-
warnings: [],
|
|
1391
|
-
archive: "",
|
|
1392
|
-
sessionRoot: "",
|
|
1393
|
-
worktree: "",
|
|
1394
|
-
worktreeReady: false,
|
|
1395
|
-
worktreeStatus: null,
|
|
1396
|
-
branch: "",
|
|
1397
|
-
codexThreadId: "",
|
|
1398
|
-
targetRoot: normalizedTargetRoot
|
|
1399
|
-
};
|
|
1400
|
-
}
|
|
1401
|
-
|
|
1402
|
-
async function markStatus(paths, status) {
|
|
1403
|
-
await writeTextFile(path.join(paths.sessionRoot, "status"), status);
|
|
1404
|
-
}
|
|
1405
|
-
|
|
1406
|
-
async function markCurrentStep(paths, stepId) {
|
|
1407
|
-
await writeTextFile(path.join(paths.sessionRoot, "current_step"), stepId);
|
|
1408
|
-
}
|
|
1409
|
-
|
|
1410
|
-
async function writeStepRecord(paths, stepId, message) {
|
|
1411
|
-
const root = isCycleStepId(stepId)
|
|
1412
|
-
? cycleStepsRoot(paths, await readActiveCycle(paths))
|
|
1413
|
-
: path.join(paths.sessionRoot, "steps");
|
|
1414
|
-
await mkdir(root, { recursive: true });
|
|
1415
|
-
await writeTextFile(
|
|
1416
|
-
path.join(root, stepId),
|
|
1417
|
-
`${timestampForStepRecord()}\n${normalizeText(message) || STEP_LABEL_BY_ID[stepId] || stepId}`
|
|
1418
|
-
);
|
|
1419
|
-
const completedSteps = await readCompletedSteps(paths);
|
|
1420
|
-
await markCurrentStep(paths, resolveNextStep(completedSteps));
|
|
1421
|
-
}
|
|
1422
|
-
|
|
1423
|
-
async function writeCycleStepRecord(paths, recordName, message, {
|
|
1424
|
-
cycle = ""
|
|
1425
|
-
} = {}) {
|
|
1426
|
-
const activeCycle = normalizeCycleNumber(cycle || await readActiveCycle(paths));
|
|
1427
|
-
const root = cycleStepsRoot(paths, activeCycle);
|
|
1428
|
-
await mkdir(root, { recursive: true });
|
|
1429
|
-
await writeTextFile(
|
|
1430
|
-
path.join(root, normalizeText(recordName)),
|
|
1431
|
-
`${timestampForStepRecord()}\n${normalizeText(message) || normalizeText(recordName)}`
|
|
1432
|
-
);
|
|
1433
|
-
}
|
|
1434
|
-
|
|
1435
|
-
async function failSession(paths, {
|
|
1436
|
-
code,
|
|
1437
|
-
message,
|
|
1438
|
-
repairCommand = "",
|
|
1439
|
-
preconditions = [],
|
|
1440
|
-
status = SESSION_STATUS.BLOCKED,
|
|
1441
|
-
prompt = "",
|
|
1442
|
-
codex = undefined
|
|
1443
|
-
}) {
|
|
1444
|
-
if (paths.sessionRoot && await fileExists(paths.sessionRoot)) {
|
|
1445
|
-
await markStatus(paths, status);
|
|
1446
|
-
}
|
|
1447
|
-
return buildSessionResponse(paths, {
|
|
1448
|
-
ok: false,
|
|
1449
|
-
status,
|
|
1450
|
-
prompt,
|
|
1451
|
-
codex,
|
|
1452
|
-
preconditions,
|
|
1453
|
-
errors: [
|
|
1454
|
-
createError({
|
|
1455
|
-
code,
|
|
1456
|
-
message,
|
|
1457
|
-
repairCommand
|
|
1458
|
-
})
|
|
1459
|
-
]
|
|
1460
|
-
});
|
|
1461
|
-
}
|
|
1462
|
-
|
|
1463
|
-
export {
|
|
1464
|
-
buildSessionErrorResponse,
|
|
1465
|
-
buildSessionResponse,
|
|
1466
|
-
buildStepDefinitions,
|
|
1467
|
-
createError,
|
|
1468
|
-
createPrecondition,
|
|
1469
|
-
failSession,
|
|
1470
|
-
markCurrentStep,
|
|
1471
|
-
markStatus,
|
|
1472
|
-
normalizeReviewPassNumber,
|
|
1473
|
-
readActiveCycle,
|
|
1474
|
-
readStepRecords,
|
|
1475
|
-
readReviewPasses,
|
|
1476
|
-
readSessionArtifacts,
|
|
1477
|
-
reviewPassDirectoryName,
|
|
1478
|
-
reviewPassRoot,
|
|
1479
|
-
writeCycleStepRecord,
|
|
1480
|
-
writeStepRecord
|
|
1481
|
-
};
|