@jskit-ai/jskit-cli 0.2.81 → 0.2.83

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 (28) 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 +127 -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 +60 -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 +326 -87
  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/execute_plan.md +33 -7
  18. package/src/server/sessionRuntime/prompts/final_comment.md +3 -1
  19. package/src/server/sessionRuntime/prompts/issue_details.md +46 -0
  20. package/src/server/sessionRuntime/prompts/new_issue.md +15 -2
  21. package/src/server/sessionRuntime/prompts/plan_issue.md +40 -9
  22. package/src/server/sessionRuntime/prompts/resolve_deslop_findings.md +16 -0
  23. package/src/server/sessionRuntime/prompts/review_changes.md +46 -5
  24. package/src/server/sessionRuntime/prompts/update_blueprint.md +38 -0
  25. package/src/server/sessionRuntime/prompts/user_check.md +15 -1
  26. package/src/server/sessionRuntime/responses.js +860 -62
  27. package/src/server/sessionRuntime.js +1699 -140
  28. package/src/server/sessionRuntime/prompts/doctor_failure.md +0 -26
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.83",
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.82",
24
+ "@jskit-ai/kernel": "0.1.74",
25
+ "@jskit-ai/shell-web": "0.1.73",
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,125 @@ 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
+
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
+
195
298
  return {
299
+ agentDecisions: agentDecisions.value,
300
+ closeReason: closeReason.value,
301
+ codexResult: codexResult.value,
196
302
  issue: issue.value,
197
303
  issueTitle: issueTitle.value,
198
304
  ok: true,
199
- plan: plan.value
305
+ plan: plan.value,
306
+ issueDetails: issueDetails.value,
307
+ reworkNotes: reworkNotes.value,
308
+ skipReason: skipReason.value
200
309
  };
201
310
  }
202
311
 
203
312
  function normalizeStepOptions(inlineOptions = {}) {
204
- return {
313
+ const options = {
205
314
  ...inlineOptions,
315
+ closeWithoutMerge: inlineOptions["close-without-merge"] === "true" || inlineOptions.closeWithoutMerge === true,
316
+ mergePr: inlineOptions["merge-pr"] === "true" || inlineOptions.mergePr === true,
206
317
  prompt: inlineOptions.prompt,
318
+ reviewFindings: inlineOptions["review-findings"] || inlineOptions.reviewFindings,
319
+ skipUiCheck: inlineOptions["skip-ui-check"] === "true" || inlineOptions.skipUiCheck === true,
207
320
  userCheck: inlineOptions["user-check"] || inlineOptions.userCheck
208
321
  };
322
+ if (Object.hasOwn(inlineOptions, "review-findings-remaining") || Object.hasOwn(inlineOptions, "reviewFindingsRemaining")) {
323
+ options.reviewFindingsRemaining = inlineOptions["review-findings-remaining"] === "true" ||
324
+ inlineOptions.reviewFindingsRemaining === true;
325
+ }
326
+ return options;
209
327
  }
210
328
 
211
329
  function resolveListArchiveOption(options = {}) {
@@ -258,7 +376,13 @@ function createSessionCommands() {
258
376
  ...normalizeStepOptions(inlineOptions),
259
377
  issue: stepInputs.issue,
260
378
  issueTitle: stepInputs.issueTitle,
261
- plan: stepInputs.plan
379
+ plan: stepInputs.plan,
380
+ issueDetails: stepInputs.issueDetails,
381
+ reworkNotes: stepInputs.reworkNotes,
382
+ skipReason: stepInputs.skipReason,
383
+ agentDecisions: stepInputs.agentDecisions,
384
+ closeReason: stepInputs.closeReason,
385
+ codexResult: stepInputs.codexResult
262
386
  }
263
387
  });
264
388
  } 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,17 @@ 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 --codex-result - after Codex prompt steps to read the final marked Codex result from stdin.",
230
+ "Use --rework-notes - with --user-check failed to start the next plan cycle.",
231
+ "Use --agent-decisions - to append session-local decision log entries from implementation, UI review, verification, or repair phases.",
232
+ "Use --review-findings-remaining true --review-findings \"<findings>\" when an accepted review pass needs another pass.",
233
+ "Use --review-findings-remaining false only when the review/deslop loop is done.",
234
+ "Use --skip-ui-check --skip-reason \"<reason>\" only when uiImpact is possible and the Deep UI Check is intentionally skipped.",
235
+ "Use --merge-pr true at PR finalization to merge the pull request.",
236
+ "Use --close-without-merge --close-reason \"<reason>\" at PR finalization to complete the session without merging.",
237
+ "Run the blueprint step once to render its Codex prompt, then again after Codex updates .jskit/APP_BLUEPRINT.md."
228
238
  ]),
