@jskit-ai/jskit-cli 0.2.80 → 0.2.81
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +4 -4
- package/src/server/commandHandlers/session.js +71 -3
- package/src/server/core/argParser.js +12 -0
- package/src/server/core/commandCatalog.js +14 -7
- package/src/server/sessionRuntime/constants.js +150 -126
- package/src/server/sessionRuntime/paths.js +2 -4
- package/src/server/sessionRuntime/preconditions.js +25 -35
- package/src/server/sessionRuntime/prompts/app_blueprint.md +26 -2
- package/src/server/sessionRuntime/prompts/doctor_failure.md +12 -1
- package/src/server/sessionRuntime/prompts/execute_plan.md +35 -0
- package/src/server/sessionRuntime/prompts/new_issue.md +21 -3
- package/src/server/sessionRuntime/prompts/plan_issue.md +50 -0
- package/src/server/sessionRuntime/prompts/pr_failure.md +14 -1
- package/src/server/sessionRuntime/prompts/review_changes.md +36 -15
- package/src/server/sessionRuntime/prompts/user_check.md +7 -3
- package/src/server/sessionRuntime/responses.js +146 -19
- package/src/server/sessionRuntime/worktrees.js +31 -0
- package/src/server/sessionRuntime.js +419 -128
- package/src/server/sessionRuntime/prompts/implement_issue.md +0 -25
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.81",
|
|
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.80",
|
|
24
|
+
"@jskit-ai/kernel": "0.1.72",
|
|
25
|
+
"@jskit-ai/shell-web": "0.1.71"
|
|
26
26
|
},
|
|
27
27
|
"engines": {
|
|
28
28
|
"node": ">=20 <23"
|
|
@@ -4,6 +4,7 @@ import {
|
|
|
4
4
|
adoptCodexThreadId,
|
|
5
5
|
buildSessionErrorResponse,
|
|
6
6
|
createSession,
|
|
7
|
+
inspectSessionDiff,
|
|
7
8
|
inspectSessionDetails,
|
|
8
9
|
listSessions,
|
|
9
10
|
runSessionStep
|
|
@@ -52,6 +53,17 @@ function writeSessionText(stdout, payload) {
|
|
|
52
53
|
stdout.write(payload.prompt);
|
|
53
54
|
stdout.write("\n");
|
|
54
55
|
}
|
|
56
|
+
if (payload.gitStatus !== undefined && payload.unstagedDiff !== undefined) {
|
|
57
|
+
stdout.write("\nGit status:\n");
|
|
58
|
+
stdout.write(payload.gitStatus || "No changes.");
|
|
59
|
+
stdout.write("\n");
|
|
60
|
+
const diff = [payload.stagedDiff, payload.unstagedDiff, payload.untrackedDiff].filter(Boolean).join("\n");
|
|
61
|
+
if (diff) {
|
|
62
|
+
stdout.write("\nDiff:\n");
|
|
63
|
+
stdout.write(diff);
|
|
64
|
+
stdout.write("\n");
|
|
65
|
+
}
|
|
66
|
+
}
|
|
55
67
|
if (payload.errors?.length) {
|
|
56
68
|
stdout.write("Errors:\n");
|
|
57
69
|
for (const error of payload.errors) {
|
|
@@ -135,6 +147,21 @@ async function resolveStepInputs({
|
|
|
135
147
|
cwd,
|
|
136
148
|
sessionId
|
|
137
149
|
}) {
|
|
150
|
+
const issueTitle = await resolveTextInput({
|
|
151
|
+
codePrefix: "issue_title",
|
|
152
|
+
fileOption: "issue-title-file",
|
|
153
|
+
inlineOptions,
|
|
154
|
+
io,
|
|
155
|
+
repairCommand: `jskit session ${sessionId} step --issue-title "<title>" --issue -`,
|
|
156
|
+
cwd,
|
|
157
|
+
sessionId,
|
|
158
|
+
stdinOption: "-",
|
|
159
|
+
textOption: "issue-title"
|
|
160
|
+
});
|
|
161
|
+
if (issueTitle.ok === false) {
|
|
162
|
+
return issueTitle;
|
|
163
|
+
}
|
|
164
|
+
|
|
138
165
|
const issue = await resolveTextInput({
|
|
139
166
|
codePrefix: "issue",
|
|
140
167
|
fileOption: "issue-file",
|
|
@@ -150,9 +177,26 @@ async function resolveStepInputs({
|
|
|
150
177
|
return issue;
|
|
151
178
|
}
|
|
152
179
|
|
|
180
|
+
const plan = await resolveTextInput({
|
|
181
|
+
codePrefix: "plan",
|
|
182
|
+
fileOption: "plan-file",
|
|
183
|
+
inlineOptions,
|
|
184
|
+
io,
|
|
185
|
+
repairCommand: `jskit session ${sessionId} step --plan -`,
|
|
186
|
+
cwd,
|
|
187
|
+
sessionId,
|
|
188
|
+
stdinOption: "-",
|
|
189
|
+
textOption: "plan"
|
|
190
|
+
});
|
|
191
|
+
if (plan.ok === false) {
|
|
192
|
+
return plan;
|
|
193
|
+
}
|
|
194
|
+
|
|
153
195
|
return {
|
|
154
196
|
issue: issue.value,
|
|
155
|
-
|
|
197
|
+
issueTitle: issueTitle.value,
|
|
198
|
+
ok: true,
|
|
199
|
+
plan: plan.value
|
|
156
200
|
};
|
|
157
201
|
}
|
|
158
202
|
|
|
@@ -164,6 +208,20 @@ function normalizeStepOptions(inlineOptions = {}) {
|
|
|
164
208
|
};
|
|
165
209
|
}
|
|
166
210
|
|
|
211
|
+
function resolveListArchiveOption(options = {}) {
|
|
212
|
+
const archives = [];
|
|
213
|
+
if (options.abandoned) {
|
|
214
|
+
archives.push("abandoned");
|
|
215
|
+
}
|
|
216
|
+
if (options.completed) {
|
|
217
|
+
archives.push("completed");
|
|
218
|
+
}
|
|
219
|
+
if (options.all) {
|
|
220
|
+
archives.push("all");
|
|
221
|
+
}
|
|
222
|
+
return archives.length > 0 ? archives : "active";
|
|
223
|
+
}
|
|
224
|
+
|
|
167
225
|
function createSessionCommands() {
|
|
168
226
|
return {
|
|
169
227
|
async commandSession({
|
|
@@ -178,7 +236,10 @@ function createSessionCommands() {
|
|
|
178
236
|
let payload;
|
|
179
237
|
|
|
180
238
|
if (!first) {
|
|
181
|
-
payload = await listSessions({
|
|
239
|
+
payload = await listSessions({
|
|
240
|
+
targetRoot: cwd,
|
|
241
|
+
archive: resolveListArchiveOption(options)
|
|
242
|
+
});
|
|
182
243
|
} else if (first === "create") {
|
|
183
244
|
payload = await createSession({ targetRoot: cwd });
|
|
184
245
|
} else if (second === "step") {
|
|
@@ -195,7 +256,9 @@ function createSessionCommands() {
|
|
|
195
256
|
sessionId: first,
|
|
196
257
|
options: {
|
|
197
258
|
...normalizeStepOptions(inlineOptions),
|
|
198
|
-
issue: stepInputs.issue
|
|
259
|
+
issue: stepInputs.issue,
|
|
260
|
+
issueTitle: stepInputs.issueTitle,
|
|
261
|
+
plan: stepInputs.plan
|
|
199
262
|
}
|
|
200
263
|
});
|
|
201
264
|
} else if (second === "abandon") {
|
|
@@ -203,6 +266,11 @@ function createSessionCommands() {
|
|
|
203
266
|
targetRoot: cwd,
|
|
204
267
|
sessionId: first
|
|
205
268
|
});
|
|
269
|
+
} else if (second === "diff") {
|
|
270
|
+
payload = await inspectSessionDiff({
|
|
271
|
+
targetRoot: cwd,
|
|
272
|
+
sessionId: first
|
|
273
|
+
});
|
|
206
274
|
} else if (second === "adopt-codex-thread") {
|
|
207
275
|
payload = await adoptCodexThreadId({
|
|
208
276
|
targetRoot: cwd,
|
|
@@ -23,6 +23,8 @@ function parseArgs(argv, { createCliError } = {}) {
|
|
|
23
23
|
verbose: false,
|
|
24
24
|
json: false,
|
|
25
25
|
all: false,
|
|
26
|
+
abandoned: false,
|
|
27
|
+
completed: false,
|
|
26
28
|
concrete: false,
|
|
27
29
|
help: true,
|
|
28
30
|
inlineOptions: {}
|
|
@@ -50,6 +52,8 @@ function parseArgs(argv, { createCliError } = {}) {
|
|
|
50
52
|
verbose: false,
|
|
51
53
|
json: false,
|
|
52
54
|
all: false,
|
|
55
|
+
abandoned: false,
|
|
56
|
+
completed: false,
|
|
53
57
|
concrete: false,
|
|
54
58
|
help: false,
|
|
55
59
|
inlineOptions: {}
|
|
@@ -109,6 +113,14 @@ function parseArgs(argv, { createCliError } = {}) {
|
|
|
109
113
|
options.all = true;
|
|
110
114
|
continue;
|
|
111
115
|
}
|
|
116
|
+
if (token === "--abandoned") {
|
|
117
|
+
options.abandoned = true;
|
|
118
|
+
continue;
|
|
119
|
+
}
|
|
120
|
+
if (token === "--completed") {
|
|
121
|
+
options.completed = true;
|
|
122
|
+
continue;
|
|
123
|
+
}
|
|
112
124
|
if (token === "--concrete") {
|
|
113
125
|
options.concrete = true;
|
|
114
126
|
continue;
|
|
@@ -9,7 +9,9 @@ const OPTION_FLAG_LABELS = Object.freeze({
|
|
|
9
9
|
checkDiLabels: "--check-di-labels",
|
|
10
10
|
verbose: "--verbose",
|
|
11
11
|
json: "--json",
|
|
12
|
-
all: "--all"
|
|
12
|
+
all: "--all",
|
|
13
|
+
abandoned: "--abandoned",
|
|
14
|
+
completed: "--completed"
|
|
13
15
|
});
|
|
14
16
|
|
|
15
17
|
const PARSED_BOOLEAN_OPTION_KEYS = Object.freeze(Object.keys(OPTION_FLAG_LABELS));
|
|
@@ -211,15 +213,18 @@ const COMMAND_DESCRIPTORS = Object.freeze({
|
|
|
211
213
|
}),
|
|
212
214
|
Object.freeze({
|
|
213
215
|
name: "[step|abandon|adopt-codex-thread]",
|
|
214
|
-
description: "Run the next step, abandon a session, or attach a Codex thread id."
|
|
216
|
+
description: "Run the next step, inspect a diff, abandon a session, or attach a Codex thread id."
|
|
215
217
|
})
|
|
216
218
|
]),
|
|
217
219
|
defaults: Object.freeze([
|
|
218
220
|
"Active session state lives in .jskit/sessions/active/<session_id> under the current target app.",
|
|
219
|
-
"Session worktrees live in .jskit/sessions/
|
|
221
|
+
"Session worktrees live in .jskit/sessions/active/<session_id>/worktree.",
|
|
220
222
|
"The session id is timestamp-based and is the primary key.",
|
|
223
|
+
"Bare list output includes active sessions only; use --abandoned, --completed, or --all for archived sessions.",
|
|
221
224
|
"Use --json for the stable machine-readable contract consumed by JSKIT AI Studio.",
|
|
222
|
-
"Use --issue - to read approved issue
|
|
225
|
+
"Use --issue - to read approved issue body from stdin.",
|
|
226
|
+
"Use --issue-title when the approved issue title is separate from the body.",
|
|
227
|
+
"Use --plan - to read the approved implementation plan from stdin."
|
|
223
228
|
]),
|
|
224
229
|
examples: Object.freeze([
|
|
225
230
|
Object.freeze({
|
|
@@ -228,15 +233,17 @@ const COMMAND_DESCRIPTORS = Object.freeze({
|
|
|
228
233
|
"jskit session create",
|
|
229
234
|
"jskit session 2026-05-11_21-42-08 step",
|
|
230
235
|
"jskit session 2026-05-11_21-42-08 step --prompt \"Fix the customer filters\"",
|
|
231
|
-
"jskit session 2026-05-11_21-42-08 step --issue -"
|
|
236
|
+
"jskit session 2026-05-11_21-42-08 step --issue-title \"Fix customer filters\" --issue -",
|
|
237
|
+
"jskit session 2026-05-11_21-42-08 step --plan -",
|
|
238
|
+
"jskit session 2026-05-11_21-42-08 diff --json"
|
|
232
239
|
])
|
|
233
240
|
})
|
|
234
241
|
]),
|
|
235
242
|
fullUse:
|
|
236
|
-
"jskit session [create|<sessionId>] [step|abandon|adopt-codex-thread] [--prompt <text>] [--issue <text>|--issue-file <path>] [--user-check <passed|failed>] [--codex-thread-id <id>] [--json]",
|
|
243
|
+
"jskit session [create|<sessionId>] [step|diff|abandon|adopt-codex-thread] [--prompt <text>] [--issue-title <text>|--issue-title-file <path>] [--issue <text>|--issue-file <path>] [--plan <text>|--plan-file <path>] [--user-check <passed|failed>] [--codex-thread-id <id>] [--abandoned|--completed|--all] [--json]",
|
|
237
244
|
showHelpOnBareInvocation: false,
|
|
238
245
|
handlerName: "commandSession",
|
|
239
|
-
allowedFlagKeys: Object.freeze(["json"]),
|
|
246
|
+
allowedFlagKeys: Object.freeze(["json", "abandoned", "completed", "all"]),
|
|
240
247
|
inlineOptionMode: "delegate",
|
|
241
248
|
allowedValueOptionNames: Object.freeze([]),
|
|
242
249
|
canDelegateInlineOptions: (positional = []) => Array.isArray(positional) && positional.length > 0
|
|
@@ -16,15 +16,62 @@ const SESSION_STATE_RELATIVE_PATH = ".jskit/sessions";
|
|
|
16
16
|
const PROMPT_DIRECTORY = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "prompts");
|
|
17
17
|
|
|
18
18
|
const INPUT_NONE = Object.freeze({ type: "none" });
|
|
19
|
+
const ISSUE_TITLE_INPUT = Object.freeze({
|
|
20
|
+
extract: "issue_title",
|
|
21
|
+
formatHint: "text",
|
|
22
|
+
label: "Approved issue title",
|
|
23
|
+
name: "issueTitle",
|
|
24
|
+
required: true,
|
|
25
|
+
type: "text"
|
|
26
|
+
});
|
|
19
27
|
const ISSUE_TEXT_INPUT = Object.freeze({
|
|
20
28
|
extract: "issue_text",
|
|
21
29
|
formatHint: "markdown",
|
|
22
|
-
label: "Approved issue
|
|
30
|
+
label: "Approved issue body",
|
|
23
31
|
multiline: true,
|
|
24
32
|
name: "issue",
|
|
25
33
|
required: true,
|
|
26
34
|
type: "text"
|
|
27
35
|
});
|
|
36
|
+
const ISSUE_DRAFT_INPUT = Object.freeze({
|
|
37
|
+
fields: Object.freeze([
|
|
38
|
+
ISSUE_TITLE_INPUT,
|
|
39
|
+
ISSUE_TEXT_INPUT
|
|
40
|
+
]),
|
|
41
|
+
type: "object"
|
|
42
|
+
});
|
|
43
|
+
const ISSUE_TITLE_OUTPUT = Object.freeze({
|
|
44
|
+
extract: "issue_title",
|
|
45
|
+
field: "issueTitle",
|
|
46
|
+
formatHint: "text",
|
|
47
|
+
label: "Issue title",
|
|
48
|
+
required: true
|
|
49
|
+
});
|
|
50
|
+
const ISSUE_TEXT_OUTPUT = Object.freeze({
|
|
51
|
+
extract: "issue_text",
|
|
52
|
+
field: "issue",
|
|
53
|
+
formatHint: "markdown",
|
|
54
|
+
label: "Issue body",
|
|
55
|
+
multiline: true,
|
|
56
|
+
required: true
|
|
57
|
+
});
|
|
58
|
+
const PLAN_INPUT = Object.freeze({
|
|
59
|
+
extract: "plan",
|
|
60
|
+
formatHint: "markdown",
|
|
61
|
+
label: "Approved plan",
|
|
62
|
+
multiline: true,
|
|
63
|
+
name: "plan",
|
|
64
|
+
required: true,
|
|
65
|
+
type: "text"
|
|
66
|
+
});
|
|
67
|
+
const PLAN_OUTPUT = Object.freeze({
|
|
68
|
+
extract: "plan",
|
|
69
|
+
field: "plan",
|
|
70
|
+
formatHint: "markdown",
|
|
71
|
+
label: "Plan",
|
|
72
|
+
multiline: true,
|
|
73
|
+
required: true
|
|
74
|
+
});
|
|
28
75
|
const USER_CHECK_INPUT = Object.freeze({
|
|
29
76
|
label: "User check result",
|
|
30
77
|
name: "userCheck",
|
|
@@ -35,19 +82,30 @@ const USER_CHECK_INPUT = Object.freeze({
|
|
|
35
82
|
required: true,
|
|
36
83
|
type: "choice"
|
|
37
84
|
});
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
})
|
|
42
|
-
|
|
43
|
-
function codexHandoff(expectedOutput) {
|
|
85
|
+
function codexHandoff(expectedOutput, {
|
|
86
|
+
autoInject = false,
|
|
87
|
+
promptActionLabel = ""
|
|
88
|
+
} = {}) {
|
|
89
|
+
const expectedOutputs = Object.freeze(Array.isArray(expectedOutput) ? [...expectedOutput] : [expectedOutput]);
|
|
44
90
|
return Object.freeze({
|
|
45
|
-
|
|
91
|
+
...(autoInject ? { autoInject: true } : {}),
|
|
92
|
+
expectedOutput: expectedOutputs[expectedOutputs.length - 1] || null,
|
|
93
|
+
expectedOutputs,
|
|
46
94
|
mode: "inject_prompt",
|
|
47
|
-
promptField: "prompt"
|
|
95
|
+
promptField: "prompt",
|
|
96
|
+
...(promptActionLabel ? { promptActionLabel } : {})
|
|
48
97
|
});
|
|
49
98
|
}
|
|
50
99
|
|
|
100
|
+
const PLAN_EXECUTION_CODEX_HANDOFF = codexHandoff([], {
|
|
101
|
+
autoInject: true,
|
|
102
|
+
promptActionLabel: "Execute plan"
|
|
103
|
+
});
|
|
104
|
+
const REVIEW_EXECUTION_CODEX_HANDOFF = codexHandoff([], {
|
|
105
|
+
autoInject: true,
|
|
106
|
+
promptActionLabel: "Start review"
|
|
107
|
+
});
|
|
108
|
+
|
|
51
109
|
function defineStep({
|
|
52
110
|
buttonLabel,
|
|
53
111
|
codex = undefined,
|
|
@@ -57,7 +115,8 @@ function defineStep({
|
|
|
57
115
|
kind = "automatic",
|
|
58
116
|
label,
|
|
59
117
|
nextCommandTemplate = "jskit session {{session_id}} step",
|
|
60
|
-
preconditions = []
|
|
118
|
+
preconditions = [],
|
|
119
|
+
utilityActions = []
|
|
61
120
|
}) {
|
|
62
121
|
return Object.freeze({
|
|
63
122
|
buttonLabel,
|
|
@@ -68,27 +127,35 @@ function defineStep({
|
|
|
68
127
|
kind,
|
|
69
128
|
label,
|
|
70
129
|
nextCommandTemplate,
|
|
71
|
-
preconditions: Object.freeze([...preconditions])
|
|
130
|
+
preconditions: Object.freeze([...preconditions]),
|
|
131
|
+
utilityActions: Object.freeze([...utilityActions])
|
|
72
132
|
});
|
|
73
133
|
}
|
|
74
134
|
|
|
75
135
|
const STEP_DEFINITIONS = Object.freeze([
|
|
76
136
|
defineStep({
|
|
77
137
|
buttonLabel: "Create session",
|
|
78
|
-
description: "Create the filesystem-backed session
|
|
138
|
+
description: "Create the filesystem-backed session record.",
|
|
79
139
|
id: "session_created",
|
|
80
140
|
label: "Session created"
|
|
81
141
|
}),
|
|
82
142
|
defineStep({
|
|
83
143
|
buttonLabel: "Create worktree",
|
|
84
|
-
description: "Create the isolated
|
|
144
|
+
description: "Create the isolated branch and worktree for this session.",
|
|
85
145
|
id: "worktree_created",
|
|
86
146
|
label: "Worktree created",
|
|
87
147
|
preconditions: ["session_exists", "git_repository", "git_current_branch"]
|
|
88
148
|
}),
|
|
89
149
|
defineStep({
|
|
90
|
-
buttonLabel: "
|
|
91
|
-
description: "
|
|
150
|
+
buttonLabel: "Install dependencies",
|
|
151
|
+
description: "Install Node dependencies inside the session worktree before Codex starts.",
|
|
152
|
+
id: "dependencies_installed",
|
|
153
|
+
label: "Dependencies installed",
|
|
154
|
+
preconditions: ["session_exists", "worktree_exists"]
|
|
155
|
+
}),
|
|
156
|
+
defineStep({
|
|
157
|
+
buttonLabel: "Submit prompt to Codex",
|
|
158
|
+
description: "Capture the initial change request and send it to Codex to draft a GitHub issue.",
|
|
92
159
|
id: "issue_prompt_rendered",
|
|
93
160
|
input: Object.freeze({
|
|
94
161
|
label: "What should change?",
|
|
@@ -99,20 +166,19 @@ const STEP_DEFINITIONS = Object.freeze([
|
|
|
99
166
|
type: "text"
|
|
100
167
|
}),
|
|
101
168
|
kind: "human_input",
|
|
102
|
-
label: "
|
|
169
|
+
label: "Initial issue prompt",
|
|
103
170
|
nextCommandTemplate: "jskit session {{session_id}} step --prompt \"<what should change>\"",
|
|
104
171
|
preconditions: ["session_exists"]
|
|
105
172
|
}),
|
|
106
173
|
defineStep({
|
|
107
|
-
buttonLabel: "Save issue
|
|
108
|
-
codex: codexHandoff(
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
description: "Save the approved issue text produced by Codex.",
|
|
174
|
+
buttonLabel: "Save issue draft",
|
|
175
|
+
codex: codexHandoff([
|
|
176
|
+
ISSUE_TITLE_OUTPUT,
|
|
177
|
+
ISSUE_TEXT_OUTPUT
|
|
178
|
+
]),
|
|
179
|
+
description: "Save the approved issue title and body drafted by Codex.",
|
|
114
180
|
id: "issue_drafted",
|
|
115
|
-
input:
|
|
181
|
+
input: ISSUE_DRAFT_INPUT,
|
|
116
182
|
kind: "codex_output",
|
|
117
183
|
label: "Issue drafted",
|
|
118
184
|
nextCommandTemplate: "jskit session {{session_id}} step --issue -",
|
|
@@ -120,113 +186,83 @@ const STEP_DEFINITIONS = Object.freeze([
|
|
|
120
186
|
}),
|
|
121
187
|
defineStep({
|
|
122
188
|
buttonLabel: "Create GitHub issue",
|
|
123
|
-
description: "Create the GitHub issue
|
|
189
|
+
description: "Create the GitHub issue from the approved draft.",
|
|
124
190
|
id: "issue_created",
|
|
125
191
|
label: "Issue created",
|
|
126
192
|
preconditions: ["session_exists", "issue_text_exists", "github_auth", "github_origin"]
|
|
127
193
|
}),
|
|
128
194
|
defineStep({
|
|
129
|
-
buttonLabel: "
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
195
|
+
buttonLabel: "Execute plan",
|
|
196
|
+
codex: codexHandoff(PLAN_OUTPUT),
|
|
197
|
+
description: "Save the approved implementation plan, comment it on the GitHub issue, and submit it to Codex.",
|
|
198
|
+
id: "plan_made",
|
|
199
|
+
input: PLAN_INPUT,
|
|
200
|
+
kind: "codex_output",
|
|
201
|
+
label: "Plan execution",
|
|
202
|
+
nextCommandTemplate: "jskit session {{session_id}} step --plan -",
|
|
203
|
+
preconditions: ["session_exists", "issue_text_exists", "issue_url_exists", "worktree_exists"]
|
|
135
204
|
}),
|
|
136
205
|
defineStep({
|
|
137
|
-
buttonLabel: "
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
206
|
+
buttonLabel: "Accept changes",
|
|
207
|
+
description: "Review the worktree diff and accept the changes as ready to commit.",
|
|
208
|
+
id: "implementation_changes_accepted",
|
|
209
|
+
kind: "user_check",
|
|
210
|
+
label: "Changes accepted",
|
|
211
|
+
preconditions: ["session_exists", "worktree_exists"],
|
|
212
|
+
utilityActions: Object.freeze([
|
|
213
|
+
Object.freeze({
|
|
214
|
+
id: "session_diff",
|
|
215
|
+
kind: "diff",
|
|
216
|
+
label: "Review changes",
|
|
217
|
+
nextCommandTemplate: "jskit session {{session_id}} diff"
|
|
218
|
+
})
|
|
219
|
+
])
|
|
144
220
|
}),
|
|
145
221
|
defineStep({
|
|
146
222
|
buttonLabel: "Commit implementation",
|
|
147
|
-
description: "Commit the implementation changes in the session worktree.",
|
|
223
|
+
description: "Commit the accepted implementation changes in the session worktree.",
|
|
148
224
|
id: "implementation_changes_committed",
|
|
149
225
|
label: "Changes committed",
|
|
150
226
|
preconditions: ["session_exists", "worktree_exists"]
|
|
151
227
|
}),
|
|
152
228
|
defineStep({
|
|
153
|
-
buttonLabel: "
|
|
154
|
-
description: "
|
|
155
|
-
id: "
|
|
229
|
+
buttonLabel: "Start review",
|
|
230
|
+
description: "Submit the code review prompt to Codex for the committed changes.",
|
|
231
|
+
id: "review_prompt_rendered",
|
|
156
232
|
kind: "codex_prompt",
|
|
157
|
-
label: "Review
|
|
233
|
+
label: "Review execution",
|
|
158
234
|
preconditions: ["session_exists", "worktree_exists"]
|
|
159
235
|
}),
|
|
160
236
|
defineStep({
|
|
161
|
-
buttonLabel: "
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
id: "initial_review_changes_detected",
|
|
165
|
-
kind: "codex_output",
|
|
166
|
-
label: "Review changes detected 1",
|
|
167
|
-
preconditions: ["session_exists", "worktree_exists"]
|
|
168
|
-
}),
|
|
169
|
-
defineStep({
|
|
170
|
-
buttonLabel: "Save user check 1",
|
|
171
|
-
description: "Record whether the first user check passed.",
|
|
172
|
-
id: "initial_user_check_completed",
|
|
173
|
-
input: USER_CHECK_INPUT,
|
|
237
|
+
buttonLabel: "Accept review changes",
|
|
238
|
+
description: "Review the post-review worktree diff and accept it as ready to commit.",
|
|
239
|
+
id: "review_changes_accepted",
|
|
174
240
|
kind: "user_check",
|
|
175
|
-
label: "
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
preconditions: ["session_exists", "worktree_exists"]
|
|
241
|
+
label: "Review changes accepted",
|
|
242
|
+
preconditions: ["session_exists", "worktree_exists"],
|
|
243
|
+
utilityActions: Object.freeze([
|
|
244
|
+
Object.freeze({
|
|
245
|
+
id: "session_diff",
|
|
246
|
+
kind: "diff",
|
|
247
|
+
label: "Review changes",
|
|
248
|
+
nextCommandTemplate: "jskit session {{session_id}} diff"
|
|
249
|
+
})
|
|
250
|
+
])
|
|
186
251
|
}),
|
|
187
252
|
defineStep({
|
|
188
|
-
buttonLabel: "
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
kind: "codex_output",
|
|
193
|
-
label: "Review changes detected 2",
|
|
253
|
+
buttonLabel: "Commit review changes",
|
|
254
|
+
description: "Commit accepted review changes, or record that no review changes were needed.",
|
|
255
|
+
id: "review_changes_committed",
|
|
256
|
+
label: "Review changes committed",
|
|
194
257
|
preconditions: ["session_exists", "worktree_exists"]
|
|
195
258
|
}),
|
|
196
259
|
defineStep({
|
|
197
|
-
buttonLabel: "Save user check
|
|
198
|
-
description: "Record whether the
|
|
199
|
-
id: "
|
|
260
|
+
buttonLabel: "Save user check",
|
|
261
|
+
description: "Record whether the user’s manual check passed.",
|
|
262
|
+
id: "user_check_completed",
|
|
200
263
|
input: USER_CHECK_INPUT,
|
|
201
264
|
kind: "user_check",
|
|
202
|
-
label: "User check
|
|
203
|
-
nextCommandTemplate: "jskit session {{session_id}} step --user-check passed",
|
|
204
|
-
preconditions: ["session_exists"]
|
|
205
|
-
}),
|
|
206
|
-
defineStep({
|
|
207
|
-
buttonLabel: "Render final review prompt",
|
|
208
|
-
description: "Render the final Codex review prompt before verification and PR.",
|
|
209
|
-
id: "final_review_prompt_rendered",
|
|
210
|
-
kind: "codex_prompt",
|
|
211
|
-
label: "Review prompt rendered 3",
|
|
212
|
-
preconditions: ["session_exists", "worktree_exists"]
|
|
213
|
-
}),
|
|
214
|
-
defineStep({
|
|
215
|
-
buttonLabel: "Detect final review changes",
|
|
216
|
-
codex: codexHandoff(CODEX_WORKTREE_OUTPUT),
|
|
217
|
-
description: "Commit any changes Codex made after the final review pass.",
|
|
218
|
-
id: "final_review_changes_detected",
|
|
219
|
-
kind: "codex_output",
|
|
220
|
-
label: "Review changes detected 3",
|
|
221
|
-
preconditions: ["session_exists", "worktree_exists"]
|
|
222
|
-
}),
|
|
223
|
-
defineStep({
|
|
224
|
-
buttonLabel: "Save final user check",
|
|
225
|
-
description: "Record whether the final user check passed.",
|
|
226
|
-
id: "final_user_check_completed",
|
|
227
|
-
input: USER_CHECK_INPUT,
|
|
228
|
-
kind: "user_check",
|
|
229
|
-
label: "User check 3",
|
|
265
|
+
label: "User check",
|
|
230
266
|
nextCommandTemplate: "jskit session {{session_id}} step --user-check passed",
|
|
231
267
|
preconditions: ["session_exists"]
|
|
232
268
|
}),
|
|
@@ -234,36 +270,22 @@ const STEP_DEFINITIONS = Object.freeze([
|
|
|
234
270
|
buttonLabel: "Run verification",
|
|
235
271
|
description: "Run the project verification command in the session worktree.",
|
|
236
272
|
id: "doctor_run",
|
|
237
|
-
label: "
|
|
238
|
-
preconditions: ["session_exists", "worktree_exists"]
|
|
239
|
-
}),
|
|
240
|
-
defineStep({
|
|
241
|
-
buttonLabel: "Push branch",
|
|
242
|
-
description: "Push the session branch to origin.",
|
|
243
|
-
id: "branch_pushed",
|
|
244
|
-
label: "Branch pushed",
|
|
273
|
+
label: "Verification run",
|
|
245
274
|
preconditions: ["session_exists", "worktree_exists"]
|
|
246
275
|
}),
|
|
247
276
|
defineStep({
|
|
248
|
-
buttonLabel: "
|
|
249
|
-
description: "
|
|
277
|
+
buttonLabel: "Push branch and create PR",
|
|
278
|
+
description: "Push the session branch to origin and create a GitHub pull request.",
|
|
250
279
|
id: "pr_created",
|
|
251
|
-
label: "PR created",
|
|
280
|
+
label: "Branch pushed, PR created",
|
|
252
281
|
preconditions: ["session_exists", "worktree_exists"]
|
|
253
282
|
}),
|
|
254
283
|
defineStep({
|
|
255
|
-
buttonLabel: "Merge PR",
|
|
256
|
-
description: "Merge the pull request
|
|
284
|
+
buttonLabel: "Merge PR and remove worktree",
|
|
285
|
+
description: "Merge the pull request, close the GitHub issue, and remove the session worktree.",
|
|
257
286
|
id: "pr_merged",
|
|
258
|
-
label: "PR merged",
|
|
259
|
-
preconditions: ["session_exists", "pr_url_exists"]
|
|
260
|
-
}),
|
|
261
|
-
defineStep({
|
|
262
|
-
buttonLabel: "Remove worktree",
|
|
263
|
-
description: "Remove the session worktree after the PR has merged.",
|
|
264
|
-
id: "worktree_removed",
|
|
265
|
-
label: "Worktree removed",
|
|
266
|
-
preconditions: ["session_exists"]
|
|
287
|
+
label: "PR merged, worktree removed",
|
|
288
|
+
preconditions: ["session_exists", "pr_url_exists", "worktree_exists"]
|
|
267
289
|
}),
|
|
268
290
|
defineStep({
|
|
269
291
|
buttonLabel: "Finish session",
|
|
@@ -292,5 +314,7 @@ export {
|
|
|
292
314
|
STEP_IDS,
|
|
293
315
|
STEP_LABEL_BY_ID,
|
|
294
316
|
STEP_PRECONDITION_NAMES,
|
|
317
|
+
PLAN_EXECUTION_CODEX_HANDOFF,
|
|
318
|
+
REVIEW_EXECUTION_CODEX_HANDOFF,
|
|
295
319
|
SESSION_STATE_RELATIVE_PATH
|
|
296
320
|
};
|
|
@@ -68,10 +68,9 @@ function resolveSessionPaths({ targetRoot, sessionId = "" } = {}) {
|
|
|
68
68
|
const sessionsRoot = path.join(sessionStateRoot, "active");
|
|
69
69
|
const completedSessionsRoot = path.join(sessionStateRoot, "completed");
|
|
70
70
|
const abandonedSessionsRoot = path.join(sessionStateRoot, "abandoned");
|
|
71
|
-
const worktreesRoot = path.join(sessionStateRoot, "worktrees");
|
|
72
71
|
const normalizedSessionId = sessionId ? normalizeSessionId(sessionId) : "";
|
|
73
72
|
const sessionRoot = normalizedSessionId ? path.join(sessionsRoot, normalizedSessionId) : "";
|
|
74
|
-
const worktree = normalizedSessionId ? path.join(
|
|
73
|
+
const worktree = normalizedSessionId ? path.join(sessionRoot, "worktree") : "";
|
|
75
74
|
const branch = normalizedSessionId ? `jskit-studio/${normalizedSessionId}` : "";
|
|
76
75
|
|
|
77
76
|
return Object.freeze({
|
|
@@ -85,8 +84,7 @@ function resolveSessionPaths({ targetRoot, sessionId = "" } = {}) {
|
|
|
85
84
|
sessionsRoot,
|
|
86
85
|
sessionStateRoot,
|
|
87
86
|
targetRoot: normalizedTargetRoot,
|
|
88
|
-
worktree
|
|
89
|
-
worktreesRoot
|
|
87
|
+
worktree
|
|
90
88
|
});
|
|
91
89
|
}
|
|
92
90
|
|