@jskit-ai/jskit-cli 0.2.81 → 0.2.82

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.
Files changed (27) hide show
  1. package/package.json +6 -4
  2. package/src/server/appBlueprint.js +1 -1
  3. package/src/server/commandHandlers/helperMap.js +104 -0
  4. package/src/server/commandHandlers/session.js +110 -3
  5. package/src/server/commandHandlers/show.js +169 -34
  6. package/src/server/core/argParser.js +8 -0
  7. package/src/server/core/commandCatalog.js +58 -2
  8. package/src/server/core/createCommandHandlers.js +4 -1
  9. package/src/server/helperMap.js +463 -0
  10. package/src/server/helperMapPaths.js +7 -0
  11. package/src/server/sessionRuntime/appReadiness.js +55 -0
  12. package/src/server/sessionRuntime/constants.js +217 -78
  13. package/src/server/sessionRuntime/preconditions.js +382 -5
  14. package/src/server/sessionRuntime/promptRenderer.js +15 -2
  15. package/src/server/sessionRuntime/prompts/automated_checks.md +42 -0
  16. package/src/server/sessionRuntime/prompts/deep_ui_check.md +53 -0
  17. package/src/server/sessionRuntime/prompts/doctor_failure.md +11 -2
  18. package/src/server/sessionRuntime/prompts/execute_plan.md +32 -6
  19. package/src/server/sessionRuntime/prompts/final_comment.md +3 -1
  20. package/src/server/sessionRuntime/prompts/issue_details.md +52 -0
  21. package/src/server/sessionRuntime/prompts/new_issue.md +15 -2
  22. package/src/server/sessionRuntime/prompts/plan_issue.md +40 -9
  23. package/src/server/sessionRuntime/prompts/review_changes.md +46 -5
  24. package/src/server/sessionRuntime/prompts/update_blueprint.md +36 -0
  25. package/src/server/sessionRuntime/prompts/user_check.md +15 -1
  26. package/src/server/sessionRuntime/responses.js +776 -56
  27. package/src/server/sessionRuntime.js +1658 -123
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jskit-ai/jskit-cli",
3
- "version": "0.2.81",
3
+ "version": "0.2.82",
4
4
  "description": "Bundle and package orchestration CLI for JSKIT apps.",
5
5
  "type": "module",
6
6
  "files": [
@@ -20,9 +20,11 @@
20
20
  "test": "node --test"
21
21
  },
22
22
  "dependencies": {
23
- "@jskit-ai/jskit-catalog": "0.1.80",
24
- "@jskit-ai/kernel": "0.1.72",
25
- "@jskit-ai/shell-web": "0.1.71"
23
+ "@jskit-ai/jskit-catalog": "0.1.81",
24
+ "@jskit-ai/kernel": "0.1.73",
25
+ "@jskit-ai/shell-web": "0.1.72",
26
+ "@vue/compiler-sfc": "^3.5.29",
27
+ "ts-morph": "^28.0.0"
26
28
  },
