@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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jskit-ai/jskit-cli",
3
- "version": "0.2.80",
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.79",
24
- "@jskit-ai/kernel": "0.1.71",
25
- "@jskit-ai/shell-web": "0.1.70"
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
- ok: true
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({ targetRoot: cwd });
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/worktrees/<session_id>.",
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 text from stdin."
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 text",
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
- const CODEX_WORKTREE_OUTPUT = Object.freeze({
39
- field: "worktree",
40
- formatHint: "git_changes"
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
- expectedOutput,
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 state.",
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 session worktree and branch.",
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: "Render issue prompt",
91
- description: "Render the prompt Codex should use to draft the GitHub issue.",
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: "Issue prompt rendered",
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 text",
108
- codex: codexHandoff(Object.freeze({
109
- extract: "issue_text",
110
- field: "issue",
111
- formatHint: "markdown"
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: ISSUE_TEXT_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 with gh.",
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: "Render implementation prompt",
130
- description: "Render the prompt Codex should use to implement the approved issue.",
131
- id: "implementation_prompt_rendered",
132
- kind: "codex_prompt",
133
- label: "Implementation prompt rendered",
134
- preconditions: ["session_exists", "issue_artifacts"]
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: "Detect implementation changes",
138
- codex: codexHandoff(CODEX_WORKTREE_OUTPUT),
139
- description: "Confirm that Codex changed files in the session worktree.",
140
- id: "implementation_changes_detected",
141
- kind: "codex_output",
142
- label: "Changes detected",
143
- preconditions: ["session_exists", "worktree_exists"]
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: "Render review prompt 1",
154
- description: "Render the first Codex review prompt for the committed changes.",
155
- id: "initial_review_prompt_rendered",
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 prompt rendered 1",
233
+ label: "Review execution",
158
234
  preconditions: ["session_exists", "worktree_exists"]
159
235
  }),
160
236
  defineStep({
161
- buttonLabel: "Detect review changes 1",
162
- codex: codexHandoff(CODEX_WORKTREE_OUTPUT),
163
- description: "Commit any changes Codex made after the first review pass.",
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: "User check 1",
176
- nextCommandTemplate: "jskit session {{session_id}} step --user-check passed",
177
- preconditions: ["session_exists"]
178
- }),
179
- defineStep({
180
- buttonLabel: "Render review prompt 2",
181
- description: "Render the second Codex review prompt for the committed changes.",
182
- id: "followup_review_prompt_rendered",
183
- kind: "codex_prompt",
184
- label: "Review prompt rendered 2",
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: "Detect review changes 2",
189
- codex: codexHandoff(CODEX_WORKTREE_OUTPUT),
190
- description: "Commit any changes Codex made after the second review pass.",
191
- id: "followup_review_changes_detected",
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 2",
198
- description: "Record whether the second user check passed.",
199
- id: "followup_user_check_completed",
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 2",
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: "Doctor run",
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: "Create PR",
249
- description: "Create a GitHub pull request for the session branch.",
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 and close the GitHub issue.",
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(worktreesRoot, normalizedSessionId) : "";
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