@jskit-ai/jskit-cli 0.2.88 → 0.2.90
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 +173 -142
- package/src/server/core/argParser.js +0 -4
- package/src/server/core/commandCatalog.js +47 -35
- package/src/server/sessionRuntime/constants.js +130 -353
- package/src/server/sessionRuntime/io.js +2 -2
- package/src/server/sessionRuntime/preconditions.js +40 -144
- package/src/server/sessionRuntime/promptRenderer.js +2 -15
- package/src/server/sessionRuntime/prompts/app_blueprint.md +2 -7
- package/src/server/sessionRuntime/prompts/{automated_checks.md → automated_checks_run.md} +2 -17
- package/src/server/sessionRuntime/prompts/{update_blueprint.md → blueprint_updated.md} +2 -11
- package/src/server/sessionRuntime/prompts/{deep_ui_check.md → deep_ui_check_run.md} +2 -19
- package/src/server/sessionRuntime/prompts/final_report_created.md +44 -0
- package/src/server/sessionRuntime/prompts/issue_created.md +26 -0
- package/src/server/sessionRuntime/prompts/issue_prompt_rendered.md +1 -0
- package/src/server/sessionRuntime/prompts/{plan_issue.md → make_plan.md} +8 -37
- package/src/server/sessionRuntime/prompts/{execute_plan.md → plan_executed.md} +4 -29
- package/src/server/sessionRuntime/prompts/{prepare_pr_merge.md → pr_merge_prepared.md} +3 -3
- package/src/server/sessionRuntime/prompts/{resolve_deslop_findings.md → review_changes_accepted_resolve.md} +2 -6
- package/src/server/sessionRuntime/prompts/{review_changes.md → review_prompt_rendered.md} +3 -28
- package/src/server/sessionRuntime/prompts/{user_check.md → user_check_completed.md} +1 -11
- package/src/server/sessionRuntime/responses.js +504 -295
- package/src/server/sessionRuntime.js +1300 -961
- package/src/server/sessionRuntime/prompts/issue_details.md +0 -49
- package/src/server/sessionRuntime/prompts/new_issue.md +0 -46
|
@@ -2,8 +2,10 @@ import { mkdir, readdir } from "node:fs/promises";
|
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import {
|
|
4
4
|
CYCLE_STEP_IDS,
|
|
5
|
+
DEPENDENCIES_INSTALL_RESULT_FILE,
|
|
6
|
+
ISSUE_FILE_CODEX_HANDOFF,
|
|
7
|
+
ISSUE_DEFINITION_CODEX_HANDOFF,
|
|
5
8
|
JSKIT_CLI_SHELL_COMMAND,
|
|
6
|
-
PROMPT_DIRECTORY,
|
|
7
9
|
REVIEW_EXECUTION_CODEX_HANDOFF,
|
|
8
10
|
REVIEW_PASS_LIMIT,
|
|
9
11
|
SESSION_WORKFLOW_VERSION,
|
|
@@ -19,7 +21,7 @@ import {
|
|
|
19
21
|
readTextIfExists,
|
|
20
22
|
readTrimmedFile,
|
|
21
23
|
runGitInWorktree,
|
|
22
|
-
|
|
24
|
+
timestampForStepRecord,
|
|
23
25
|
writeTextFile
|
|
24
26
|
} from "./io.js";
|
|
25
27
|
import {
|
|
@@ -56,6 +58,24 @@ function createPrecondition({
|
|
|
56
58
|
});
|
|
57
59
|
}
|
|
58
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
|
+
|
|
59
79
|
function normalizeStepId(stepId) {
|
|
60
80
|
return normalizeText(stepId);
|
|
61
81
|
}
|
|
@@ -113,10 +133,6 @@ async function readActiveCycle(paths) {
|
|
|
113
133
|
return normalizeCycleNumber(cycle || DEFAULT_ACTIVE_CYCLE);
|
|
114
134
|
}
|
|
115
135
|
|
|
116
|
-
async function writeActiveCycle(paths, cycle) {
|
|
117
|
-
await writeTextFile(path.join(paths.sessionRoot, "active_cycle"), `${normalizeCycleNumber(cycle)}\n`);
|
|
118
|
-
}
|
|
119
|
-
|
|
120
136
|
function cycleStepsRoot(paths, cycle) {
|
|
121
137
|
return path.join(paths.sessionRoot, "steps", cycleDirectoryName(cycle));
|
|
122
138
|
}
|
|
@@ -125,18 +141,6 @@ function cycleRoot(paths, cycle) {
|
|
|
125
141
|
return path.join(paths.sessionRoot, "cycles", cycleDirectoryName(cycle));
|
|
126
142
|
}
|
|
127
143
|
|
|
128
|
-
function cyclePlanPath(paths, cycle) {
|
|
129
|
-
return path.join(cycleRoot(paths, cycle), "plan.md");
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
function cyclePlanPromptFileName(cycle) {
|
|
133
|
-
return `cycle_${normalizeCycleNumber(cycle)}_plan_request.md`;
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
function cyclePlanExecutionPromptFileName(cycle) {
|
|
137
|
-
return `cycle_${normalizeCycleNumber(cycle)}_plan_execution.md`;
|
|
138
|
-
}
|
|
139
|
-
|
|
140
144
|
function normalizeReviewPassNumber(value = "") {
|
|
141
145
|
const normalized = normalizeText(value).replace(/^pass_/u, "");
|
|
142
146
|
if (!/^\d+$/u.test(normalized)) {
|
|
@@ -191,7 +195,7 @@ async function readReviewPassInfo(paths, pass) {
|
|
|
191
195
|
pass: normalizedPass,
|
|
192
196
|
label: reviewPassDirectoryName(normalizedPass),
|
|
193
197
|
status,
|
|
194
|
-
promptPath: prompt?.promptPath || path.join(root, "
|
|
198
|
+
promptPath: prompt?.promptPath || path.join(root, "review_prompt_rendered"),
|
|
195
199
|
acceptedAt: accepted?.acceptedAt || "",
|
|
196
200
|
changedFiles,
|
|
197
201
|
commit: "",
|
|
@@ -235,27 +239,6 @@ async function readReviewPromptForStep(paths, artifacts = {}) {
|
|
|
235
239
|
return "";
|
|
236
240
|
}
|
|
237
241
|
|
|
238
|
-
const PROMPT_ARTIFACT_BY_STEP_ID = Object.freeze({
|
|
239
|
-
issue_drafted: "issue_draft.md",
|
|
240
|
-
issue_details_gathered: "issue_details.md",
|
|
241
|
-
deep_ui_check_run: "deep_ui_check_run.md",
|
|
242
|
-
automated_checks_run: "automated_checks_run.md",
|
|
243
|
-
blueprint_updated: "update_blueprint.md",
|
|
244
|
-
pr_merge_prepared: "prepare_pr_merge.md",
|
|
245
|
-
user_check_completed: "user_check.md"
|
|
246
|
-
});
|
|
247
|
-
|
|
248
|
-
async function promptArtifactForStep(paths, stepId) {
|
|
249
|
-
const normalizedStepId = normalizeStepId(stepId);
|
|
250
|
-
if (normalizedStepId === "plan_made") {
|
|
251
|
-
return cyclePlanPromptFileName(await readActiveCycle(paths));
|
|
252
|
-
}
|
|
253
|
-
if (normalizedStepId === "plan_executed") {
|
|
254
|
-
return cyclePlanExecutionPromptFileName(await readActiveCycle(paths));
|
|
255
|
-
}
|
|
256
|
-
return PROMPT_ARTIFACT_BY_STEP_ID[normalizedStepId] || "";
|
|
257
|
-
}
|
|
258
|
-
|
|
259
242
|
async function readPromptForStep(paths, stepId, artifacts = {}) {
|
|
260
243
|
if (!stepCanExposeStoredPrompt(stepId)) {
|
|
261
244
|
return "";
|
|
@@ -263,13 +246,6 @@ async function readPromptForStep(paths, stepId, artifacts = {}) {
|
|
|
263
246
|
if (REVIEW_STEP_IDS.includes(normalizeStepId(stepId))) {
|
|
264
247
|
return readReviewPromptForStep(paths, artifacts);
|
|
265
248
|
}
|
|
266
|
-
const promptArtifact = await promptArtifactForStep(paths, stepId);
|
|
267
|
-
if (promptArtifact) {
|
|
268
|
-
const prompt = await readTextIfExists(path.join(paths.sessionRoot, "prompts", promptArtifact));
|
|
269
|
-
if (prompt) {
|
|
270
|
-
return prompt;
|
|
271
|
-
}
|
|
272
|
-
}
|
|
273
249
|
return "";
|
|
274
250
|
}
|
|
275
251
|
|
|
@@ -286,11 +262,18 @@ async function readStepFileNames(stepsRoot) {
|
|
|
286
262
|
|
|
287
263
|
async function readCompletedSteps(paths) {
|
|
288
264
|
const stepsRoot = path.join(paths.sessionRoot, "steps");
|
|
289
|
-
const activeCycle = await readActiveCycle(paths);
|
|
290
265
|
const globalStepIds = normalizeKnownStepIds(
|
|
291
266
|
(await readStepFileNames(stepsRoot)).filter((stepId) => !isCycleStepId(stepId))
|
|
292
267
|
);
|
|
293
|
-
const cycleStepIds =
|
|
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
|
+
}
|
|
294
277
|
return applyReviewPassCompletionOverlay(paths, normalizeKnownStepIds([...globalStepIds, ...cycleStepIds]));
|
|
295
278
|
}
|
|
296
279
|
|
|
@@ -302,7 +285,6 @@ async function applyReviewPassCompletionOverlay(paths, completedSteps = []) {
|
|
|
302
285
|
const reviewPasses = await readReviewPasses(paths);
|
|
303
286
|
const latestPass = reviewPasses.at(-1);
|
|
304
287
|
if (!latestPass) {
|
|
305
|
-
REVIEW_STEP_IDS.forEach((stepId) => completed.delete(stepId));
|
|
306
288
|
return normalizeKnownStepIds([...completed]);
|
|
307
289
|
}
|
|
308
290
|
const latestPassAccepted = latestPass.status === "accepted" || latestPass.status === "no_changes";
|
|
@@ -311,11 +293,6 @@ async function applyReviewPassCompletionOverlay(paths, completedSteps = []) {
|
|
|
311
293
|
REVIEW_STEP_IDS.forEach((stepId) => completed.delete(stepId));
|
|
312
294
|
return normalizeKnownStepIds([...completed]);
|
|
313
295
|
}
|
|
314
|
-
if (latestPass.status === "prompted") {
|
|
315
|
-
completed.add("review_prompt_rendered");
|
|
316
|
-
completed.delete("review_changes_accepted");
|
|
317
|
-
return normalizeKnownStepIds([...completed]);
|
|
318
|
-
}
|
|
319
296
|
if (latestPass.status === "accepted" || latestPass.status === "no_changes") {
|
|
320
297
|
REVIEW_STEP_IDS.forEach((stepId) => completed.add(stepId));
|
|
321
298
|
}
|
|
@@ -327,7 +304,7 @@ async function readCycleInfo(paths, cycle) {
|
|
|
327
304
|
const root = cycleRoot(paths, normalizedCycle);
|
|
328
305
|
const userCheckPassed = await readTextIfExists(path.join(cycleStepsRoot(paths, normalizedCycle), "user_check_completed"));
|
|
329
306
|
const userCheckFailed = await readTextIfExists(path.join(cycleStepsRoot(paths, normalizedCycle), "user_check_failed"));
|
|
330
|
-
const reworkRequestPath = path.join(root, "rework_request
|
|
307
|
+
const reworkRequestPath = path.join(root, "rework_request");
|
|
331
308
|
const reworkRequest = await readTextIfExists(reworkRequestPath);
|
|
332
309
|
return {
|
|
333
310
|
cycle: normalizedCycle,
|
|
@@ -336,7 +313,7 @@ async function readCycleInfo(paths, cycle) {
|
|
|
336
313
|
reworkRequestPath: reworkRequest ? reworkRequestPath : "",
|
|
337
314
|
status: userCheckPassed ? "passed" : userCheckFailed ? "failed" : "active",
|
|
338
315
|
userCheckResult: userCheckPassed ? "passed" : userCheckFailed ? "failed" : "",
|
|
339
|
-
|
|
316
|
+
userCheckRecord: (userCheckPassed || userCheckFailed).trim()
|
|
340
317
|
};
|
|
341
318
|
}
|
|
342
319
|
|
|
@@ -438,7 +415,7 @@ async function readWorktreeStatus(paths, worktreeReady) {
|
|
|
438
415
|
};
|
|
439
416
|
}
|
|
440
417
|
|
|
441
|
-
async function
|
|
418
|
+
async function readStepRecords(paths) {
|
|
442
419
|
const stepsRoot = path.join(paths.sessionRoot, "steps");
|
|
443
420
|
try {
|
|
444
421
|
const entries = await readdir(stepsRoot, { withFileTypes: true });
|
|
@@ -447,19 +424,19 @@ async function readReceiptSteps(paths) {
|
|
|
447
424
|
entries
|
|
448
425
|
.filter((entry) => entry.isFile())
|
|
449
426
|
.map((entry) => entry.name)
|
|
450
|
-
.forEach((
|
|
451
|
-
const stepId = normalizeStepId(
|
|
427
|
+
.forEach((recordName) => {
|
|
428
|
+
const stepId = normalizeStepId(recordName);
|
|
452
429
|
if (STEP_IDS.includes(stepId)) {
|
|
453
|
-
if (!knownStepRows.has(stepId) ||
|
|
430
|
+
if (!knownStepRows.has(stepId) || recordName === stepId) {
|
|
454
431
|
knownStepRows.set(stepId, {
|
|
455
|
-
|
|
432
|
+
recordName,
|
|
456
433
|
stepId
|
|
457
434
|
});
|
|
458
435
|
}
|
|
459
436
|
return;
|
|
460
437
|
}
|
|
461
438
|
unknownStepRows.push({
|
|
462
|
-
|
|
439
|
+
recordName,
|
|
463
440
|
stepId
|
|
464
441
|
});
|
|
465
442
|
});
|
|
@@ -480,14 +457,14 @@ async function readReceiptSteps(paths) {
|
|
|
480
457
|
return left.stepId.localeCompare(right.stepId);
|
|
481
458
|
});
|
|
482
459
|
|
|
483
|
-
const
|
|
460
|
+
const globalRecords = await Promise.all(stepRows.map(async ({ recordName, stepId }) => ({
|
|
484
461
|
cycle: "",
|
|
462
|
+
details: (await readTextIfExists(path.join(stepsRoot, recordName))).trim(),
|
|
485
463
|
label: STEP_LABEL_BY_ID[stepId] || stepId,
|
|
486
|
-
receipt: (await readTextIfExists(path.join(stepsRoot, receiptName))).trim(),
|
|
487
464
|
stepId
|
|
488
465
|
})));
|
|
489
466
|
|
|
490
|
-
const
|
|
467
|
+
const cycleRecords = [];
|
|
491
468
|
const cycleDirectories = entries
|
|
492
469
|
.filter((entry) => entry.isDirectory() && /^cycle_\d+$/u.test(entry.name))
|
|
493
470
|
.map((entry) => entry.name)
|
|
@@ -496,18 +473,18 @@ async function readReceiptSteps(paths) {
|
|
|
496
473
|
const cycle = normalizeCycleNumber(cycleDirectory);
|
|
497
474
|
const cycleRootPath = path.join(stepsRoot, cycleDirectory);
|
|
498
475
|
const cycleStepIds = await readStepFileNames(cycleRootPath);
|
|
499
|
-
for (const
|
|
500
|
-
const stepId = normalizeStepId(
|
|
501
|
-
|
|
476
|
+
for (const recordName of cycleStepIds) {
|
|
477
|
+
const stepId = normalizeStepId(recordName);
|
|
478
|
+
cycleRecords.push({
|
|
502
479
|
cycle,
|
|
480
|
+
details: (await readTextIfExists(path.join(cycleRootPath, recordName))).trim(),
|
|
503
481
|
label: STEP_LABEL_BY_ID[stepId] || stepId,
|
|
504
|
-
receipt: (await readTextIfExists(path.join(cycleRootPath, receiptName))).trim(),
|
|
505
482
|
stepId
|
|
506
483
|
});
|
|
507
484
|
}
|
|
508
485
|
}
|
|
509
486
|
|
|
510
|
-
return [...
|
|
487
|
+
return [...globalRecords, ...cycleRecords];
|
|
511
488
|
} catch {
|
|
512
489
|
return [];
|
|
513
490
|
}
|
|
@@ -530,37 +507,56 @@ function cloneContractValue(value) {
|
|
|
530
507
|
);
|
|
531
508
|
}
|
|
532
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
|
+
|
|
533
547
|
async function publicCodexContract(codex = null) {
|
|
534
548
|
if (!codex || typeof codex !== "object" || Array.isArray(codex)) {
|
|
535
549
|
return null;
|
|
536
550
|
}
|
|
537
|
-
|
|
538
|
-
const resolvePrompt = clonedCodex.responseContract?.resolvePrompt;
|
|
539
|
-
const templateFile = normalizeText(resolvePrompt?.templateFile || "");
|
|
540
|
-
if (templateFile) {
|
|
541
|
-
const template = await readTextIfExists(path.join(PROMPT_DIRECTORY, templateFile));
|
|
542
|
-
clonedCodex.responseContract.resolvePrompt = {
|
|
543
|
-
...resolvePrompt,
|
|
544
|
-
template
|
|
545
|
-
};
|
|
546
|
-
}
|
|
547
|
-
return clonedCodex;
|
|
551
|
+
return cloneContractValue(codex);
|
|
548
552
|
}
|
|
549
553
|
|
|
550
554
|
function stepRepeatabilityContract(stepId) {
|
|
551
|
-
if (!CYCLE_STEP_IDS.includes(normalizeStepId(stepId))) {
|
|
552
|
-
return {
|
|
553
|
-
repeatable: false,
|
|
554
|
-
repeatableGroupId: "",
|
|
555
|
-
repeatableGroupLabel: "",
|
|
556
|
-
repeatableLabel: ""
|
|
557
|
-
};
|
|
558
|
-
}
|
|
559
555
|
return {
|
|
560
|
-
repeatable:
|
|
561
|
-
repeatableGroupId: "
|
|
562
|
-
repeatableGroupLabel: "
|
|
563
|
-
repeatableLabel: "
|
|
556
|
+
repeatable: false,
|
|
557
|
+
repeatableGroupId: "",
|
|
558
|
+
repeatableGroupLabel: "",
|
|
559
|
+
repeatableLabel: ""
|
|
564
560
|
};
|
|
565
561
|
}
|
|
566
562
|
|
|
@@ -595,17 +591,6 @@ function stepIsRetryableWhenBlocked(stepId) {
|
|
|
595
591
|
].includes(normalizeStepId(stepId));
|
|
596
592
|
}
|
|
597
593
|
|
|
598
|
-
function stepIsConditional(stepId) {
|
|
599
|
-
return [
|
|
600
|
-
"deep_ui_check_run"
|
|
601
|
-
].includes(normalizeStepId(stepId));
|
|
602
|
-
}
|
|
603
|
-
|
|
604
|
-
function activeCycleInfoFromArtifacts(artifacts = {}) {
|
|
605
|
-
const activeCycle = normalizeCycleNumber(artifacts.activeCycle || "");
|
|
606
|
-
return (artifacts.cycles || []).find((cycle) => cycle?.cycle === activeCycle) || null;
|
|
607
|
-
}
|
|
608
|
-
|
|
609
594
|
function uiCheckPromptedForStep(artifacts = {}, stepId = "") {
|
|
610
595
|
const normalizedStepId = normalizeStepId(stepId);
|
|
611
596
|
return (artifacts.uiChecks || []).some((entry) => {
|
|
@@ -615,10 +600,8 @@ function uiCheckPromptedForStep(artifacts = {}, stepId = "") {
|
|
|
615
600
|
}
|
|
616
601
|
|
|
617
602
|
function skipReasonForStep(stepId, artifacts = {}) {
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
return "uiImpact is none.";
|
|
621
|
-
}
|
|
603
|
+
void stepId;
|
|
604
|
+
void artifacts;
|
|
622
605
|
return "";
|
|
623
606
|
}
|
|
624
607
|
|
|
@@ -627,53 +610,27 @@ function buildCurrentStepAction(stepId, artifacts = {}) {
|
|
|
627
610
|
if (!step) {
|
|
628
611
|
return null;
|
|
629
612
|
}
|
|
630
|
-
const activeCycleInfo = activeCycleInfoFromArtifacts(artifacts);
|
|
631
613
|
const planExecutionPrompted = artifacts.planExecution?.prompted === true;
|
|
632
614
|
const planExecutionSubmitted = artifacts.planExecution?.submitted === true;
|
|
633
|
-
const
|
|
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";
|
|
634
620
|
const deepUiCheckPrompted = step.id === "deep_ui_check_run" && uiCheckPromptedForStep(artifacts, "deep_ui_check_run");
|
|
635
|
-
const
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
? step.codex.promptActionLabel || ""
|
|
640
|
-
: "";
|
|
641
|
-
const buttonLabel = promptPhaseButtonLabel || step.buttonLabel;
|
|
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
|
+
});
|
|
642
625
|
const alternateActions = [];
|
|
643
|
-
if (step.id === "user_check_completed") {
|
|
644
|
-
alternateActions.push({
|
|
645
|
-
id: "return_to_plan_made",
|
|
646
|
-
input: {
|
|
647
|
-
formatHint: "markdown",
|
|
648
|
-
label: "What needs to be reworked?",
|
|
649
|
-
multiline: true,
|
|
650
|
-
name: "reworkNotes",
|
|
651
|
-
required: true,
|
|
652
|
-
type: "text"
|
|
653
|
-
},
|
|
654
|
-
label: "Return to Plan made",
|
|
655
|
-
presentation: "exclusive",
|
|
656
|
-
requiredErrorCode: "user_check_failed",
|
|
657
|
-
submitOptions: {
|
|
658
|
-
userCheck: "failed"
|
|
659
|
-
},
|
|
660
|
-
targetStep: "plan_made"
|
|
661
|
-
});
|
|
662
|
-
}
|
|
663
626
|
if (step.id === "review_changes_accepted") {
|
|
664
627
|
alternateActions.push({
|
|
665
628
|
id: "request_another_review_pass",
|
|
666
|
-
helpText: "
|
|
629
|
+
helpText: "Run another explicit deslop prompt before continuing.",
|
|
667
630
|
input: {
|
|
668
|
-
|
|
669
|
-
label: "What important findings remain?",
|
|
670
|
-
multiline: true,
|
|
671
|
-
name: "reviewFindings",
|
|
672
|
-
placeholder: "List the specific findings Codex still needs to address.",
|
|
673
|
-
required: true,
|
|
674
|
-
type: "text"
|
|
631
|
+
type: "none"
|
|
675
632
|
},
|
|
676
|
-
label: "Run
|
|
633
|
+
label: "Run deslop",
|
|
677
634
|
presentation: "secondary",
|
|
678
635
|
submitOptions: {
|
|
679
636
|
reviewFindingsRemaining: true
|
|
@@ -681,73 +638,75 @@ function buildCurrentStepAction(stepId, artifacts = {}) {
|
|
|
681
638
|
targetStep: "review_prompt_rendered"
|
|
682
639
|
});
|
|
683
640
|
}
|
|
684
|
-
if (step.id === "pr_finalized") {
|
|
685
|
-
alternateActions.push({
|
|
686
|
-
id: "skip_merge",
|
|
687
|
-
helpText: "Leave the PR open and record the skipped-merge outcome; final cleanup removes the session worktree.",
|
|
688
|
-
input: {
|
|
689
|
-
type: "none"
|
|
690
|
-
},
|
|
691
|
-
label: "Skip merge",
|
|
692
|
-
presentation: "secondary",
|
|
693
|
-
submitOptions: {
|
|
694
|
-
closeWithoutMerge: true
|
|
695
|
-
},
|
|
696
|
-
targetStep: "pr_finalized"
|
|
697
|
-
});
|
|
698
|
-
}
|
|
699
|
-
if (step.id === "main_checkout_synced") {
|
|
700
|
-
alternateActions.push({
|
|
701
|
-
id: "skip_main_checkout_sync",
|
|
702
|
-
helpText: "Leave the main checkout untouched and continue with session cleanup.",
|
|
703
|
-
input: {
|
|
704
|
-
type: "none"
|
|
705
|
-
},
|
|
706
|
-
label: "Skip sync",
|
|
707
|
-
presentation: "secondary",
|
|
708
|
-
submitOptions: {
|
|
709
|
-
skipMainSync: true
|
|
710
|
-
},
|
|
711
|
-
targetStep: "main_checkout_synced"
|
|
712
|
-
});
|
|
713
|
-
}
|
|
714
641
|
const dynamicButtonLabel = (() => {
|
|
715
|
-
if (step.id === "
|
|
716
|
-
return "
|
|
642
|
+
if (step.id === "issue_created" && !artifacts.issueText) {
|
|
643
|
+
return "Create issue file";
|
|
717
644
|
}
|
|
718
|
-
if (step.id === "
|
|
719
|
-
return "
|
|
645
|
+
if (step.id === "issue_submitted") {
|
|
646
|
+
return "Create issue on GH";
|
|
720
647
|
}
|
|
721
|
-
if (step.id === "
|
|
722
|
-
return "
|
|
648
|
+
if (step.id === "pr_created") {
|
|
649
|
+
return "Create PR on GH";
|
|
723
650
|
}
|
|
724
|
-
if (step.id === "
|
|
725
|
-
return "
|
|
651
|
+
if (step.id === "pr_merge_prepared") {
|
|
652
|
+
return "Merge";
|
|
726
653
|
}
|
|
727
654
|
if (step.id === "main_checkout_synced" && artifacts.prOutcome?.outcome && artifacts.prOutcome.outcome !== "merged") {
|
|
728
|
-
return "
|
|
655
|
+
return "Sync main checkout";
|
|
729
656
|
}
|
|
730
|
-
return buttonLabel;
|
|
657
|
+
return step.buttonLabel;
|
|
731
658
|
})();
|
|
732
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
|
+
}
|
|
733
678
|
if (step.id === "plan_executed" && planExecutionPrompted && !planExecutionSubmitted) {
|
|
734
|
-
return "Codex has the execution prompt. Review the result, then use
|
|
679
|
+
return "Codex has the execution prompt. Review the result, then use Next when ready.";
|
|
735
680
|
}
|
|
736
681
|
if (step.id === "deep_ui_check_run" && deepUiCheckPrompted) {
|
|
737
|
-
return "Codex has the
|
|
738
|
-
}
|
|
739
|
-
if (step.id === "automated_checks_run" && artifacts.prompt) {
|
|
740
|
-
return "Codex has the automated-checks prompt. Studio advances when Codex finishes.";
|
|
682
|
+
return "Codex has the run deep UI check prompt. Review the result, then use Next when ready.";
|
|
741
683
|
}
|
|
742
|
-
if (step.id === "
|
|
743
|
-
return "Codex
|
|
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.";
|
|
744
686
|
}
|
|
745
687
|
if (step.id === "main_checkout_synced" && artifacts.prOutcome?.outcome && artifacts.prOutcome.outcome !== "merged") {
|
|
746
|
-
return "
|
|
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.";
|
|
747
692
|
}
|
|
748
693
|
return step.description;
|
|
749
694
|
})();
|
|
750
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
|
+
}
|
|
751
710
|
return step.utilityActions || [];
|
|
752
711
|
})();
|
|
753
712
|
return {
|
|
@@ -757,14 +716,13 @@ function buildCurrentStepAction(stepId, artifacts = {}) {
|
|
|
757
716
|
displayGroupId: step.displayGroupId,
|
|
758
717
|
displayGroupLabel: step.displayGroupLabel,
|
|
759
718
|
index: STEP_IDS.indexOf(step.id),
|
|
760
|
-
input: cloneContractValue(step.input),
|
|
761
|
-
kind: step.kind,
|
|
719
|
+
input: cloneContractValue(issueDefinitionPrompted || issueFilePromptAction || pullRequestFileAction ? { type: "none" } : step.input),
|
|
720
|
+
kind: issueDefinitionPrompted || issueFilePromptAction || pullRequestFileAction ? "codex_prompt" : step.kind,
|
|
762
721
|
label: dynamicButtonLabel,
|
|
763
|
-
automation: cloneContractValue(step.automation || { mode: "manual" }),
|
|
722
|
+
automation: cloneContractValue(issueDefinitionPrompted || issueFilePromptAction || pullRequestFileAction ? { mode: "codex_prompt" } : step.automation || { mode: "manual" }),
|
|
764
723
|
...stepRepeatabilityContract(step.id),
|
|
765
|
-
requiredInput: cloneContractValue(step.input),
|
|
724
|
+
requiredInput: cloneContractValue(issueDefinitionPrompted || issueFilePromptAction || pullRequestFileAction ? { type: "none" } : step.input),
|
|
766
725
|
requiresExplicitRun: step.requiresExplicitRun === true,
|
|
767
|
-
conditional: stepIsConditional(step.id),
|
|
768
726
|
retryable: artifacts.status === SESSION_STATUS.BLOCKED && stepIsRetryableWhenBlocked(step.id),
|
|
769
727
|
skipReason: skipReasonForStep(step.id, artifacts),
|
|
770
728
|
stepId: step.id,
|
|
@@ -774,19 +732,17 @@ function buildCurrentStepAction(stepId, artifacts = {}) {
|
|
|
774
732
|
}
|
|
775
733
|
|
|
776
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
|
+
}
|
|
777
741
|
if (normalizeStepId(stepId) === "review_changes_accepted" && latestReviewPassIsPrompted(artifacts)) {
|
|
778
742
|
return cloneContractValue(REVIEW_EXECUTION_CODEX_HANDOFF);
|
|
779
743
|
}
|
|
780
744
|
const step = STEP_DEFINITION_BY_ID[stepId];
|
|
781
|
-
|
|
782
|
-
if (
|
|
783
|
-
codex &&
|
|
784
|
-
normalizeStepId(stepId) === "plan_made" &&
|
|
785
|
-
normalizeCycleNumber(artifacts.activeCycle || "") !== "001"
|
|
786
|
-
) {
|
|
787
|
-
codex.promptIntroText = "Codex will create a revised implementation plan based on the rework notes.";
|
|
788
|
-
}
|
|
789
|
-
return codex;
|
|
745
|
+
return step?.codex ? cloneContractValue(step.codex) : null;
|
|
790
746
|
}
|
|
791
747
|
|
|
792
748
|
async function buildCodexHandoff(stepId, artifacts = {}) {
|
|
@@ -795,55 +751,59 @@ async function buildCodexHandoff(stepId, artifacts = {}) {
|
|
|
795
751
|
|
|
796
752
|
async function readSessionArtifacts(paths) {
|
|
797
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");
|
|
798
756
|
const [
|
|
799
757
|
status,
|
|
800
758
|
rawCurrentStep,
|
|
801
759
|
issueUrl,
|
|
760
|
+
issueNumber,
|
|
802
761
|
prUrl,
|
|
803
762
|
issueText,
|
|
804
763
|
issueTitle,
|
|
805
|
-
planText,
|
|
806
|
-
issueDetails,
|
|
807
|
-
agentDecisions,
|
|
808
764
|
finalReportText,
|
|
765
|
+
pullRequestText,
|
|
809
766
|
githubCommentsText,
|
|
810
767
|
codexThreadId,
|
|
811
768
|
workflowVersion,
|
|
812
769
|
baseBranch,
|
|
813
770
|
baseCommit,
|
|
814
|
-
|
|
815
|
-
|
|
771
|
+
planExecutionRecord,
|
|
772
|
+
issueDefinitionRequested,
|
|
773
|
+
issueFileRequested,
|
|
774
|
+
pullRequestFileRequested,
|
|
775
|
+
makePlanRequested,
|
|
776
|
+
blueprintUpdateRequested,
|
|
777
|
+
executePlanRequested,
|
|
816
778
|
prOutcomeText,
|
|
817
|
-
mainCheckoutSyncText
|
|
779
|
+
mainCheckoutSyncText,
|
|
780
|
+
changesCommittedText
|
|
818
781
|
] = await Promise.all([
|
|
819
782
|
readTrimmedFile(path.join(paths.sessionRoot, "status")),
|
|
820
783
|
readTrimmedFile(path.join(paths.sessionRoot, "current_step")),
|
|
821
784
|
readTrimmedFile(path.join(paths.sessionRoot, "issue_url")),
|
|
785
|
+
readTrimmedFile(path.join(paths.sessionRoot, "metadata", "issue_number")),
|
|
822
786
|
readTrimmedFile(path.join(paths.sessionRoot, "pr_url")),
|
|
823
787
|
readTextIfExists(path.join(paths.sessionRoot, "issue.md")),
|
|
824
788
|
readTrimmedFile(path.join(paths.sessionRoot, "issue_title")),
|
|
825
|
-
readTextIfExists(
|
|
826
|
-
readTextIfExists(path.join(paths.sessionRoot, "
|
|
827
|
-
readTextIfExists(path.join(paths.sessionRoot, "agent_decisions.md")),
|
|
828
|
-
readTextIfExists(path.join(paths.sessionRoot, "final_report.md")),
|
|
789
|
+
readTextIfExists(path.join(paths.sessionRoot, "final_report")),
|
|
790
|
+
readTextIfExists(path.join(paths.sessionRoot, "pull_request.md")),
|
|
829
791
|
readTextIfExists(path.join(paths.sessionRoot, "github_comments.json")),
|
|
830
792
|
readTrimmedFile(path.join(paths.sessionRoot, "codex_thread_id")),
|
|
831
793
|
readWorkflowVersion(paths),
|
|
832
794
|
readTrimmedFile(path.join(paths.sessionRoot, "base_branch")),
|
|
833
795
|
readTrimmedFile(path.join(paths.sessionRoot, "base_commit")),
|
|
834
|
-
readTextIfExists(
|
|
835
|
-
|
|
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")),
|
|
836
803
|
readTextIfExists(path.join(paths.sessionRoot, "pr_outcome.json")),
|
|
837
|
-
readTextIfExists(path.join(paths.sessionRoot, "main_checkout_sync.json"))
|
|
804
|
+
readTextIfExists(path.join(paths.sessionRoot, "main_checkout_sync.json")),
|
|
805
|
+
readTextIfExists(path.join(paths.sessionRoot, "changes_committed.json"))
|
|
838
806
|
]);
|
|
839
|
-
let issueMetadata = null;
|
|
840
|
-
if (issueMetadataText) {
|
|
841
|
-
try {
|
|
842
|
-
issueMetadata = JSON.parse(issueMetadataText);
|
|
843
|
-
} catch {
|
|
844
|
-
issueMetadata = null;
|
|
845
|
-
}
|
|
846
|
-
}
|
|
847
807
|
let githubComments = {};
|
|
848
808
|
if (githubCommentsText) {
|
|
849
809
|
try {
|
|
@@ -871,6 +831,18 @@ async function readSessionArtifacts(paths) {
|
|
|
871
831
|
mainCheckoutSync = null;
|
|
872
832
|
}
|
|
873
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
|
+
: [];
|
|
874
846
|
const cycles = await readCycles(paths, activeCycle);
|
|
875
847
|
const checks = await readStructuredChecks(paths);
|
|
876
848
|
const uiChecks = await readStructuredUiChecks(paths);
|
|
@@ -878,9 +850,8 @@ async function readSessionArtifacts(paths) {
|
|
|
878
850
|
const worktreeReady = await hasWorktree(paths);
|
|
879
851
|
const worktreeStatus = await readWorktreeStatus(paths, worktreeReady);
|
|
880
852
|
const commandLogPath = path.join(paths.sessionRoot, "command_log.jsonl");
|
|
881
|
-
const
|
|
882
|
-
const
|
|
883
|
-
const planExecutionPromptExists = await fileExists(planExecutionPromptPath);
|
|
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));
|
|
884
855
|
const appRootForArtifacts = worktreeReady ? paths.worktree : paths.targetRoot;
|
|
885
856
|
const appReady = await inspectReadyJskitAppRoot(appRootForArtifacts);
|
|
886
857
|
const blueprintPath = path.join(appRootForArtifacts, ".jskit", "APP_BLUEPRINT.md");
|
|
@@ -888,12 +859,12 @@ async function readSessionArtifacts(paths) {
|
|
|
888
859
|
const currentStep = normalizeStepId(rawCurrentStep);
|
|
889
860
|
let completedSteps = await readCompletedSteps(paths);
|
|
890
861
|
const worktreeRemovalCompleted = completedSteps.includes("session_finished");
|
|
891
|
-
const
|
|
862
|
+
const worktreeStepRecordInvalid = !worktreeReady &&
|
|
892
863
|
completedSteps.includes("worktree_created") &&
|
|
893
864
|
!worktreeRemovalCompleted &&
|
|
894
865
|
status !== SESSION_STATUS.FINISHED &&
|
|
895
866
|
status !== SESSION_STATUS.ABANDONED;
|
|
896
|
-
if (
|
|
867
|
+
if (worktreeStepRecordInvalid) {
|
|
897
868
|
completedSteps = completedSteps.filter((stepId) => !["worktree_created", "dependencies_installed"].includes(stepId));
|
|
898
869
|
}
|
|
899
870
|
const nextStep = resolveNextStep(completedSteps);
|
|
@@ -904,6 +875,8 @@ async function readSessionArtifacts(paths) {
|
|
|
904
875
|
? nextStep
|
|
905
876
|
: currentStep || nextStep;
|
|
906
877
|
const prompt = await readPromptForStep(paths, effectiveCurrentStep, { reviewPasses });
|
|
878
|
+
const dependencyInstallDetails = dependencyInstallRecord.trim() || dependencyInstallResult.trim();
|
|
879
|
+
const dependencyInstallReady = Boolean(dependencyInstallRecord.trim() || dependencyInstallResult.trim());
|
|
907
880
|
|
|
908
881
|
return {
|
|
909
882
|
codexThreadId,
|
|
@@ -923,64 +896,296 @@ async function readSessionArtifacts(paths) {
|
|
|
923
896
|
commandLogExists: await fileExists(commandLogPath),
|
|
924
897
|
commandLogPath,
|
|
925
898
|
dependencyInstall: {
|
|
926
|
-
installed: Boolean(
|
|
927
|
-
|
|
928
|
-
|
|
899
|
+
installed: Boolean(dependencyInstallRecord.trim()),
|
|
900
|
+
ready: dependencyInstallReady,
|
|
901
|
+
details: dependencyInstallDetails,
|
|
902
|
+
status: dependencyInstallRecord.trim()
|
|
929
903
|
? "installed"
|
|
904
|
+
: dependencyInstallResult.trim() ? "ready_to_advance"
|
|
930
905
|
: worktreeReady ? "pending" : "waiting_for_worktree"
|
|
931
906
|
},
|
|
932
907
|
helperMapExists: await fileExists(helperMapPath),
|
|
933
908
|
helperMapPath,
|
|
934
909
|
githubComments,
|
|
935
|
-
|
|
936
|
-
issueCategory: normalizeText(issueMetadata?.issueCategory || ""),
|
|
937
|
-
uiImpact: normalizeText(issueMetadata?.uiImpact || ""),
|
|
938
|
-
agentDecisions: agentDecisions.trim(),
|
|
939
|
-
agentDecisionsLatest: agentDecisions
|
|
940
|
-
.split(/\r?\n/u)
|
|
941
|
-
.map((line) => line.trim())
|
|
942
|
-
.filter((line) => line && !line.startsWith("#") && !line.startsWith("Session:"))
|
|
943
|
-
.slice(-5)
|
|
944
|
-
.join("\n"),
|
|
910
|
+
issueNumber: issueNumber || issueNumberFromUrl(issueUrl),
|
|
945
911
|
issueTitle,
|
|
946
912
|
issueText: issueText.trim(),
|
|
947
913
|
issueUrl,
|
|
948
914
|
nextStep,
|
|
915
|
+
pullRequestPath: path.join(paths.sessionRoot, "pull_request.md"),
|
|
916
|
+
pullRequestText: pullRequestText.trim(),
|
|
949
917
|
prUrl,
|
|
950
918
|
prOutcome,
|
|
951
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()),
|
|
952
927
|
planExecution: {
|
|
953
|
-
prompted:
|
|
954
|
-
promptPath:
|
|
955
|
-
|
|
956
|
-
submitted: Boolean(
|
|
928
|
+
prompted: Boolean(executePlanRequested.trim()),
|
|
929
|
+
promptPath: "",
|
|
930
|
+
details: planExecutionRecord.trim(),
|
|
931
|
+
submitted: Boolean(planExecutionRecord.trim())
|
|
957
932
|
},
|
|
958
|
-
planText: planText.trim(),
|
|
959
|
-
issueDetails: issueDetails.trim(),
|
|
960
933
|
finalReportText: finalReportText.trim(),
|
|
961
934
|
prompt: prompt.trim(),
|
|
962
935
|
status: status || SESSION_STATUS.PENDING,
|
|
936
|
+
warnings,
|
|
963
937
|
workflowVersion,
|
|
964
938
|
worktreeReady,
|
|
965
939
|
worktreeStatus
|
|
966
940
|
};
|
|
967
941
|
}
|
|
968
942
|
|
|
969
|
-
function
|
|
943
|
+
function stepCanExposeNextCommand(stepId, artifacts = {}) {
|
|
970
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)) {
|
|
971
982
|
return "";
|
|
972
983
|
}
|
|
973
|
-
const template = STEP_DEFINITION_BY_ID[stepId]?.nextCommandTemplate || `${JSKIT_CLI_SHELL_COMMAND} session {{session_id}}
|
|
984
|
+
const template = STEP_DEFINITION_BY_ID[stepId]?.nextCommandTemplate || `${JSKIT_CLI_SHELL_COMMAND} session {{session_id}} next`;
|
|
974
985
|
return template.replaceAll("{{session_id}}", sessionId);
|
|
975
986
|
}
|
|
976
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
|
+
|
|
977
1181
|
async function buildSessionResponse(paths, {
|
|
978
1182
|
codex = undefined,
|
|
979
1183
|
ok = true,
|
|
980
1184
|
errors = [],
|
|
981
1185
|
preconditions = [],
|
|
982
1186
|
prompt = undefined,
|
|
983
|
-
status = undefined
|
|
1187
|
+
status = undefined,
|
|
1188
|
+
warnings = []
|
|
984
1189
|
} = {}) {
|
|
985
1190
|
const responsePaths = paths.sessionId ? await pathsForExistingSession(paths) : paths;
|
|
986
1191
|
const artifacts = responsePaths.sessionRoot ? await readSessionArtifacts(responsePaths) : {};
|
|
@@ -1012,26 +1217,28 @@ async function buildSessionResponse(paths, {
|
|
|
1012
1217
|
codex: null,
|
|
1013
1218
|
prompt: "",
|
|
1014
1219
|
nextCommand: "",
|
|
1220
|
+
issueDefinitionRequested: artifacts.issueDefinitionRequested === true,
|
|
1221
|
+
issueFileRequested: artifacts.issueFileRequested === true,
|
|
1222
|
+
issueNumber: artifacts.issueNumber || "",
|
|
1015
1223
|
issueUrl: artifacts.issueUrl || "",
|
|
1016
1224
|
issueTitle: artifacts.issueTitle || "",
|
|
1017
1225
|
issueText: artifacts.issueText || "",
|
|
1018
|
-
|
|
1226
|
+
pullRequestFileRequested: artifacts.pullRequestFileRequested === true,
|
|
1227
|
+
pullRequestPath: artifacts.pullRequestPath || "",
|
|
1228
|
+
pullRequestText: artifacts.pullRequestText || "",
|
|
1019
1229
|
githubComments: cloneContractValue(artifacts.githubComments || {}),
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
agentDecisionsLatest: artifacts.agentDecisionsLatest || "",
|
|
1230
|
+
makePlanRequested: artifacts.makePlanRequested === true,
|
|
1231
|
+
blueprintUpdateRequested: artifacts.blueprintUpdateRequested === true,
|
|
1232
|
+
executePlanRequested: artifacts.executePlanRequested === true,
|
|
1024
1233
|
planExecution: cloneContractValue(artifacts.planExecution || null),
|
|
1025
|
-
|
|
1026
|
-
issueDetails: artifacts.issueDetails || "",
|
|
1027
|
-
issueDetailsPath: artifacts.issueDetails ? path.join(responsePaths.sessionRoot, "issue_details.md") : "",
|
|
1028
|
-
finalReportPath: artifacts.finalReportText ? path.join(responsePaths.sessionRoot, "final_report.md") : "",
|
|
1234
|
+
finalReportPath: artifacts.finalReportText ? path.join(responsePaths.sessionRoot, "final_report") : "",
|
|
1029
1235
|
finalReportText: artifacts.finalReportText || "",
|
|
1030
1236
|
helperMapPath: artifacts.helperMapPath || "",
|
|
1031
1237
|
helperMapExists: artifacts.helperMapExists === true,
|
|
1032
1238
|
prUrl: artifacts.prUrl || "",
|
|
1033
1239
|
prOutcome: cloneContractValue(artifacts.prOutcome || null),
|
|
1034
1240
|
mainCheckoutSync: cloneContractValue(artifacts.mainCheckoutSync || null),
|
|
1241
|
+
acceptedChangesCommit: cloneContractValue(artifacts.acceptedChangesCommit || null),
|
|
1035
1242
|
preconditions,
|
|
1036
1243
|
errors: [
|
|
1037
1244
|
createError({
|
|
@@ -1039,6 +1246,7 @@ async function buildSessionResponse(paths, {
|
|
|
1039
1246
|
message: `Session ${paths.sessionId || ""} uses workflow version ${artifacts.workflowVersion || "missing"}, but this JSKIT runtime expects ${SESSION_WORKFLOW_VERSION}.`
|
|
1040
1247
|
})
|
|
1041
1248
|
],
|
|
1249
|
+
warnings: [],
|
|
1042
1250
|
archive: responsePaths.archive || "active",
|
|
1043
1251
|
sessionRoot: responsePaths.sessionRoot || "",
|
|
1044
1252
|
worktree: paths.worktree || "",
|
|
@@ -1052,6 +1260,7 @@ async function buildSessionResponse(paths, {
|
|
|
1052
1260
|
const responsePrompt = typeof prompt === "string"
|
|
1053
1261
|
? prompt
|
|
1054
1262
|
: stepCanExposeStoredPrompt(currentStep) ? artifacts.prompt || "" : "";
|
|
1263
|
+
const responseWarnings = mergeWarnings(artifacts.warnings || [], warnings);
|
|
1055
1264
|
|
|
1056
1265
|
return {
|
|
1057
1266
|
ok: ok === true,
|
|
@@ -1076,31 +1285,35 @@ async function buildSessionResponse(paths, {
|
|
|
1076
1285
|
commandLogPath: artifacts.commandLogPath || "",
|
|
1077
1286
|
stepDefinitions: buildStepDefinitions(),
|
|
1078
1287
|
currentStepAction: buildCurrentStepAction(currentStep, artifacts),
|
|
1288
|
+
actionCommands: buildStepActionCommands(paths.sessionId || "", currentStep, artifacts),
|
|
1079
1289
|
codex: codex === undefined ? await buildCodexHandoff(currentStep, artifacts) : await publicCodexContract(codex),
|
|
1080
1290
|
prompt: responsePrompt,
|
|
1081
|
-
nextCommand: buildNextCommand(paths.sessionId || "", currentStep),
|
|
1291
|
+
nextCommand: buildNextCommand(paths.sessionId || "", currentStep, artifacts),
|
|
1292
|
+
issueDefinitionRequested: artifacts.issueDefinitionRequested === true,
|
|
1293
|
+
issueFileRequested: artifacts.issueFileRequested === true,
|
|
1294
|
+
issueNumber: artifacts.issueNumber || "",
|
|
1082
1295
|
issueUrl: artifacts.issueUrl || "",
|
|
1083
1296
|
issueTitle: artifacts.issueTitle || "",
|
|
1084
1297
|
issueText: artifacts.issueText || "",
|
|
1085
|
-
|
|
1298
|
+
pullRequestFileRequested: artifacts.pullRequestFileRequested === true,
|
|
1299
|
+
pullRequestPath: artifacts.pullRequestPath || "",
|
|
1300
|
+
pullRequestText: artifacts.pullRequestText || "",
|
|
1086
1301
|
githubComments: cloneContractValue(artifacts.githubComments || {}),
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
agentDecisionsLatest: artifacts.agentDecisionsLatest || "",
|
|
1302
|
+
makePlanRequested: artifacts.makePlanRequested === true,
|
|
1303
|
+
blueprintUpdateRequested: artifacts.blueprintUpdateRequested === true,
|
|
1304
|
+
executePlanRequested: artifacts.executePlanRequested === true,
|
|
1091
1305
|
planExecution: cloneContractValue(artifacts.planExecution || null),
|
|
1092
|
-
|
|
1093
|
-
issueDetails: artifacts.issueDetails || "",
|
|
1094
|
-
issueDetailsPath: artifacts.issueDetails ? path.join(responsePaths.sessionRoot, "issue_details.md") : "",
|
|
1095
|
-
finalReportPath: artifacts.finalReportText ? path.join(responsePaths.sessionRoot, "final_report.md") : "",
|
|
1306
|
+
finalReportPath: artifacts.finalReportText ? path.join(responsePaths.sessionRoot, "final_report") : "",
|
|
1096
1307
|
finalReportText: artifacts.finalReportText || "",
|
|
1097
1308
|
helperMapPath: artifacts.helperMapPath || "",
|
|
1098
1309
|
helperMapExists: artifacts.helperMapExists === true,
|
|
1099
1310
|
prUrl: artifacts.prUrl || "",
|
|
1100
1311
|
prOutcome: cloneContractValue(artifacts.prOutcome || null),
|
|
1101
1312
|
mainCheckoutSync: cloneContractValue(artifacts.mainCheckoutSync || null),
|
|
1313
|
+
acceptedChangesCommit: cloneContractValue(artifacts.acceptedChangesCommit || null),
|
|
1102
1314
|
preconditions,
|
|
1103
1315
|
errors,
|
|
1316
|
+
warnings: responseWarnings,
|
|
1104
1317
|
archive: responsePaths.archive || (resolvedStatus === SESSION_STATUS.FINISHED ? "completed" : resolvedStatus === SESSION_STATUS.ABANDONED ? "abandoned" : "active"),
|
|
1105
1318
|
sessionRoot: responsePaths.sessionRoot || "",
|
|
1106
1319
|
worktree: paths.worktree || "",
|
|
@@ -1160,16 +1373,11 @@ function buildSessionErrorResponse({
|
|
|
1160
1373
|
nextCommand: "",
|
|
1161
1374
|
issueTitle: "",
|
|
1162
1375
|
issueText: "",
|
|
1163
|
-
|
|
1376
|
+
pullRequestFileRequested: false,
|
|
1377
|
+
pullRequestPath: "",
|
|
1378
|
+
pullRequestText: "",
|
|
1164
1379
|
githubComments: {},
|
|
1165
|
-
issueCategory: "",
|
|
1166
|
-
uiImpact: "",
|
|
1167
|
-
agentDecisionsPath: "",
|
|
1168
|
-
agentDecisionsLatest: "",
|
|
1169
1380
|
planExecution: null,
|
|
1170
|
-
planText: "",
|
|
1171
|
-
issueDetails: "",
|
|
1172
|
-
issueDetailsPath: "",
|
|
1173
1381
|
finalReportPath: "",
|
|
1174
1382
|
finalReportText: "",
|
|
1175
1383
|
helperMapPath: "",
|
|
@@ -1179,6 +1387,7 @@ function buildSessionErrorResponse({
|
|
|
1179
1387
|
prOutcome: null,
|
|
1180
1388
|
preconditions,
|
|
1181
1389
|
errors: errorList,
|
|
1390
|
+
warnings: [],
|
|
1182
1391
|
archive: "",
|
|
1183
1392
|
sessionRoot: "",
|
|
1184
1393
|
worktree: "",
|
|
@@ -1198,27 +1407,28 @@ async function markCurrentStep(paths, stepId) {
|
|
|
1198
1407
|
await writeTextFile(path.join(paths.sessionRoot, "current_step"), stepId);
|
|
1199
1408
|
}
|
|
1200
1409
|
|
|
1201
|
-
async function
|
|
1202
|
-
const
|
|
1203
|
-
|
|
1410
|
+
async function writeStepRecord(paths, stepId, message) {
|
|
1411
|
+
const root = isCycleStepId(stepId)
|
|
1412
|
+
? cycleStepsRoot(paths, await readActiveCycle(paths))
|
|
1413
|
+
: path.join(paths.sessionRoot, "steps");
|
|
1204
1414
|
await mkdir(root, { recursive: true });
|
|
1205
1415
|
await writeTextFile(
|
|
1206
1416
|
path.join(root, stepId),
|
|
1207
|
-
`${
|
|
1417
|
+
`${timestampForStepRecord()}\n${normalizeText(message) || STEP_LABEL_BY_ID[stepId] || stepId}`
|
|
1208
1418
|
);
|
|
1209
1419
|
const completedSteps = await readCompletedSteps(paths);
|
|
1210
1420
|
await markCurrentStep(paths, resolveNextStep(completedSteps));
|
|
1211
1421
|
}
|
|
1212
1422
|
|
|
1213
|
-
async function
|
|
1423
|
+
async function writeCycleStepRecord(paths, recordName, message, {
|
|
1214
1424
|
cycle = ""
|
|
1215
1425
|
} = {}) {
|
|
1216
1426
|
const activeCycle = normalizeCycleNumber(cycle || await readActiveCycle(paths));
|
|
1217
1427
|
const root = cycleStepsRoot(paths, activeCycle);
|
|
1218
1428
|
await mkdir(root, { recursive: true });
|
|
1219
1429
|
await writeTextFile(
|
|
1220
|
-
path.join(root, normalizeText(
|
|
1221
|
-
`${
|
|
1430
|
+
path.join(root, normalizeText(recordName)),
|
|
1431
|
+
`${timestampForStepRecord()}\n${normalizeText(message) || normalizeText(recordName)}`
|
|
1222
1432
|
);
|
|
1223
1433
|
}
|
|
1224
1434
|
|
|
@@ -1261,12 +1471,11 @@ export {
|
|
|
1261
1471
|
markStatus,
|
|
1262
1472
|
normalizeReviewPassNumber,
|
|
1263
1473
|
readActiveCycle,
|
|
1264
|
-
|
|
1474
|
+
readStepRecords,
|
|
1265
1475
|
readReviewPasses,
|
|
1266
1476
|
readSessionArtifacts,
|
|
1267
1477
|
reviewPassDirectoryName,
|
|
1268
1478
|
reviewPassRoot,
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
writeReceipt
|
|
1479
|
+
writeCycleStepRecord,
|
|
1480
|
+
writeStepRecord
|
|
1272
1481
|
};
|