@jskit-ai/jskit-cli 0.2.82 → 0.2.84
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 +18 -1
- package/src/server/core/commandCatalog.js +3 -1
- package/src/server/sessionRuntime/constants.js +125 -25
- package/src/server/sessionRuntime/prompts/execute_plan.md +1 -1
- package/src/server/sessionRuntime/prompts/issue_details.md +2 -8
- package/src/server/sessionRuntime/prompts/new_issue.md +1 -1
- package/src/server/sessionRuntime/prompts/resolve_deslop_findings.md +16 -0
- package/src/server/sessionRuntime/prompts/update_blueprint.md +3 -1
- package/src/server/sessionRuntime/responses.js +95 -17
- package/src/server/sessionRuntime.js +93 -69
- package/src/server/sessionRuntime/prompts/doctor_failure.md +0 -35
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jskit-ai/jskit-cli",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.84",
|
|
4
4
|
"description": "Bundle and package orchestration CLI for JSKIT apps.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"files": [
|
|
@@ -20,9 +20,9 @@
|
|
|
20
20
|
"test": "node --test"
|
|
21
21
|
},
|
|
22
22
|
"dependencies": {
|
|
23
|
-
"@jskit-ai/jskit-catalog": "0.1.
|
|
24
|
-
"@jskit-ai/kernel": "0.1.
|
|
25
|
-
"@jskit-ai/shell-web": "0.1.
|
|
23
|
+
"@jskit-ai/jskit-catalog": "0.1.83",
|
|
24
|
+
"@jskit-ai/kernel": "0.1.75",
|
|
25
|
+
"@jskit-ai/shell-web": "0.1.74",
|
|
26
26
|
"@vue/compiler-sfc": "^3.5.29",
|
|
27
27
|
"ts-morph": "^28.0.0"
|
|
28
28
|
},
|
|
@@ -280,9 +280,25 @@ async function resolveStepInputs({
|
|
|
280
280
|
return closeReason;
|
|
281
281
|
}
|
|
282
282
|
|
|
283
|
+
const codexResult = await resolveTextInput({
|
|
284
|
+
codePrefix: "codex_result",
|
|
285
|
+
fileOption: "codex-result-file",
|
|
286
|
+
inlineOptions,
|
|
287
|
+
io,
|
|
288
|
+
repairCommand: `jskit session ${sessionId} step --codex-result -`,
|
|
289
|
+
cwd,
|
|
290
|
+
sessionId,
|
|
291
|
+
stdinOption: "-",
|
|
292
|
+
textOption: "codex-result"
|
|
293
|
+
});
|
|
294
|
+
if (codexResult.ok === false) {
|
|
295
|
+
return codexResult;
|
|
296
|
+
}
|
|
297
|
+
|
|
283
298
|
return {
|
|
284
299
|
agentDecisions: agentDecisions.value,
|
|
285
300
|
closeReason: closeReason.value,
|
|
301
|
+
codexResult: codexResult.value,
|
|
286
302
|
issue: issue.value,
|
|
287
303
|
issueTitle: issueTitle.value,
|
|
288
304
|
ok: true,
|
|
@@ -365,7 +381,8 @@ function createSessionCommands() {
|
|
|
365
381
|
reworkNotes: stepInputs.reworkNotes,
|
|
366
382
|
skipReason: stepInputs.skipReason,
|
|
367
383
|
agentDecisions: stepInputs.agentDecisions,
|
|
368
|
-
closeReason: stepInputs.closeReason
|
|
384
|
+
closeReason: stepInputs.closeReason,
|
|
385
|
+
codexResult: stepInputs.codexResult
|
|
369
386
|
}
|
|
370
387
|
});
|
|
371
388
|
} else if (second === "abandon") {
|
|
@@ -226,6 +226,7 @@ const COMMAND_DESCRIPTORS = Object.freeze({
|
|
|
226
226
|
"Use --issue-title when the approved issue title is separate from the body.",
|
|
227
227
|
"Use --issue-details - to read confirmed issue details from stdin.",
|
|
228
228
|
"Use --plan - to read the approved implementation plan from stdin.",
|
|
229
|
+
"Use --codex-result - after Codex prompt steps to read the final marked Codex result from stdin.",
|
|
229
230
|
"Use --rework-notes - with --user-check failed to start the next plan cycle.",
|
|
230
231
|
"Use --agent-decisions - to append session-local decision log entries from implementation, UI review, verification, or repair phases.",
|
|
231
232
|
"Use --review-findings-remaining true --review-findings \"<findings>\" when an accepted review pass needs another pass.",
|
|
@@ -245,6 +246,7 @@ const COMMAND_DESCRIPTORS = Object.freeze({
|
|
|
245
246
|
"jskit session 2026-05-11_21-42-08 step --issue-title \"Fix customer filters\" --issue -",
|
|
246
247
|
"jskit session 2026-05-11_21-42-08 step --issue-details -",
|
|
247
248
|
"jskit session 2026-05-11_21-42-08 step --plan -",
|
|
249
|
+
"jskit session 2026-05-11_21-42-08 step --codex-result -",
|
|
248
250
|
"jskit session 2026-05-11_21-42-08 step --agent-decisions -",
|
|
249
251
|
"jskit session 2026-05-11_21-42-08 step --review-findings-remaining true --review-findings \"A helper duplication fix needs another pass\"",
|
|
250
252
|
"jskit session 2026-05-11_21-42-08 step --review-findings-remaining false",
|
|
@@ -258,7 +260,7 @@ const COMMAND_DESCRIPTORS = Object.freeze({
|
|
|
258
260
|
})
|
|
259
261
|
]),
|
|
260
262
|
fullUse:
|
|
261
|
-
"jskit session [create|<sessionId>] [step|diff|abandon|adopt-codex-thread] [--prompt <text>] [--issue-title <text>|--issue-title-file <path>] [--issue <text>|--issue-file <path>] [--issue-details <text>|--issue-details-file <path>] [--plan <text>|--plan-file <path>] [--agent-decisions <text>|--agent-decisions-file <path>] [--review-findings-remaining true --review-findings <text>|--review-findings-remaining false] [--skip-ui-check --skip-reason <text>] [--merge-pr true|--close-without-merge --close-reason <text>] [--user-check <passed|failed>] [--rework-notes <text>|--rework-notes-file <path>] [--codex-thread-id <id>] [--abandoned|--completed|--all] [--json]",
|
|
263
|
+
"jskit session [create|<sessionId>] [step|diff|abandon|adopt-codex-thread] [--prompt <text>] [--issue-title <text>|--issue-title-file <path>] [--issue <text>|--issue-file <path>] [--issue-details <text>|--issue-details-file <path>] [--plan <text>|--plan-file <path>] [--codex-result <text>|--codex-result-file <path>] [--agent-decisions <text>|--agent-decisions-file <path>] [--review-findings-remaining true --review-findings <text>|--review-findings-remaining false] [--skip-ui-check --skip-reason <text>] [--merge-pr true|--close-without-merge --close-reason <text>] [--user-check <passed|failed>] [--rework-notes <text>|--rework-notes-file <path>] [--codex-thread-id <id>] [--abandoned|--completed|--all] [--json]",
|
|
262
264
|
showHelpOnBareInvocation: false,
|
|
263
265
|
handlerName: "commandSession",
|
|
264
266
|
allowedFlagKeys: Object.freeze(["json", "abandoned", "completed", "all"]),
|
|
@@ -138,24 +138,100 @@ const USER_CHECK_INPUT = Object.freeze({
|
|
|
138
138
|
required: true,
|
|
139
139
|
type: "choice"
|
|
140
140
|
});
|
|
141
|
-
|
|
141
|
+
|
|
142
|
+
const JSKIT_STEP_RESULT_CONTRACT = Object.freeze({
|
|
143
|
+
completionBehavior: "auto_advance",
|
|
144
|
+
kind: "completion_marker",
|
|
145
|
+
marker: "jskit_step_result",
|
|
146
|
+
missingMarkerBehavior: "resend",
|
|
147
|
+
required: true,
|
|
148
|
+
stepField: "step"
|
|
149
|
+
});
|
|
150
|
+
const DESLOP_RESULT_CONTRACT = Object.freeze({
|
|
151
|
+
autoResolvePriorities: Object.freeze(["high", "medium"]),
|
|
152
|
+
completionBehavior: "deslop_loop",
|
|
153
|
+
kind: "deslop_result",
|
|
154
|
+
marker: "deslop_result",
|
|
155
|
+
missingMarkerBehavior: "resend",
|
|
156
|
+
required: true,
|
|
157
|
+
resolvePrompt: Object.freeze({
|
|
158
|
+
findingsPlaceholder: "{{findings}}",
|
|
159
|
+
marker: "resolve_deslop_findings",
|
|
160
|
+
templateFile: "resolve_deslop_findings.md"
|
|
161
|
+
})
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
function fieldResponseContract(outputDefinitions) {
|
|
165
|
+
const fields = outputDefinitions
|
|
166
|
+
.filter((output) => output?.field && output?.extract)
|
|
167
|
+
.map((output) => Object.freeze({
|
|
168
|
+
extract: output.extract,
|
|
169
|
+
field: output.field,
|
|
170
|
+
formatHint: output.formatHint || "",
|
|
171
|
+
label: output.label || output.field,
|
|
172
|
+
manualEntry: true,
|
|
173
|
+
...(output.multiline === true ? { multiline: true } : {}),
|
|
174
|
+
...(Array.isArray(output.options) ? { options: Object.freeze(output.options.map((option) => Object.freeze({ ...option }))) } : {}),
|
|
175
|
+
required: output.required !== false
|
|
176
|
+
}));
|
|
177
|
+
return Object.freeze({
|
|
178
|
+
fields: Object.freeze(fields),
|
|
179
|
+
kind: "fields",
|
|
180
|
+
missingMarkerBehavior: "manual_or_resend",
|
|
181
|
+
required: fields.some((field) => field.required)
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
function codexHandoff(outputDefinition, {
|
|
142
186
|
autoInject = false,
|
|
143
|
-
promptActionLabel = ""
|
|
187
|
+
promptActionLabel = "",
|
|
188
|
+
promptIntroText = "",
|
|
189
|
+
promptWaitingText = "",
|
|
190
|
+
responseContract = undefined
|
|
144
191
|
} = {}) {
|
|
145
|
-
const
|
|
192
|
+
const outputDefinitions = Object.freeze(Array.isArray(outputDefinition) ? [...outputDefinition] : [outputDefinition]);
|
|
193
|
+
const resolvedResponseContract = responseContract === undefined
|
|
194
|
+
? fieldResponseContract(outputDefinitions)
|
|
195
|
+
: responseContract;
|
|
146
196
|
return Object.freeze({
|
|
147
197
|
...(autoInject ? { autoInject: true } : {}),
|
|
148
|
-
expectedOutput: expectedOutputs[expectedOutputs.length - 1] || null,
|
|
149
|
-
expectedOutputs,
|
|
150
198
|
mode: "inject_prompt",
|
|
151
199
|
promptField: "prompt",
|
|
152
|
-
...(promptActionLabel ? { promptActionLabel } : {})
|
|
200
|
+
...(promptActionLabel ? { promptActionLabel } : {}),
|
|
201
|
+
...(promptIntroText ? { promptIntroText } : {}),
|
|
202
|
+
...(promptWaitingText ? { promptWaitingText } : {}),
|
|
203
|
+
...(resolvedResponseContract ? { responseContract: resolvedResponseContract } : {})
|
|
153
204
|
});
|
|
154
205
|
}
|
|
155
206
|
|
|
207
|
+
function stepAutomationFor({
|
|
208
|
+
codex = undefined,
|
|
209
|
+
id,
|
|
210
|
+
kind,
|
|
211
|
+
requiresExplicitRun = false
|
|
212
|
+
}) {
|
|
213
|
+
if (requiresExplicitRun) {
|
|
214
|
+
return Object.freeze({ mode: "manual" });
|
|
215
|
+
}
|
|
216
|
+
if (id === "dependencies_installed") {
|
|
217
|
+
return Object.freeze({ mode: "terminal" });
|
|
218
|
+
}
|
|
219
|
+
if (kind === "automatic") {
|
|
220
|
+
return Object.freeze({ mode: "immediate" });
|
|
221
|
+
}
|
|
222
|
+
if (kind === "codex_prompt" && codex?.autoInject === true) {
|
|
223
|
+
return Object.freeze({ mode: "codex_prompt" });
|
|
224
|
+
}
|
|
225
|
+
if (kind === "codex_output" && codex?.autoInject === true) {
|
|
226
|
+
return Object.freeze({ mode: "codex_output_prompt" });
|
|
227
|
+
}
|
|
228
|
+
return Object.freeze({ mode: "manual" });
|
|
229
|
+
}
|
|
230
|
+
|
|
156
231
|
const PLAN_EXECUTION_CODEX_HANDOFF = codexHandoff([], {
|
|
157
232
|
autoInject: true,
|
|
158
|
-
promptActionLabel: "Get Codex to execute plan"
|
|
233
|
+
promptActionLabel: "Get Codex to execute plan",
|
|
234
|
+
responseContract: JSKIT_STEP_RESULT_CONTRACT
|
|
159
235
|
});
|
|
160
236
|
const ISSUE_DETAILS_CODEX_HANDOFF = codexHandoff([
|
|
161
237
|
ISSUE_CATEGORY_OUTPUT,
|
|
@@ -163,23 +239,29 @@ const ISSUE_DETAILS_CODEX_HANDOFF = codexHandoff([
|
|
|
163
239
|
ISSUE_DETAILS_OUTPUT
|
|
164
240
|
], {
|
|
165
241
|
autoInject: true,
|
|
166
|
-
promptActionLabel: "Start details conversation"
|
|
242
|
+
promptActionLabel: "Start details conversation",
|
|
243
|
+
promptIntroText: "Codex will gather issue details and classify the issue.",
|
|
244
|
+
promptWaitingText: "Please respond and finalise things with Codex"
|
|
167
245
|
});
|
|
168
246
|
const REVIEW_EXECUTION_CODEX_HANDOFF = codexHandoff([], {
|
|
169
247
|
autoInject: true,
|
|
170
|
-
promptActionLabel: "Run deslop"
|
|
248
|
+
promptActionLabel: "Run deslop",
|
|
249
|
+
responseContract: DESLOP_RESULT_CONTRACT
|
|
171
250
|
});
|
|
172
251
|
const DEEP_UI_CHECK_CODEX_HANDOFF = codexHandoff([], {
|
|
173
252
|
autoInject: true,
|
|
174
|
-
promptActionLabel: "Run Deep UI check"
|
|
253
|
+
promptActionLabel: "Run Deep UI check",
|
|
254
|
+
responseContract: JSKIT_STEP_RESULT_CONTRACT
|
|
175
255
|
});
|
|
176
256
|
const AUTOMATED_CHECK_REPAIR_CODEX_HANDOFF = codexHandoff([], {
|
|
177
257
|
autoInject: true,
|
|
178
|
-
promptActionLabel: "Run automated checks"
|
|
258
|
+
promptActionLabel: "Run automated checks",
|
|
259
|
+
responseContract: JSKIT_STEP_RESULT_CONTRACT
|
|
179
260
|
});
|
|
180
261
|
const BLUEPRINT_CODEX_HANDOFF = codexHandoff([], {
|
|
181
262
|
autoInject: true,
|
|
182
|
-
promptActionLabel: "Update blueprint"
|
|
263
|
+
promptActionLabel: "Update blueprint",
|
|
264
|
+
responseContract: JSKIT_STEP_RESULT_CONTRACT
|
|
183
265
|
});
|
|
184
266
|
|
|
185
267
|
function defineStep({
|
|
@@ -193,11 +275,20 @@ function defineStep({
|
|
|
193
275
|
nextCommandTemplate = DEFAULT_NEXT_COMMAND_TEMPLATE,
|
|
194
276
|
preconditions = [],
|
|
195
277
|
requiresExplicitRun = false,
|
|
278
|
+
submitOptions = {},
|
|
279
|
+
automation = undefined,
|
|
196
280
|
utilityActions = [],
|
|
197
281
|
displayGroupId = "",
|
|
198
282
|
displayGroupLabel = ""
|
|
199
283
|
}) {
|
|
284
|
+
const resolvedAutomation = automation || stepAutomationFor({
|
|
285
|
+
codex,
|
|
286
|
+
id,
|
|
287
|
+
kind,
|
|
288
|
+
requiresExplicitRun
|
|
289
|
+
});
|
|
200
290
|
return Object.freeze({
|
|
291
|
+
automation: Object.freeze({ ...resolvedAutomation }),
|
|
201
292
|
buttonLabel,
|
|
202
293
|
codex,
|
|
203
294
|
description,
|
|
@@ -210,6 +301,7 @@ function defineStep({
|
|
|
210
301
|
nextCommandTemplate,
|
|
211
302
|
preconditions: Object.freeze([...preconditions]),
|
|
212
303
|
requiresExplicitRun,
|
|
304
|
+
submitOptions: Object.freeze({ ...submitOptions }),
|
|
213
305
|
utilityActions: Object.freeze([...utilityActions])
|
|
214
306
|
});
|
|
215
307
|
}
|
|
@@ -259,7 +351,8 @@ const STEP_DEFINITIONS = Object.freeze([
|
|
|
259
351
|
ISSUE_TEXT_OUTPUT
|
|
260
352
|
], {
|
|
261
353
|
autoInject: true,
|
|
262
|
-
promptActionLabel: "Get Codex to create issue text"
|
|
354
|
+
promptActionLabel: "Get Codex to create issue text",
|
|
355
|
+
promptIntroText: "Codex will create a comprehensive issue."
|
|
263
356
|
}),
|
|
264
357
|
description: "Codex drafts the issue title and body; user reviews or edits them; JSKIT saves the approved draft.",
|
|
265
358
|
id: "issue_drafted",
|
|
@@ -294,7 +387,8 @@ const STEP_DEFINITIONS = Object.freeze([
|
|
|
294
387
|
buttonLabel: "Save plan",
|
|
295
388
|
codex: codexHandoff(PLAN_OUTPUT, {
|
|
296
389
|
autoInject: true,
|
|
297
|
-
promptActionLabel: "Get Codex to create plan"
|
|
390
|
+
promptActionLabel: "Get Codex to create plan",
|
|
391
|
+
promptIntroText: "Codex will create an implementation plan based on the issue."
|
|
298
392
|
}),
|
|
299
393
|
description: "Codex writes an implementation plan for the active cycle; cycle 001 plans from the issue, later cycles plan from user rework notes.",
|
|
300
394
|
id: "plan_made",
|
|
@@ -342,12 +436,15 @@ const STEP_DEFINITIONS = Object.freeze([
|
|
|
342
436
|
kind: "user_check",
|
|
343
437
|
label: "Review/deslop",
|
|
344
438
|
nextCommandTemplate: `${JSKIT_CLI_SHELL_COMMAND} session {{session_id}} step --review-findings-remaining false`,
|
|
345
|
-
preconditions: ["session_exists", "worktree_exists", "dependencies_installed", "ready_jskit_app", "issue_metadata_exists", "active_cycle_exists", "deep_ui_check_satisfied"]
|
|
439
|
+
preconditions: ["session_exists", "worktree_exists", "dependencies_installed", "ready_jskit_app", "issue_metadata_exists", "active_cycle_exists", "deep_ui_check_satisfied"],
|
|
440
|
+
submitOptions: Object.freeze({
|
|
441
|
+
reviewFindingsRemaining: false
|
|
442
|
+
})
|
|
346
443
|
}),
|
|
347
444
|
defineStep({
|
|
348
445
|
buttonLabel: "Run automated checks",
|
|
349
446
|
codex: AUTOMATED_CHECK_REPAIR_CODEX_HANDOFF,
|
|
350
|
-
description: "JSKIT asks Codex to run
|
|
447
|
+
description: "JSKIT asks Codex to run the official verification command in the worktree, fix failures, and report the final passing result.",
|
|
351
448
|
id: "automated_checks_run",
|
|
352
449
|
kind: "codex_prompt",
|
|
353
450
|
label: "Automated checks",
|
|
@@ -361,7 +458,14 @@ const STEP_DEFINITIONS = Object.freeze([
|
|
|
361
458
|
kind: "user_check",
|
|
362
459
|
label: "User check",
|
|
363
460
|
nextCommandTemplate: `${JSKIT_CLI_SHELL_COMMAND} session {{session_id}} step --user-check passed`,
|
|
364
|
-
preconditions: ["session_exists", "worktree_exists", "dependencies_installed", "ready_jskit_app", "issue_metadata_exists", "active_cycle_exists", "automated_checks_passed", "deep_ui_check_satisfied"]
|
|
461
|
+
preconditions: ["session_exists", "worktree_exists", "dependencies_installed", "ready_jskit_app", "issue_metadata_exists", "active_cycle_exists", "automated_checks_passed", "deep_ui_check_satisfied"],
|
|
462
|
+
utilityActions: Object.freeze([
|
|
463
|
+
Object.freeze({
|
|
464
|
+
id: "session_app_test",
|
|
465
|
+
kind: "app_test",
|
|
466
|
+
label: "Test app"
|
|
467
|
+
})
|
|
468
|
+
])
|
|
365
469
|
}),
|
|
366
470
|
defineStep({
|
|
367
471
|
buttonLabel: "Commit accepted changes",
|
|
@@ -379,13 +483,6 @@ const STEP_DEFINITIONS = Object.freeze([
|
|
|
379
483
|
label: "Blueprint updated",
|
|
380
484
|
preconditions: ["session_exists", "worktree_exists", "dependencies_installed", "ready_jskit_app", "issue_metadata_exists", "active_cycle_exists", "automated_checks_passed", "deep_ui_check_satisfied", "active_cycle_user_check_passed", "accepted_changes_committed"]
|
|
381
485
|
}),
|
|
382
|
-
defineStep({
|
|
383
|
-
buttonLabel: "Run verification",
|
|
384
|
-
description: "JSKIT runs the final project verification command in the session worktree and records the result.",
|
|
385
|
-
id: "doctor_run",
|
|
386
|
-
label: "Verification run",
|
|
387
|
-
preconditions: ["session_exists", "worktree_exists", "dependencies_installed", "ready_jskit_app", "issue_metadata_exists", "active_cycle_exists", "automated_checks_passed", "deep_ui_check_satisfied", "active_cycle_user_check_passed", "accepted_changes_committed", "blueprint_update_satisfied"]
|
|
388
|
-
}),
|
|
389
486
|
defineStep({
|
|
390
487
|
buttonLabel: "Create final report",
|
|
391
488
|
description: "JSKIT creates the deterministic final session report and comments it on the GitHub issue.",
|
|
@@ -406,7 +503,10 @@ const STEP_DEFINITIONS = Object.freeze([
|
|
|
406
503
|
id: "pr_finalized",
|
|
407
504
|
label: "PR finalized, worktree removed",
|
|
408
505
|
preconditions: ["session_exists", "pr_url_exists", "worktree_exists"],
|
|
409
|
-
requiresExplicitRun: true
|
|
506
|
+
requiresExplicitRun: true,
|
|
507
|
+
submitOptions: Object.freeze({
|
|
508
|
+
mergePr: true
|
|
509
|
+
})
|
|
410
510
|
}),
|
|
411
511
|
defineStep({
|
|
412
512
|
buttonLabel: "Finish session",
|
|
@@ -32,7 +32,7 @@ Implementation rules:
|
|
|
32
32
|
- Keep direct knex exceptional and minimal. Prefer internal json-rest-api seams outside generated CRUD packages and explicit weird-custom feature lanes.
|
|
33
33
|
- Keep runtime, UI, and data concerns separated.
|
|
34
34
|
- Avoid accidental scope expansion.
|
|
35
|
-
- Do not create
|
|
35
|
+
- Do not create `.jskit/WORKBOARD.md`; the session files and receipts are the workflow record.
|
|
36
36
|
- If user-facing UI changes, bring the screen to Material Design and Vuetify quality before calling it done. Include coherent responsive layout, loading, empty, error, disabled, and success states where relevant.
|
|
37
37
|
- If verification needs login, use the app's local development auth bootstrap path rather than a live external auth login.
|
|
38
38
|
|
|
@@ -15,15 +15,9 @@ No stones unturned:
|
|
|
15
15
|
- Read `.jskit/APP_BLUEPRINT.md` and `.jskit/helper-map.md` when present.
|
|
16
16
|
- Read package.json, `.jskit/lock.json`, `config/public.js`, relevant `src/`, relevant `packages/`, and relevant package docs or `npx --no-install jskit show ... --details`.
|
|
17
17
|
- Ask follow-up questions until all important issue details are known.
|
|
18
|
+
- Ask the user for whatever is still needed.
|
|
18
19
|
- Ask for final confirmation before emitting final details.
|
|
19
|
-
|
|
20
|
-
When you are ready to discuss details with the user, output this marker exactly once:
|
|
21
|
-
|
|
22
|
-
[issue_details_conversation_ready]
|
|
23
|
-
ready
|
|
24
|
-
[/issue_details_conversation_ready]
|
|
25
|
-
|
|
26
|
-
Then ask the user for whatever is still needed. Do not output the final markers below until the user has confirmed the details.
|
|
20
|
+
- Do not output the final markers below until the user has confirmed the details.
|
|
27
21
|
|
|
28
22
|
For CRUD or persisted data, confirm entity/table name, fields, field types, nullability, defaults, uniqueness, indexes, relationships, ownership, allowed operations, list/view/form shape, validation, permissions, migration/generator lane, and exact CRUD generator command or exact reason no CRUD generator applies.
|
|
29
23
|
|
|
@@ -15,7 +15,7 @@ Do not run workflow, repair, or mutation commands during issue drafting:
|
|
|
15
15
|
|
|
16
16
|
- Do not run `npx --no-install jskit session`, `npx --no-install jskit session step`, or any command that advances a JSKIT session.
|
|
17
17
|
- Do not run `gh`, `git add`, `git commit`, `git push`, `npm install`, generators, tests, verification, devlinks, or doctor commands.
|
|
18
|
-
- Do not try to fix missing PATH entries or missing
|
|
18
|
+
- Do not try to fix missing PATH entries or missing helper binaries. If a tool is unavailable, continue with read-only file inspection.
|
|
19
19
|
- Do not edit files.
|
|
20
20
|
|
|
21
21
|
Draft an implementation-ready issue, not a broad product essay.
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
Resolve the listed deslop findings in the current worktree.
|
|
2
|
+
|
|
3
|
+
Fix only the listed findings. Keep scope tight. Do not create commits, branches, issues, pull requests, merges, or worktree cleanup.
|
|
4
|
+
|
|
5
|
+
Use existing JSKIT helpers, generators, package seams, and app-local helpers where they fit. Do not introduce helper churn or unrelated refactors while resolving these findings.
|
|
6
|
+
|
|
7
|
+
[resolve_deslop_findings]
|
|
8
|
+
{{findings}}
|
|
9
|
+
[/resolve_deslop_findings]
|
|
10
|
+
|
|
11
|
+
When finished:
|
|
12
|
+
|
|
13
|
+
- Summarize the exact findings fixed.
|
|
14
|
+
- List changed files.
|
|
15
|
+
- List checks run, or say why checks were not run.
|
|
16
|
+
- Wait for the next review prompt.
|
|
@@ -32,5 +32,7 @@ Rules:
|
|
|
32
32
|
When finished, summarize the blueprint result normally, then end with this exact marker:
|
|
33
33
|
|
|
34
34
|
[jskit_step_result]
|
|
35
|
-
|
|
35
|
+
status: complete
|
|
36
|
+
step: blueprint_updated
|
|
37
|
+
summary: Short summary of blueprint changes, or why no blueprint update was needed.
|
|
36
38
|
[/jskit_step_result]
|
|
@@ -3,6 +3,8 @@ import path from "node:path";
|
|
|
3
3
|
import {
|
|
4
4
|
CYCLE_STEP_IDS,
|
|
5
5
|
JSKIT_CLI_SHELL_COMMAND,
|
|
6
|
+
PROMPT_DIRECTORY,
|
|
7
|
+
REVIEW_EXECUTION_CODEX_HANDOFF,
|
|
6
8
|
REVIEW_PASS_LIMIT,
|
|
7
9
|
SESSION_WORKFLOW_VERSION,
|
|
8
10
|
SESSION_STATUS,
|
|
@@ -73,8 +75,14 @@ function normalizeKnownStepIds(stepIds = []) {
|
|
|
73
75
|
}
|
|
74
76
|
|
|
75
77
|
function stepCanExposeStoredPrompt(stepId) {
|
|
76
|
-
const
|
|
77
|
-
|
|
78
|
+
const normalizedStepId = normalizeStepId(stepId);
|
|
79
|
+
const step = STEP_DEFINITION_BY_ID[normalizedStepId];
|
|
80
|
+
return Boolean(
|
|
81
|
+
normalizedStepId === "review_changes_accepted" ||
|
|
82
|
+
step?.codex ||
|
|
83
|
+
step?.kind === "codex_prompt" ||
|
|
84
|
+
step?.kind === "human_input"
|
|
85
|
+
);
|
|
78
86
|
}
|
|
79
87
|
|
|
80
88
|
const DEFAULT_ACTIVE_CYCLE = "001";
|
|
@@ -198,6 +206,35 @@ async function readReviewPasses(paths) {
|
|
|
198
206
|
return Promise.all(passes.map((pass) => readReviewPassInfo(paths, pass)));
|
|
199
207
|
}
|
|
200
208
|
|
|
209
|
+
const REVIEW_STEP_IDS = Object.freeze([
|
|
210
|
+
"review_prompt_rendered",
|
|
211
|
+
"review_changes_accepted"
|
|
212
|
+
]);
|
|
213
|
+
|
|
214
|
+
function latestReviewPass(artifacts = {}) {
|
|
215
|
+
const passes = Array.isArray(artifacts.reviewPasses) ? artifacts.reviewPasses : [];
|
|
216
|
+
return passes.at(-1) || null;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
function latestReviewPassIsPrompted(artifacts = {}) {
|
|
220
|
+
return latestReviewPass(artifacts)?.status === "prompted";
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
async function readPromptFromAbsolutePath(filePath = "") {
|
|
224
|
+
return filePath ? readTextIfExists(filePath) : "";
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
async function readReviewPromptForStep(paths, artifacts = {}) {
|
|
228
|
+
const latestPass = latestReviewPass(artifacts);
|
|
229
|
+
if (latestPass?.status === "prompted") {
|
|
230
|
+
const prompt = await readPromptFromAbsolutePath(latestPass.promptPath);
|
|
231
|
+
if (prompt) {
|
|
232
|
+
return prompt;
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
return "";
|
|
236
|
+
}
|
|
237
|
+
|
|
201
238
|
const PROMPT_ARTIFACT_BY_STEP_ID = Object.freeze({
|
|
202
239
|
issue_drafted: "issue_draft.md",
|
|
203
240
|
issue_details_gathered: "issue_details.md",
|
|
@@ -218,10 +255,13 @@ async function promptArtifactForStep(paths, stepId) {
|
|
|
218
255
|
return PROMPT_ARTIFACT_BY_STEP_ID[normalizedStepId] || "";
|
|
219
256
|
}
|
|
220
257
|
|
|
221
|
-
async function readPromptForStep(paths, stepId) {
|
|
258
|
+
async function readPromptForStep(paths, stepId, artifacts = {}) {
|
|
222
259
|
if (!stepCanExposeStoredPrompt(stepId)) {
|
|
223
260
|
return "";
|
|
224
261
|
}
|
|
262
|
+
if (REVIEW_STEP_IDS.includes(normalizeStepId(stepId))) {
|
|
263
|
+
return readReviewPromptForStep(paths, artifacts);
|
|
264
|
+
}
|
|
225
265
|
const promptArtifact = await promptArtifactForStep(paths, stepId);
|
|
226
266
|
if (promptArtifact) {
|
|
227
267
|
const prompt = await readTextIfExists(path.join(paths.sessionRoot, "prompts", promptArtifact));
|
|
@@ -229,7 +269,7 @@ async function readPromptForStep(paths, stepId) {
|
|
|
229
269
|
return prompt;
|
|
230
270
|
}
|
|
231
271
|
}
|
|
232
|
-
return
|
|
272
|
+
return "";
|
|
233
273
|
}
|
|
234
274
|
|
|
235
275
|
async function readStepFileNames(stepsRoot) {
|
|
@@ -253,11 +293,6 @@ async function readCompletedSteps(paths) {
|
|
|
253
293
|
return applyReviewPassCompletionOverlay(paths, normalizeKnownStepIds([...globalStepIds, ...cycleStepIds]));
|
|
254
294
|
}
|
|
255
295
|
|
|
256
|
-
const REVIEW_STEP_IDS = Object.freeze([
|
|
257
|
-
"review_prompt_rendered",
|
|
258
|
-
"review_changes_accepted"
|
|
259
|
-
]);
|
|
260
|
-
|
|
261
296
|
async function applyReviewPassCompletionOverlay(paths, completedSteps = []) {
|
|
262
297
|
const completed = new Set(completedSteps);
|
|
263
298
|
if (!REVIEW_STEP_IDS.some((stepId) => completed.has(stepId))) {
|
|
@@ -315,7 +350,7 @@ async function readCycles(paths, activeCycle) {
|
|
|
315
350
|
}
|
|
316
351
|
}
|
|
317
352
|
} catch {
|
|
318
|
-
//
|
|
353
|
+
// No cycle directory exists until a session enters a repeatable work cycle.
|
|
319
354
|
}
|
|
320
355
|
}
|
|
321
356
|
return Promise.all([...cycles]
|
|
@@ -494,6 +529,23 @@ function cloneContractValue(value) {
|
|
|
494
529
|
);
|
|
495
530
|
}
|
|
496
531
|
|
|
532
|
+
async function publicCodexContract(codex = null) {
|
|
533
|
+
if (!codex || typeof codex !== "object" || Array.isArray(codex)) {
|
|
534
|
+
return null;
|
|
535
|
+
}
|
|
536
|
+
const clonedCodex = cloneContractValue(codex);
|
|
537
|
+
const resolvePrompt = clonedCodex.responseContract?.resolvePrompt;
|
|
538
|
+
const templateFile = normalizeText(resolvePrompt?.templateFile || "");
|
|
539
|
+
if (templateFile) {
|
|
540
|
+
const template = await readTextIfExists(path.join(PROMPT_DIRECTORY, templateFile));
|
|
541
|
+
clonedCodex.responseContract.resolvePrompt = {
|
|
542
|
+
...resolvePrompt,
|
|
543
|
+
template
|
|
544
|
+
};
|
|
545
|
+
}
|
|
546
|
+
return clonedCodex;
|
|
547
|
+
}
|
|
548
|
+
|
|
497
549
|
function stepRepeatabilityContract(stepId) {
|
|
498
550
|
if (!CYCLE_STEP_IDS.includes(normalizeStepId(stepId))) {
|
|
499
551
|
return {
|
|
@@ -513,6 +565,8 @@ function stepRepeatabilityContract(stepId) {
|
|
|
513
565
|
|
|
514
566
|
function publicStepDefinition(step, index) {
|
|
515
567
|
return {
|
|
568
|
+
automation: cloneContractValue(step.automation || { mode: "manual" }),
|
|
569
|
+
...(step.codex ? { codex: cloneContractValue(step.codex) } : {}),
|
|
516
570
|
description: step.description,
|
|
517
571
|
displayGroupId: step.displayGroupId || "",
|
|
518
572
|
displayGroupLabel: step.displayGroupLabel || "",
|
|
@@ -523,6 +577,7 @@ function publicStepDefinition(step, index) {
|
|
|
523
577
|
label: step.label,
|
|
524
578
|
...stepRepeatabilityContract(step.id),
|
|
525
579
|
requiresExplicitRun: step.requiresExplicitRun === true,
|
|
580
|
+
submitOptions: cloneContractValue(step.submitOptions || {}),
|
|
526
581
|
utilityActions: cloneContractValue(step.utilityActions || [])
|
|
527
582
|
};
|
|
528
583
|
}
|
|
@@ -534,8 +589,7 @@ function buildStepDefinitions() {
|
|
|
534
589
|
function stepIsRetryableWhenBlocked(stepId) {
|
|
535
590
|
return [
|
|
536
591
|
"automated_checks_run",
|
|
537
|
-
"deep_ui_check_run"
|
|
538
|
-
"doctor_run"
|
|
592
|
+
"deep_ui_check_run"
|
|
539
593
|
].includes(normalizeStepId(stepId));
|
|
540
594
|
}
|
|
541
595
|
|
|
@@ -607,11 +661,13 @@ function buildCurrentStepAction(stepId, artifacts = {}) {
|
|
|
607
661
|
if (step.id === "review_changes_accepted") {
|
|
608
662
|
alternateActions.push({
|
|
609
663
|
id: "request_another_review_pass",
|
|
664
|
+
helpText: "Use this only when important review findings remain. Studio will record these notes and loop back to Codex review.",
|
|
610
665
|
input: {
|
|
611
666
|
formatHint: "markdown",
|
|
612
667
|
label: "What important findings remain?",
|
|
613
668
|
multiline: true,
|
|
614
669
|
name: "reviewFindings",
|
|
670
|
+
placeholder: "List the specific findings Codex still needs to address.",
|
|
615
671
|
required: true,
|
|
616
672
|
type: "text"
|
|
617
673
|
},
|
|
@@ -626,6 +682,7 @@ function buildCurrentStepAction(stepId, artifacts = {}) {
|
|
|
626
682
|
if (step.id === "pr_finalized") {
|
|
627
683
|
alternateActions.push({
|
|
628
684
|
id: "close_without_merge",
|
|
685
|
+
helpText: "Leave the PR open, record the reason, and remove the session worktree without merging.",
|
|
629
686
|
input: {
|
|
630
687
|
formatHint: "markdown",
|
|
631
688
|
label: "Why is this session finishing without merge?",
|
|
@@ -679,10 +736,13 @@ function buildCurrentStepAction(stepId, artifacts = {}) {
|
|
|
679
736
|
alternateActions,
|
|
680
737
|
buttonLabel: dynamicButtonLabel,
|
|
681
738
|
description: dynamicDescription,
|
|
739
|
+
displayGroupId: step.displayGroupId,
|
|
740
|
+
displayGroupLabel: step.displayGroupLabel,
|
|
682
741
|
index: STEP_IDS.indexOf(step.id),
|
|
683
742
|
input: cloneContractValue(step.input),
|
|
684
743
|
kind: step.kind,
|
|
685
744
|
label: dynamicButtonLabel,
|
|
745
|
+
automation: cloneContractValue(step.automation || { mode: "manual" }),
|
|
686
746
|
...stepRepeatabilityContract(step.id),
|
|
687
747
|
requiredInput: cloneContractValue(step.input),
|
|
688
748
|
requiresExplicitRun: step.requiresExplicitRun === true,
|
|
@@ -690,13 +750,29 @@ function buildCurrentStepAction(stepId, artifacts = {}) {
|
|
|
690
750
|
retryable: artifacts.status === SESSION_STATUS.BLOCKED && stepIsRetryableWhenBlocked(step.id),
|
|
691
751
|
skipReason: skipReasonForStep(step.id, artifacts),
|
|
692
752
|
stepId: step.id,
|
|
753
|
+
submitOptions: cloneContractValue(step.submitOptions || {}),
|
|
693
754
|
utilityActions: cloneContractValue(dynamicUtilityActions)
|
|
694
755
|
};
|
|
695
756
|
}
|
|
696
757
|
|
|
697
|
-
function
|
|
758
|
+
function rawCodexHandoff(stepId, artifacts = {}) {
|
|
759
|
+
if (normalizeStepId(stepId) === "review_changes_accepted" && latestReviewPassIsPrompted(artifacts)) {
|
|
760
|
+
return cloneContractValue(REVIEW_EXECUTION_CODEX_HANDOFF);
|
|
761
|
+
}
|
|
698
762
|
const step = STEP_DEFINITION_BY_ID[stepId];
|
|
699
|
-
|
|
763
|
+
const codex = step?.codex ? cloneContractValue(step.codex) : null;
|
|
764
|
+
if (
|
|
765
|
+
codex &&
|
|
766
|
+
normalizeStepId(stepId) === "plan_made" &&
|
|
767
|
+
normalizeCycleNumber(artifacts.activeCycle || "") !== "001"
|
|
768
|
+
) {
|
|
769
|
+
codex.promptIntroText = "Codex will create a revised implementation plan based on the rework notes.";
|
|
770
|
+
}
|
|
771
|
+
return codex;
|
|
772
|
+
}
|
|
773
|
+
|
|
774
|
+
async function buildCodexHandoff(stepId, artifacts = {}) {
|
|
775
|
+
return publicCodexContract(rawCodexHandoff(stepId, artifacts));
|
|
700
776
|
}
|
|
701
777
|
|
|
702
778
|
async function readSessionArtifacts(paths) {
|
|
@@ -798,7 +874,7 @@ async function readSessionArtifacts(paths) {
|
|
|
798
874
|
(completedSteps.includes(currentStep) || currentStepIndex < 0 || currentStepIndex > nextStepIndex)
|
|
799
875
|
? nextStep
|
|
800
876
|
: currentStep || nextStep;
|
|
801
|
-
const prompt = await readPromptForStep(paths, effectiveCurrentStep);
|
|
877
|
+
const prompt = await readPromptForStep(paths, effectiveCurrentStep, { reviewPasses });
|
|
802
878
|
|
|
803
879
|
return {
|
|
804
880
|
codexThreadId,
|
|
@@ -969,7 +1045,7 @@ async function buildSessionResponse(paths, {
|
|
|
969
1045
|
commandLogPath: artifacts.commandLogPath || "",
|
|
970
1046
|
stepDefinitions: buildStepDefinitions(),
|
|
971
1047
|
currentStepAction: buildCurrentStepAction(currentStep, artifacts),
|
|
972
|
-
codex: codex === undefined ? buildCodexHandoff(currentStep, artifacts) :
|
|
1048
|
+
codex: codex === undefined ? await buildCodexHandoff(currentStep, artifacts) : await publicCodexContract(codex),
|
|
973
1049
|
prompt: responsePrompt,
|
|
974
1050
|
nextCommand: buildNextCommand(paths.sessionId || "", currentStep),
|
|
975
1051
|
issueUrl: artifacts.issueUrl || "",
|
|
@@ -1120,7 +1196,8 @@ async function failSession(paths, {
|
|
|
1120
1196
|
repairCommand = "",
|
|
1121
1197
|
preconditions = [],
|
|
1122
1198
|
status = SESSION_STATUS.BLOCKED,
|
|
1123
|
-
prompt = ""
|
|
1199
|
+
prompt = "",
|
|
1200
|
+
codex = undefined
|
|
1124
1201
|
}) {
|
|
1125
1202
|
if (paths.sessionRoot && await fileExists(paths.sessionRoot)) {
|
|
1126
1203
|
await markStatus(paths, status);
|
|
@@ -1129,6 +1206,7 @@ async function failSession(paths, {
|
|
|
1129
1206
|
ok: false,
|
|
1130
1207
|
status,
|
|
1131
1208
|
prompt,
|
|
1209
|
+
codex,
|
|
1132
1210
|
preconditions,
|
|
1133
1211
|
errors: [
|
|
1134
1212
|
createError({
|
|
@@ -9,6 +9,7 @@ import path from "node:path";
|
|
|
9
9
|
import {
|
|
10
10
|
BLUEPRINT_CODEX_HANDOFF,
|
|
11
11
|
AUTOMATED_CHECK_REPAIR_CODEX_HANDOFF,
|
|
12
|
+
CYCLE_STEP_IDS,
|
|
12
13
|
DEEP_UI_CHECK_CODEX_HANDOFF,
|
|
13
14
|
ISSUE_DETAILS_CODEX_HANDOFF,
|
|
14
15
|
PLAN_EXECUTION_CODEX_HANDOFF,
|
|
@@ -16,6 +17,7 @@ import {
|
|
|
16
17
|
REVIEW_EXECUTION_CODEX_HANDOFF,
|
|
17
18
|
SESSION_STATUS,
|
|
18
19
|
SESSION_WORKFLOW_VERSION,
|
|
20
|
+
STEP_DEFINITION_BY_ID,
|
|
19
21
|
STEP_DEFINITIONS,
|
|
20
22
|
STEP_IDS,
|
|
21
23
|
STEP_PRECONDITION_NAMES
|
|
@@ -165,6 +167,15 @@ function extractMarkedText(value = "", marker = "") {
|
|
|
165
167
|
return normalizeText(matches.length > 0 ? matches[matches.length - 1][1] : "");
|
|
166
168
|
}
|
|
167
169
|
|
|
170
|
+
function extractMarkedField(value = "", fieldName = "") {
|
|
171
|
+
const normalizedFieldName = normalizeText(fieldName);
|
|
172
|
+
if (!normalizedFieldName) {
|
|
173
|
+
return "";
|
|
174
|
+
}
|
|
175
|
+
const pattern = new RegExp(`^\\s*${normalizedFieldName.replace(/[.*+?^${}()|[\]\\]/gu, "\\$&")}\\s*:\\s*(.+)$`, "imu");
|
|
176
|
+
return normalizeText(pattern.exec(normalizeText(value))?.[1] || "");
|
|
177
|
+
}
|
|
178
|
+
|
|
168
179
|
function extractIssueTitle(value = "") {
|
|
169
180
|
return extractMarkedText(value, "issue_title");
|
|
170
181
|
}
|
|
@@ -211,6 +222,62 @@ async function writePromptArtifact(paths, fileName, prompt) {
|
|
|
211
222
|
await writeTextFile(path.join(paths.sessionRoot, "prompts", fileName), prompt);
|
|
212
223
|
}
|
|
213
224
|
|
|
225
|
+
async function codexResultPath(paths, stepId) {
|
|
226
|
+
if (CYCLE_STEP_IDS.includes(stepId)) {
|
|
227
|
+
const activeCycle = await readActiveCycle(paths);
|
|
228
|
+
return path.join(cycleRootPath(paths, activeCycle), "codex_results", `${stepId}.md`);
|
|
229
|
+
}
|
|
230
|
+
return path.join(paths.sessionRoot, "codex_results", `${stepId}.md`);
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
function codexResponseContractForStep(stepId) {
|
|
234
|
+
const contract = STEP_DEFINITION_BY_ID[stepId]?.codex?.responseContract;
|
|
235
|
+
return contract && typeof contract === "object" && !Array.isArray(contract) ? contract : null;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
async function requireCodexStepResult(paths, stepId, result, preconditions = [], contractOverride = null) {
|
|
239
|
+
const contract = contractOverride || codexResponseContractForStep(stepId);
|
|
240
|
+
if (contract?.required !== true || !contract.marker) {
|
|
241
|
+
return null;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
const source = String(result || "");
|
|
245
|
+
if (!source.trim()) {
|
|
246
|
+
return failSession(paths, {
|
|
247
|
+
code: "codex_result_required",
|
|
248
|
+
message: `The ${STEP_DEFINITION_BY_ID[stepId]?.label || stepId} step has already produced a Codex prompt. Paste Codex's final marked result with --codex-result - before JSKIT records the receipt.`,
|
|
249
|
+
repairCommand: `jskit session ${paths.sessionId} step --codex-result -`,
|
|
250
|
+
preconditions
|
|
251
|
+
});
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
const markedResult = extractMarkedText(source, contract.marker);
|
|
255
|
+
if (!markedResult) {
|
|
256
|
+
return failSession(paths, {
|
|
257
|
+
code: "codex_result_marker_missing",
|
|
258
|
+
message: `Codex output for ${STEP_DEFINITION_BY_ID[stepId]?.label || stepId} must include [${contract.marker}]... [/${contract.marker}] before JSKIT can advance.`,
|
|
259
|
+
repairCommand: `jskit session ${paths.sessionId} step --codex-result -`,
|
|
260
|
+
preconditions
|
|
261
|
+
});
|
|
262
|
+
}
|
|
263
|
+
const stepField = normalizeText(contract.stepField);
|
|
264
|
+
if (stepField) {
|
|
265
|
+
const resultStep = extractMarkedField(markedResult, stepField);
|
|
266
|
+
if (resultStep !== stepId) {
|
|
267
|
+
return failSession(paths, {
|
|
268
|
+
code: "codex_result_step_mismatch",
|
|
269
|
+
message: `Codex output for ${STEP_DEFINITION_BY_ID[stepId]?.label || stepId} must include ${stepField}: ${stepId} inside [${contract.marker}] before JSKIT can advance.`,
|
|
270
|
+
repairCommand: `jskit session ${paths.sessionId} step --codex-result -`,
|
|
271
|
+
preconditions
|
|
272
|
+
});
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
await writeTextFile(await codexResultPath(paths, stepId), source);
|
|
277
|
+
await appendAgentDecisions(paths, extractAgentDecisions(source));
|
|
278
|
+
return null;
|
|
279
|
+
}
|
|
280
|
+
|
|
214
281
|
function commandText(command, args = []) {
|
|
215
282
|
return [command, ...args].map((part) => {
|
|
216
283
|
const value = String(part || "");
|
|
@@ -1050,11 +1117,15 @@ async function makePlan(paths, options = {}, context = {}) {
|
|
|
1050
1117
|
});
|
|
1051
1118
|
}
|
|
1052
1119
|
|
|
1053
|
-
async function renderPlanExecutionPrompt(paths,
|
|
1120
|
+
async function renderPlanExecutionPrompt(paths, options = {}, context = {}) {
|
|
1054
1121
|
const preconditions = context.preconditions || [];
|
|
1055
1122
|
const activeCycle = await readActiveCycle(paths);
|
|
1056
1123
|
const executionPromptPath = path.join(paths.sessionRoot, "prompts", cyclePlanExecutionPromptFileName(activeCycle));
|
|
1057
1124
|
if (await fileExists(executionPromptPath)) {
|
|
1125
|
+
const codexResultFailure = await requireCodexStepResult(paths, "plan_executed", options.codexResult, preconditions);
|
|
1126
|
+
if (codexResultFailure) {
|
|
1127
|
+
return codexResultFailure;
|
|
1128
|
+
}
|
|
1058
1129
|
await writeReceipt(paths, "plan_executed", `Cycle ${activeCycle} plan execution completed by Codex.`);
|
|
1059
1130
|
await markStatus(paths, SESSION_STATUS.RUNNING);
|
|
1060
1131
|
return buildSessionResponse(paths, {
|
|
@@ -1402,7 +1473,8 @@ async function acceptReviewChanges(paths, options = {}) {
|
|
|
1402
1473
|
async function runAutomatedChecks(paths, {
|
|
1403
1474
|
stepId,
|
|
1404
1475
|
label
|
|
1405
|
-
}) {
|
|
1476
|
+
}, options = {}, context = {}) {
|
|
1477
|
+
const preconditions = context.preconditions || [];
|
|
1406
1478
|
const [command, args] = await doctorCommandForWorktree(paths.worktree);
|
|
1407
1479
|
const promptFileName = `${stepId}.md`;
|
|
1408
1480
|
const promptPath = path.join(paths.sessionRoot, "prompts", promptFileName);
|
|
@@ -1411,6 +1483,10 @@ async function runAutomatedChecks(paths, {
|
|
|
1411
1483
|
const checkCommand = [command, ...args].join(" ");
|
|
1412
1484
|
|
|
1413
1485
|
if (await fileExists(promptPath)) {
|
|
1486
|
+
const codexResultFailure = await requireCodexStepResult(paths, stepId, options.codexResult, preconditions);
|
|
1487
|
+
if (codexResultFailure) {
|
|
1488
|
+
return codexResultFailure;
|
|
1489
|
+
}
|
|
1414
1490
|
await writeTextFile(
|
|
1415
1491
|
path.join(checksRoot, `${stepId}.json`),
|
|
1416
1492
|
`${JSON.stringify({
|
|
@@ -1423,7 +1499,9 @@ async function runAutomatedChecks(paths, {
|
|
|
1423
1499
|
);
|
|
1424
1500
|
await writeReceipt(paths, stepId, `${label} completed by Codex: ${checkCommand}.`);
|
|
1425
1501
|
await markStatus(paths, SESSION_STATUS.RUNNING);
|
|
1426
|
-
return buildSessionResponse(paths
|
|
1502
|
+
return buildSessionResponse(paths, {
|
|
1503
|
+
preconditions
|
|
1504
|
+
});
|
|
1427
1505
|
}
|
|
1428
1506
|
|
|
1429
1507
|
const prompt = await renderPrompt(paths, "automated_checks.md", {
|
|
@@ -1443,6 +1521,7 @@ async function runAutomatedChecks(paths, {
|
|
|
1443
1521
|
await markStatus(paths, SESSION_STATUS.WAITING_FOR_USER);
|
|
1444
1522
|
return buildSessionResponse(paths, {
|
|
1445
1523
|
codex: AUTOMATED_CHECK_REPAIR_CODEX_HANDOFF,
|
|
1524
|
+
preconditions,
|
|
1446
1525
|
prompt,
|
|
1447
1526
|
status: SESSION_STATUS.WAITING_FOR_USER
|
|
1448
1527
|
});
|
|
@@ -1501,6 +1580,10 @@ async function runDeepUiCheck(paths, {
|
|
|
1501
1580
|
const promptFileName = `${stepId}.md`;
|
|
1502
1581
|
const promptPath = path.join(paths.sessionRoot, "prompts", promptFileName);
|
|
1503
1582
|
if (await fileExists(promptPath)) {
|
|
1583
|
+
const codexResultFailure = await requireCodexStepResult(paths, stepId, options.codexResult, preconditions);
|
|
1584
|
+
if (codexResultFailure) {
|
|
1585
|
+
return codexResultFailure;
|
|
1586
|
+
}
|
|
1504
1587
|
await writeReceipt(paths, stepId, `${label} completed by Codex.`);
|
|
1505
1588
|
await markStatus(paths, SESSION_STATUS.RUNNING);
|
|
1506
1589
|
return buildSessionResponse(paths, {
|
|
@@ -1644,7 +1727,7 @@ async function commitAcceptedChanges(paths, _options = {}, context = {}) {
|
|
|
1644
1727
|
});
|
|
1645
1728
|
}
|
|
1646
1729
|
|
|
1647
|
-
async function updateBlueprint(paths,
|
|
1730
|
+
async function updateBlueprint(paths, options = {}, context = {}) {
|
|
1648
1731
|
const preconditions = context.preconditions || [];
|
|
1649
1732
|
const issueText = await readTrimmedFile(path.join(paths.sessionRoot, "issue.md"));
|
|
1650
1733
|
const issueTitle = await readTrimmedFile(path.join(paths.sessionRoot, "issue_title")) || titleFromIssue(issueText);
|
|
@@ -1657,6 +1740,10 @@ async function updateBlueprint(paths, _options = {}, context = {}) {
|
|
|
1657
1740
|
const blueprintPromptPath = path.join(paths.sessionRoot, "prompts", "update_blueprint.md");
|
|
1658
1741
|
|
|
1659
1742
|
if (await fileExists(blueprintPromptPath)) {
|
|
1743
|
+
const codexResultFailure = await requireCodexStepResult(paths, "blueprint_updated", options.codexResult, preconditions);
|
|
1744
|
+
if (codexResultFailure) {
|
|
1745
|
+
return codexResultFailure;
|
|
1746
|
+
}
|
|
1660
1747
|
const changedFiles = await changedFilesInWorktree(paths);
|
|
1661
1748
|
const unexpectedChanges = changedFiles.filter((file) => file !== ".jskit/APP_BLUEPRINT.md");
|
|
1662
1749
|
if (unexpectedChanges.length > 0) {
|
|
@@ -1755,68 +1842,6 @@ async function doctorCommandForWorktree(worktree) {
|
|
|
1755
1842
|
return ["npx", ["--no-install", "jskit", "app", "verify"]];
|
|
1756
1843
|
}
|
|
1757
1844
|
|
|
1758
|
-
async function runDoctor(paths) {
|
|
1759
|
-
const repairCommitFailure = await maybeCommitDoctorRepair(paths);
|
|
1760
|
-
if (repairCommitFailure) {
|
|
1761
|
-
return repairCommitFailure;
|
|
1762
|
-
}
|
|
1763
|
-
const [command, args] = await doctorCommandForWorktree(paths.worktree);
|
|
1764
|
-
const result = await runLoggedCommand(paths, "doctor_run", command, args, {
|
|
1765
|
-
cwd: paths.worktree,
|
|
1766
|
-
timeout: 1000 * 60 * 15
|
|
1767
|
-
});
|
|
1768
|
-
await writeTextFile(path.join(paths.sessionRoot, "doctor.log"), result.output);
|
|
1769
|
-
if (!result.ok) {
|
|
1770
|
-
const prompt = await renderPrompt(paths, "doctor_failure.md", {
|
|
1771
|
-
doctor_output: result.output
|
|
1772
|
-
});
|
|
1773
|
-
await writePromptArtifact(paths, "doctor_failure.md", prompt);
|
|
1774
|
-
return failSession(paths, {
|
|
1775
|
-
code: "doctor_failed",
|
|
1776
|
-
message: "Doctor/verification command failed. Paste the failure prompt into Codex, then rerun this step.",
|
|
1777
|
-
repairCommand: `${command} ${args.join(" ")}`,
|
|
1778
|
-
prompt
|
|
1779
|
-
});
|
|
1780
|
-
}
|
|
1781
|
-
await writeReceipt(paths, "doctor_run", `Doctor command passed: ${command} ${args.join(" ")}.`);
|
|
1782
|
-
await markStatus(paths, SESSION_STATUS.RUNNING);
|
|
1783
|
-
return buildSessionResponse(paths);
|
|
1784
|
-
}
|
|
1785
|
-
|
|
1786
|
-
async function maybeCommitDoctorRepair(paths) {
|
|
1787
|
-
if (!await fileExists(path.join(paths.sessionRoot, "doctor.log")) || await fileExists(path.join(paths.sessionRoot, "steps", "doctor_run"))) {
|
|
1788
|
-
return null;
|
|
1789
|
-
}
|
|
1790
|
-
const status = await worktreeStatus(paths.worktree);
|
|
1791
|
-
if (!status.ok) {
|
|
1792
|
-
return failSession(paths, {
|
|
1793
|
-
code: "git_status_failed",
|
|
1794
|
-
message: status.output || "Failed to inspect verification repair changes.",
|
|
1795
|
-
repairCommand: `git -C ${paths.worktree} status --short`
|
|
1796
|
-
});
|
|
1797
|
-
}
|
|
1798
|
-
if (status.changedFiles.length < 1) {
|
|
1799
|
-
return null;
|
|
1800
|
-
}
|
|
1801
|
-
const result = await commitWorktree(paths, {
|
|
1802
|
-
message: `Verification repairs for ${paths.sessionId}`
|
|
1803
|
-
});
|
|
1804
|
-
if (!result.ok) {
|
|
1805
|
-
return failSession(paths, {
|
|
1806
|
-
code: "doctor_repair_commit_failed",
|
|
1807
|
-
message: result.output || "Failed to commit verification repair changes.",
|
|
1808
|
-
repairCommand: `git -C ${paths.worktree} status --short`
|
|
1809
|
-
});
|
|
1810
|
-
}
|
|
1811
|
-
await writeTextFile(path.join(paths.sessionRoot, "doctor_repair_commit.json"), `${JSON.stringify({
|
|
1812
|
-
changedFiles: result.changedFiles || [],
|
|
1813
|
-
commit: await currentHead(paths),
|
|
1814
|
-
committedAt: timestampForReceipt(),
|
|
1815
|
-
ok: true
|
|
1816
|
-
}, null, 2)}\n`);
|
|
1817
|
-
return null;
|
|
1818
|
-
}
|
|
1819
|
-
|
|
1820
1845
|
async function commitLinesSinceBase(paths) {
|
|
1821
1846
|
const baseCommit = await readTrimmedFile(path.join(paths.sessionRoot, "base_commit"));
|
|
1822
1847
|
const args = baseCommit
|
|
@@ -2536,10 +2561,10 @@ const STEP_RUNNERS = Object.freeze({
|
|
|
2536
2561
|
issue_details_gathered: saveIssueDetails,
|
|
2537
2562
|
plan_made: makePlan,
|
|
2538
2563
|
plan_executed: renderPlanExecutionPrompt,
|
|
2539
|
-
automated_checks_run: (paths) => runAutomatedChecks(paths, {
|
|
2564
|
+
automated_checks_run: (paths, options, context) => runAutomatedChecks(paths, {
|
|
2540
2565
|
label: "Automated checks",
|
|
2541
2566
|
stepId: "automated_checks_run"
|
|
2542
|
-
}),
|
|
2567
|
+
}, options, context),
|
|
2543
2568
|
deep_ui_check_run: (paths, options, context) => runDeepUiCheck(paths, {
|
|
2544
2569
|
label: "Deep UI check",
|
|
2545
2570
|
phase: "pre_review",
|
|
@@ -2550,7 +2575,6 @@ const STEP_RUNNERS = Object.freeze({
|
|
|
2550
2575
|
user_check_completed: userCheck,
|
|
2551
2576
|
changes_committed: commitAcceptedChanges,
|
|
2552
2577
|
blueprint_updated: updateBlueprint,
|
|
2553
|
-
doctor_run: runDoctor,
|
|
2554
2578
|
final_report_created: createFinalReport,
|
|
2555
2579
|
pr_created: createPr,
|
|
2556
2580
|
pr_finalized: finalizePr,
|
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
The JSKIT session doctor or verification step failed.
|
|
2
|
-
|
|
3
|
-
Session:
|
|
4
|
-
|
|
5
|
-
{{session_id}}
|
|
6
|
-
|
|
7
|
-
Worktree:
|
|
8
|
-
|
|
9
|
-
{{worktree}}
|
|
10
|
-
|
|
11
|
-
Failure output:
|
|
12
|
-
|
|
13
|
-
{{doctor_output}}
|
|
14
|
-
|
|
15
|
-
Fix the root cause in this worktree. Do not silence the failure or remove checks to make the step pass.
|
|
16
|
-
|
|
17
|
-
Diagnosis rules:
|
|
18
|
-
|
|
19
|
-
- Re-read the issue, `issue_details.md`, the active cycle plan file, `agent_decisions.md`, `.jskit/APP_BLUEPRINT.md`, `.jskit/helper-map.md`, check receipts, and UI check receipts before repairing.
|
|
20
|
-
- Identify whether the failure is dependency/setup, JSKIT metadata, generated contract drift, routing/surface wiring, CRUD ownership, UI verification receipt, test-auth, or ordinary application code.
|
|
21
|
-
- Prefer repairing the JSKIT-owned contract or generated metadata over adding local-path hacks.
|
|
22
|
-
- Do not run `npm install` only because optional agent docs are missing. Run installs only when the failure or a JSKIT setup/session step requires dependency repair.
|
|
23
|
-
- If a generator/package command is the correct repair, use the `npx --no-install jskit` command rather than hand-recreating generated structure.
|
|
24
|
-
- For UI receipt failures, run the relevant Playwright check and record it with `npx --no-install jskit app verify-ui --command "<playwright command>" --feature "<label>" --auth-mode <mode>` when possible.
|
|
25
|
-
- If login is required, use the app's development auth bootstrap path rather than a live external auth flow.
|
|
26
|
-
|
|
27
|
-
Do not push, open a PR, merge, or remove the worktree. When the fix is ready, report the root cause, files changed, verification, and anything still unverified. The user or Studio will rerun the JSKIT session step.
|
|
28
|
-
|
|
29
|
-
If the repair records a meaningful verification decision, tradeoff, missing coverage, or root-cause explanation future steps should know, include concise entries with reasons in this exact marker block:
|
|
30
|
-
|
|
31
|
-
```text
|
|
32
|
-
[agent_decisions]
|
|
33
|
-
<verification or repair decisions, or "No new decisions.">
|
|
34
|
-
[/agent_decisions]
|
|
35
|
-
```
|