27
29
  "engines": {
28
30
  "node": ">=20 <23"
@@ -28,7 +28,7 @@ function resolveAppBlueprintPaths(targetRoot = process.cwd()) {
28
28
  function extractAppBlueprintText(value = "") {
29
29
  const text = normalizeText(value);
30
30
  const match = /\[app_blueprint\]([\s\S]*?)\[\/app_blueprint\]/u.exec(text);
31
- return normalizeText(match ? match[1] : text);
31
+ return normalizeText(match ? match[1] : "");
32
32
  }
33
33
 
34
34
  async function readAppPromptTemplate(paths, templateName) {
@@ -0,0 +1,104 @@
1
+ function writeJson(stdout, payload) {
2
+ stdout.write(`${JSON.stringify(payload, null, 2)}\n`);
3
+ }
4
+
5
+ function writeErrors(stdout, payload) {
6
+ for (const error of payload.errors || []) {
7
+ stdout.write(`[${error.code}] ${error.message}\n`);
8
+ if (error.repairCommand) {
9
+ stdout.write(`Repair: ${error.repairCommand}\n`);
10
+ }
11
+ }
12
+ }
13
+
14
+ function writeHelperMapText(stdout, payload) {
15
+ if (Object.hasOwn(payload, "changed")) {
16
+ stdout.write(
17
+ payload.changed
18
+ ? `Updated helper map at ${payload.helperMapMarkdownPath}.\n`
19
+ : `Helper map is up to date at ${payload.helperMapMarkdownPath}.\n`
20
+ );
21
+ return;
22
+ }
23
+ if (payload.exists === false) {
24
+ stdout.write(`No helper map set at ${payload.helperMapMarkdownPath}. Run jskit helper-map update.\n`);
25
+ return;
26
+ }
27
+ if (payload.markdown) {
28
+ stdout.write(payload.markdown);
29
+ return;
30
+ }
31
+ stdout.write(`Helper map is up to date at ${payload.helperMapMarkdownPath}.\n`);
32
+ }
33
+
34
+ function createHelperMapCommands(ctx = {}) {
35
+ const { resolveAppRootFromCwd } = ctx;
36
+
37
+ async function commandHelperMap({
38
+ positional = [],
39
+ options = {},
40
+ cwd,
41
+ stdout
42
+ } = {}) {
43
+ const subcommand = String(positional[0] || "").trim();
44
+ let payload;
45
+
46
+ try {
47
+ const appRoot = await resolveAppRootFromCwd(cwd);
48
+ if (positional.length > 1) {
49
+ payload = {
50
+ ok: false,
51
+ errors: [
52
+ {
53
+ code: "unexpected_helper_map_argument",
54
+ message: `Unexpected helper-map argument: ${positional.slice(1).join(" ")}`,
55
+ repairCommand: "jskit helper-map"
56
+ }
57
+ ]
58
+ };
59
+ } else if (!subcommand) {
60
+ const { readHelperMap } = await import("../helperMap.js");
61
+ payload = await readHelperMap({ targetRoot: appRoot });
62
+ } else if (subcommand === "update") {
63
+ const { updateHelperMap } = await import("../helperMap.js");
64
+ payload = await updateHelperMap({ targetRoot: appRoot });
65
+ } else {
66
+ payload = {
67
+ ok: false,
68
+ errors: [
69
+ {
70
+ code: "unknown_helper_map_subcommand",
71
+ message: `Unknown helper-map subcommand: ${subcommand}`,
72
+ repairCommand: "jskit helper-map update"
73
+ }
74
+ ]
75
+ };
76
+ }
77
+ } catch (error) {
78
+ payload = {
79
+ ok: false,
80
+ errors: [
81
+ {
82
+ code: "helper_map_failed",
83
+ message: String(error?.message || error),
84
+ repairCommand: "jskit helper-map update"
85
+ }
86
+ ]
87
+ };
88
+ }
89
+
90
+ if (options.json) {
91
+ writeJson(stdout, payload);
92
+ } else if (payload.ok === false) {
93
+ writeErrors(stdout, payload);
94
+ } else {
95
+ writeHelperMapText(stdout, payload);
96
+ }
97
+
98
+ return payload.ok === false ? 1 : 0;
99
+ }
100
+
101
+ return { commandHelperMap };
102
+ }
103
+
104
+ export { createHelperMapCommands };
@@ -147,6 +147,19 @@ async function resolveStepInputs({
147
147
  cwd,
148
148
  sessionId
149
149
  }) {
150
+ if (Object.hasOwn(inlineOptions, "blueprint") || Object.hasOwn(inlineOptions, "blueprint-file")) {
151
+ return {
152
+ ok: false,
153
+ payload: buildSessionErrorResponse({
154
+ targetRoot: cwd,
155
+ sessionId,
156
+ code: "session_blueprint_input_removed",
157
+ message: "The session blueprint step no longer accepts --blueprint input. Run the step to get the Codex prompt, let Codex edit .jskit/APP_BLUEPRINT.md, then run the step again.",
158
+ repairCommand: `jskit session ${sessionId} step`
159
+ })
160
+ };
161
+ }
162
+
150
163
  const issueTitle = await resolveTextInput({
151
164
  codePrefix: "issue_title",
152
165
  fileOption: "issue-title-file",
@@ -192,20 +205,109 @@ async function resolveStepInputs({
192
205
  return plan;
193
206
  }
194
207
 
208
+ const issueDetails = await resolveTextInput({
209
+ codePrefix: "issue_details",
210
+ fileOption: "issue-details-file",
211
+ inlineOptions,
212
+ io,
213
+ repairCommand: `jskit session ${sessionId} step --issue-details -`,
214
+ cwd,
215
+ sessionId,
216
+ stdinOption: "-",
217
+ textOption: "issue-details"
218
+ });
219
+ if (issueDetails.ok === false) {
220
+ return issueDetails;
221
+ }
222
+
223
+ const reworkNotes = await resolveTextInput({
224
+ codePrefix: "rework_notes",
225
+ fileOption: "rework-notes-file",
226
+ inlineOptions,
227
+ io,
228
+ repairCommand: `jskit session ${sessionId} step --user-check failed --rework-notes -`,
229
+ cwd,
230
+ sessionId,
231
+ stdinOption: "-",
232
+ textOption: "rework-notes"
233
+ });
234
+ if (reworkNotes.ok === false) {
235
+ return reworkNotes;
236
+ }
237
+
238
+ const skipReason = await resolveTextInput({
239
+ codePrefix: "skip_reason",
240
+ fileOption: "skip-reason-file",
241
+ inlineOptions,
242
+ io,
243
+ repairCommand: `jskit session ${sessionId} step --skip-ui-check --skip-reason "<reason>"`,
244
+ cwd,
245
+ sessionId,
246
+ stdinOption: "-",
247
+ textOption: "skip-reason"
248
+ });
249
+ if (skipReason.ok === false) {
250
+ return skipReason;
251
+ }
252
+
253
+ const agentDecisions = await resolveTextInput({
254
+ codePrefix: "agent_decisions",
255
+ fileOption: "agent-decisions-file",
256
+ inlineOptions,
257
+ io,
258
+ repairCommand: `jskit session ${sessionId} step --agent-decisions -`,
259
+ cwd,
260
+ sessionId,
261
+ stdinOption: "-",
262
+ textOption: "agent-decisions"
263
+ });
264
+ if (agentDecisions.ok === false) {
265
+ return agentDecisions;
266
+ }
267
+
268
+ const closeReason = await resolveTextInput({
269
+ codePrefix: "close_reason",
270
+ fileOption: "close-reason-file",
271
+ inlineOptions,
272
+ io,
273
+ repairCommand: `jskit session ${sessionId} step --close-without-merge --close-reason "<reason>"`,
274
+ cwd,
275
+ sessionId,
276
+ stdinOption: "-",
277
+ textOption: "close-reason"
278
+ });
279
+ if (closeReason.ok === false) {
280
+ return closeReason;
281
+ }
282
+
195
283
  return {
284
+ agentDecisions: agentDecisions.value,
285
+ closeReason: closeReason.value,
196
286
  issue: issue.value,
197
287
  issueTitle: issueTitle.value,
198
288
  ok: true,
199
- plan: plan.value
289
+ plan: plan.value,
290
+ issueDetails: issueDetails.value,
291
+ reworkNotes: reworkNotes.value,
292
+ skipReason: skipReason.value
200
293
  };
201
294
  }
202
295
 
203
296
  function normalizeStepOptions(inlineOptions = {}) {
204
- return {
297
+ const options = {
205
298
  ...inlineOptions,
299
+ closeWithoutMerge: inlineOptions["close-without-merge"] === "true" || inlineOptions.closeWithoutMerge === true,
300
+ mergePr: inlineOptions["merge-pr"] === "true" || inlineOptions.mergePr === true,
206
301
  prompt: inlineOptions.prompt,
302
+ reviewFindings: inlineOptions["review-findings"] || inlineOptions.reviewFindings,
303
+ skipUiCheck: inlineOptions["skip-ui-check"] === "true" || inlineOptions.skipUiCheck === true,
207
304
  userCheck: inlineOptions["user-check"] || inlineOptions.userCheck
208
305
  };
306
+ if (Object.hasOwn(inlineOptions, "review-findings-remaining") || Object.hasOwn(inlineOptions, "reviewFindingsRemaining")) {
307
+ options.reviewFindingsRemaining = inlineOptions["review-findings-remaining"] === "true" ||
308
+ inlineOptions.reviewFindingsRemaining === true;
309
+ }
310
+ return options;
209
311
  }
210
312
 
211
313
  function resolveListArchiveOption(options = {}) {
@@ -258,7 +360,12 @@ function createSessionCommands() {
258
360
  ...normalizeStepOptions(inlineOptions),
259
361
  issue: stepInputs.issue,
260
362
  issueTitle: stepInputs.issueTitle,
261
- plan: stepInputs.plan
363
+ plan: stepInputs.plan,
364
+ issueDetails: stepInputs.issueDetails,
365
+ reworkNotes: stepInputs.reworkNotes,
366
+ skipReason: stepInputs.skipReason,
367
+ agentDecisions: stepInputs.agentDecisions,
368
+ closeReason: stepInputs.closeReason
262
369
  }
263
370
  });
264
371
  } else if (second === "abandon") {
@@ -12,10 +12,17 @@ function createShowCommand(ctx = {}) {
12
12
  createColorFormatter,
13
13
  resolveWrapWidth,
14
14
  writeWrappedItems,
15
+ normalizeRelativePath,
15
16
  normalizeRelativePosixPath,
17
+ resolveAppRootFromCwd,
16
18
  resolvePackageIdInput,
17
19
  loadPackageRegistry,
18
20
  loadBundleRegistry,
21
+ loadAppLocalPackageRegistry,
22
+ loadLockFile,
23
+ mergePackageRegistries,
24
+ path,
25
+ fileExists,
19
26
  inspectPackageOfferings,
20
27
  buildFileWriteGroups,
21
28
  listDeclaredCapabilities,
@@ -28,7 +35,149 @@ function createShowCommand(ctx = {}) {
28
35
  deriveProviderDisplayName
29
36
  } = ctx;
30
37
 
31
- async function commandShow({ positional, options, stdout }) {
38
+ async function renderPackageEntry({
39
+ packageRegistry,
40
+ packageEntry,
41
+ options,
42
+ stdout,
43
+ color
44
+ }) {
45
+ const {
46
+ payload,
47
+ provides,
48
+ requires,
49
+ capabilityDetails
50
+ } = await buildPackageShowPayload({
51
+ packageRegistry,
52
+ packageEntry,
53
+ options,
54
+ inspectPackageOfferings,
55
+ buildFileWriteGroups,
56
+ listDeclaredCapabilities,
57
+ buildCapabilityDetailsForPackage
58
+ });
59
+
60
+ if (options.json) {
61
+ stdout.write(`${JSON.stringify(payload, null, 2)}\n`);
62
+ } else {
63
+ renderPackagePayloadText({
64
+ payload,
65
+ provides,
66
+ requires,
67
+ capabilityDetails,
68
+ options,
69
+ stdout,
70
+ color,
71
+ resolveWrapWidth,
72
+ writeWrappedItems,
73
+ normalizeRelativePosixPath,
74
+ formatPackageSubpathImport,
75
+ normalizePlacementOutlets,
76
+ normalizePlacementContributions,
77
+ shouldShowPackageExportTarget,
78
+ classifyExportedSymbols,
79
+ deriveProviderDisplayName
80
+ });
81
+ }
82
+ }
83
+
84
+ function isLocalPackageLockEntry(lockEntry = {}) {
85
+ const sourceType = String(lockEntry?.source?.type || "").trim();
86
+ return sourceType === "local-package" || sourceType === "app-local-package";
87
+ }
88
+
89
+ function resolveInstalledLocalPackageEntry(lock = {}, id = "") {
90
+ const normalizedId = String(id || "").trim();
91
+ if (!normalizedId) {
92
+ return null;
93
+ }
94
+
95
+ const installedPackages = lock?.installedPackages || {};
96
+ const directEntry = installedPackages[normalizedId];
97
+ if (directEntry && isLocalPackageLockEntry(directEntry)) {
98
+ return {
99
+ packageId: normalizedId,
100
+ lockEntry: directEntry
101
+ };
102
+ }
103
+
104
+ for (const [packageId, lockEntry] of Object.entries(installedPackages)) {
105
+ if (!isLocalPackageLockEntry(lockEntry)) {
106
+ continue;
107
+ }
108
+ const recordedPackageId = String(lockEntry?.packageId || "").trim();
109
+ if (recordedPackageId === normalizedId) {
110
+ return {
111
+ packageId,
112
+ lockEntry
113
+ };
114
+ }
115
+ }
116
+
117
+ return null;
118
+ }
119
+
120
+ async function assertInstalledLocalPackageIsDiscoverable({ appRoot, id }) {
121
+ const { lock } = await loadLockFile(appRoot);
122
+ const installedLocalPackage = resolveInstalledLocalPackageEntry(lock, id);
123
+ if (!installedLocalPackage) {
124
+ return;
125
+ }
126
+
127
+ const { packageId, lockEntry } = installedLocalPackage;
128
+ const source = lockEntry?.source || {};
129
+ const descriptorPath = String(source.descriptorPath || "").trim();
130
+ if (!descriptorPath) {
131
+ throw createCliError(
132
+ `Local package ${packageId} is recorded in .jskit/lock.json but has no descriptorPath.`
133
+ );
134
+ }
135
+
136
+ const absoluteDescriptorPath = path.resolve(appRoot, descriptorPath);
137
+ if (!(await fileExists(absoluteDescriptorPath))) {
138
+ throw createCliError(
139
+ `Local package ${packageId} is recorded in .jskit/lock.json but descriptor is missing at ${descriptorPath}.`
140
+ );
141
+ }
142
+
143
+ const packagePath = String(source.packagePath || "").trim();
144
+ if (packagePath) {
145
+ const packageJsonPath = path.join(appRoot, packagePath, "package.json");
146
+ if (!(await fileExists(packageJsonPath))) {
147
+ throw createCliError(
148
+ `Local package ${packageId} is recorded in .jskit/lock.json but package.json is missing at ${packagePath}/package.json.`
149
+ );
150
+ }
151
+ }
152
+
153
+ const descriptorLabel = normalizeRelativePath(appRoot, absoluteDescriptorPath);
154
+ throw createCliError(
155
+ `Local package ${packageId} is recorded in .jskit/lock.json but was not discoverable from ${descriptorLabel}. Ensure its package.json name matches the descriptor packageId.`
156
+ );
157
+ }
158
+
159
+ async function resolveAppLocalShowTarget({ id, cwd, catalogPackageRegistry }) {
160
+ let appRoot = "";
161
+ try {
162
+ appRoot = await resolveAppRootFromCwd(cwd);
163
+ } catch {
164
+ return null;
165
+ }
166
+
167
+ const appLocalRegistry = await loadAppLocalPackageRegistry(appRoot);
168
+ const resolvedPackageId = resolvePackageIdInput(id, appLocalRegistry);
169
+ if (resolvedPackageId) {
170
+ return {
171
+ packageRegistry: mergePackageRegistries(catalogPackageRegistry, appLocalRegistry),
172
+ packageEntry: appLocalRegistry.get(resolvedPackageId)
173
+ };
174
+ }
175
+
176
+ await assertInstalledLocalPackageIsDiscoverable({ appRoot, id });
177
+ return null;
178
+ }
179
+
180
+ async function commandShow({ positional, options, cwd, stdout }) {
32
181
  const id = String(positional[0] || "").trim();
33
182
  if (!id) {
34
183
  throw createCliError("show requires an id.", { showUsage: true });
@@ -41,43 +190,13 @@ function createShowCommand(ctx = {}) {
41
190
 
42
191
  if (resolvedPackageId) {
43
192
  const packageEntry = packageRegistry.get(resolvedPackageId);
44
- const {
45
- payload,
46
- provides,
47
- requires,
48
- capabilityDetails
49
- } = await buildPackageShowPayload({
193
+ await renderPackageEntry({
50
194
  packageRegistry,
51
195
  packageEntry,
52
196
  options,
53
- inspectPackageOfferings,
54
- buildFileWriteGroups,
55
- listDeclaredCapabilities,
56
- buildCapabilityDetailsForPackage
197
+ stdout,
198
+ color
57
199
  });
58
-
59
- if (options.json) {
60
- stdout.write(`${JSON.stringify(payload, null, 2)}\n`);
61
- } else {
62
- renderPackagePayloadText({
63
- payload,
64
- provides,
65
- requires,
66
- capabilityDetails,
67
- options,
68
- stdout,
69
- color,
70
- resolveWrapWidth,
71
- writeWrappedItems,
72
- normalizeRelativePosixPath,
73
- formatPackageSubpathImport,
74
- normalizePlacementOutlets,
75
- normalizePlacementContributions,
76
- shouldShowPackageExportTarget,
77
- classifyExportedSymbols,
78
- deriveProviderDisplayName
79
- });
80
- }
81
200
  return 0;
82
201
  }
83
202
 
@@ -103,6 +222,22 @@ function createShowCommand(ctx = {}) {
103
222
  return 0;
104
223
  }
105
224
 
225
+ const appLocalTarget = await resolveAppLocalShowTarget({
226
+ id,
227
+ cwd,
228
+ catalogPackageRegistry: packageRegistry
229
+ });
230
+ if (appLocalTarget) {
231
+ await renderPackageEntry({
232
+ packageRegistry: appLocalTarget.packageRegistry,
233
+ packageEntry: appLocalTarget.packageEntry,
234
+ options,
235
+ stdout,
236
+ color
237
+ });
238
+ return 0;
239
+ }
240
+
106
241
  throw createCliError(`Unknown package or bundle: ${id}`);
107
242
  }
108
243
 
@@ -137,6 +137,14 @@ function parseArgs(argv, { createCliError } = {}) {
137
137
  options.inlineOptions.install = "true";
138
138
  continue;
139
139
  }
140
+ if (token === "--skip-ui-check") {
141
+ options.inlineOptions["skip-ui-check"] = "true";
142
+ continue;
143
+ }
144
+ if (token === "--close-without-merge") {
145
+ options.inlineOptions["close-without-merge"] = "true";
146
+ continue;
147
+ }
140
148
 
141
149
  if (token.startsWith("--")) {
142
150
  const withoutPrefix = token.slice(2);
@@ -224,7 +224,16 @@ const COMMAND_DESCRIPTORS = Object.freeze({
224
224
  "Use --json for the stable machine-readable contract consumed by JSKIT AI Studio.",
225
225
  "Use --issue - to read approved issue body from stdin.",
226
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."
227
+ "Use --issue-details - to read confirmed issue details from stdin.",
228
+ "Use --plan - to read the approved implementation plan from stdin.",
229
+ "Use --rework-notes - with --user-check failed to start the next plan cycle.",
230
+ "Use --agent-decisions - to append session-local decision log entries from implementation, UI review, verification, or repair phases.",
231
+ "Use --review-findings-remaining true --review-findings \"<findings>\" when an accepted review pass needs another pass.",
232
+ "Use --review-findings-remaining false only when the review/deslop loop is done.",
233
+ "Use --skip-ui-check --skip-reason \"<reason>\" only when uiImpact is possible and the Deep UI Check is intentionally skipped.",
234
+ "Use --merge-pr true at PR finalization to merge the pull request.",
235
+ "Use --close-without-merge --close-reason \"<reason>\" at PR finalization to complete the session without merging.",
236
+ "Run the blueprint step once to render its Codex prompt, then again after Codex updates .jskit/APP_BLUEPRINT.md."
228
237
  ]),
229
238
  examples: Object.freeze([
230
239
  Object.freeze({
@@ -234,13 +243,22 @@ const COMMAND_DESCRIPTORS = Object.freeze({
234
243
  "jskit session 2026-05-11_21-42-08 step",
235
244
  "jskit session 2026-05-11_21-42-08 step --prompt \"Fix the customer filters\"",
236
245
  "jskit session 2026-05-11_21-42-08 step --issue-title \"Fix customer filters\" --issue -",
246
+ "jskit session 2026-05-11_21-42-08 step --issue-details -",
237
247
  "jskit session 2026-05-11_21-42-08 step --plan -",
248
+ "jskit session 2026-05-11_21-42-08 step --agent-decisions -",
249
+ "jskit session 2026-05-11_21-42-08 step --review-findings-remaining true --review-findings \"A helper duplication fix needs another pass\"",
250
+ "jskit session 2026-05-11_21-42-08 step --review-findings-remaining false",
251
+ "jskit session 2026-05-11_21-42-08 step --skip-ui-check --skip-reason \"No user-facing UI changes\"",
252
+ "jskit session 2026-05-11_21-42-08 step",
253
+ "jskit session 2026-05-11_21-42-08 step --merge-pr true",
254
+ "jskit session 2026-05-11_21-42-08 step --close-without-merge --close-reason \"Prototype kept for reference\"",
255
+ "jskit session 2026-05-11_21-42-08 step --user-check failed --rework-notes -",
238
256
  "jskit session 2026-05-11_21-42-08 diff --json"
239
257
  ])
240
258
  })
241
259
  ]),
242
260
  fullUse:
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]",
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]",
244
262
  showHelpOnBareInvocation: false,
245
263
  handlerName: "commandSession",
246
264
  allowedFlagKeys: Object.freeze(["json", "abandoned", "completed", "all"]),
@@ -288,6 +306,44 @@ const COMMAND_DESCRIPTORS = Object.freeze({
288
306
  return subcommand === "prompt" || subcommand === "set";
289
307
  }
290
308
  }),
309
+ "helper-map": Object.freeze({
310
+ command: "helper-map",
311
+ aliases: Object.freeze([]),
312
+ showInOverview: true,
313
+ summary: "Read or update the generated app helper map.",
314
+ minimalUse: "jskit helper-map update",
315
+ parameters: Object.freeze([
316
+ Object.freeze({
317
+ name: "[update]",
318
+ description: "Without a subcommand, prints the saved helper map. update refreshes .jskit/helper-map files."
319
+ })
320
+ ]),
321
+ defaults: Object.freeze([
322
+ "The helper map is generated app state, not a hand-maintained workflow file.",
323
+ "The JSON file lives at .jskit/helper-map.json and the readable map lives at .jskit/helper-map.md.",
324
+ "Use the map before adding helpers, composables, service functions, maps, or package glue.",
325
+ "Use --json for a stable machine-readable response."
326
+ ]),
327
+ examples: Object.freeze([
328
+ Object.freeze({
329
+ label: "Refresh helper map",
330
+ lines: Object.freeze([
331
+ "jskit helper-map update",
332
+ "jskit helper-map --json"
333
+ ])
334
+ })
335
+ ]),
336
+ fullUse: "jskit helper-map [update] [--json]",
337
+ showHelpOnBareInvocation: false,
338
+ handlerName: "commandHelperMap",
339
+ allowedFlagKeys: Object.freeze(["json"]),
340
+ inlineOptionMode: "delegate",
341
+ allowedValueOptionNames: Object.freeze([]),
342
+ canDelegateInlineOptions: (positional = []) => {
343
+ const subcommand = String(Array.isArray(positional) ? positional[0] || "" : "").trim();
344
+ return subcommand === "update";
345
+ }
346
+ }),
291
347
  add: Object.freeze({
292
348
  command: "add",
293
349
  aliases: Object.freeze([]),
@@ -8,6 +8,7 @@ import { createHealthCommands } from "../commandHandlers/health.js";
8
8
  import { createCompletionCommands } from "../commandHandlers/completion.js";
9
9
  import { createSessionCommands } from "../commandHandlers/session.js";
10
10
  import { createBlueprintCommands } from "../commandHandlers/blueprint.js";
11
+ import { createHelperMapCommands } from "../commandHandlers/helperMap.js";
11
12
 
12
13
  function createCommandHandlers(deps = {}) {
13
14
  const shared = createCommandHandlerShared(deps);
@@ -33,6 +34,7 @@ function createCommandHandlers(deps = {}) {
33
34
  const { commandCompletion } = createCompletionCommands(commandContext);
34
35
  const { commandSession } = createSessionCommands(commandContext);
35
36
  const { commandBlueprint } = createBlueprintCommands(commandContext);
37
+ const { commandHelperMap } = createHelperMapCommands(commandContext);
36
38
 
37
39
  return {
38
40
  commandList,
@@ -52,7 +54,8 @@ function createCommandHandlers(deps = {}) {
52
54
  commandDoctor,
53
55
  commandLintDescriptors,
54
56
  commandSession,
55
- commandBlueprint
57
+ commandBlueprint,
58
+ commandHelperMap
56
59
  };
57
60
  }
58
61