229
239
  examples: Object.freeze([
230
240
  Object.freeze({
@@ -234,13 +244,23 @@ const COMMAND_DESCRIPTORS = Object.freeze({
234
244
  "jskit session 2026-05-11_21-42-08 step",
235
245
  "jskit session 2026-05-11_21-42-08 step --prompt \"Fix the customer filters\"",
236
246
  "jskit session 2026-05-11_21-42-08 step --issue-title \"Fix customer filters\" --issue -",
247
+ "jskit session 2026-05-11_21-42-08 step --issue-details -",
237
248
  "jskit session 2026-05-11_21-42-08 step --plan -",
249
+ "jskit session 2026-05-11_21-42-08 step --codex-result -",
250
+ "jskit session 2026-05-11_21-42-08 step --agent-decisions -",
251
+ "jskit session 2026-05-11_21-42-08 step --review-findings-remaining true --review-findings \"A helper duplication fix needs another pass\"",
252
+ "jskit session 2026-05-11_21-42-08 step --review-findings-remaining false",
253
+ "jskit session 2026-05-11_21-42-08 step --skip-ui-check --skip-reason \"No user-facing UI changes\"",
254
+ "jskit session 2026-05-11_21-42-08 step",
255
+ "jskit session 2026-05-11_21-42-08 step --merge-pr true",
256
+ "jskit session 2026-05-11_21-42-08 step --close-without-merge --close-reason \"Prototype kept for reference\"",
257
+ "jskit session 2026-05-11_21-42-08 step --user-check failed --rework-notes -",
238
258
  "jskit session 2026-05-11_21-42-08 diff --json"
239
259
  ])
240
260
  })
241
261
  ]),
242
262
  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]",
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]",
244
264
  showHelpOnBareInvocation: false,
245
265
  handlerName: "commandSession",
246
266
  allowedFlagKeys: Object.freeze(["json", "abandoned", "completed", "all"]),
@@ -288,6 +308,44 @@ const COMMAND_DESCRIPTORS = Object.freeze({
288
308
  return subcommand === "prompt" || subcommand === "set";
289
309
  }
290
310
  }),
311
+ "helper-map": Object.freeze({
312
+ command: "helper-map",
313
+ aliases: Object.freeze([]),
314
+ showInOverview: true,
315
+ summary: "Read or update the generated app helper map.",
316
+ minimalUse: "jskit helper-map update",
317
+ parameters: Object.freeze([
318
+ Object.freeze({
319
+ name: "[update]",
320
+ description: "Without a subcommand, prints the saved helper map. update refreshes .jskit/helper-map files."
321
+ })
322
+ ]),
323
+ defaults: Object.freeze([
324
+ "The helper map is generated app state, not a hand-maintained workflow file.",
325
+ "The JSON file lives at .jskit/helper-map.json and the readable map lives at .jskit/helper-map.md.",
326
+ "Use the map before adding helpers, composables, service functions, maps, or package glue.",
327
+ "Use --json for a stable machine-readable response."
328
+ ]),
329
+ examples: Object.freeze([
330
+ Object.freeze({
331
+ label: "Refresh helper map",
332
+ lines: Object.freeze([
333
+ "jskit helper-map update",
334
+ "jskit helper-map --json"
335
+ ])
336
+ })
337
+ ]),
338
+ fullUse: "jskit helper-map [update] [--json]",
339
+ showHelpOnBareInvocation: false,
340
+ handlerName: "commandHelperMap",
341
+ allowedFlagKeys: Object.freeze(["json"]),
342
+ inlineOptionMode: "delegate",
343
+ allowedValueOptionNames: Object.freeze([]),
344
+ canDelegateInlineOptions: (positional = []) => {
345
+ const subcommand = String(Array.isArray(positional) ? positional[0] || "" : "").trim();
346
+ return subcommand === "update";
347
+ }
348
+ }),
291
349
  add: Object.freeze({
292
350
  command: "add",
293
351
  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