@jskit-ai/jskit-cli 0.2.80 → 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.
- package/package.json +6 -4
- package/src/server/appBlueprint.js +1 -1
- package/src/server/commandHandlers/helperMap.js +104 -0
- package/src/server/commandHandlers/session.js +179 -4
- package/src/server/commandHandlers/show.js +169 -34
- package/src/server/core/argParser.js +20 -0
- package/src/server/core/commandCatalog.js +70 -7
- package/src/server/core/createCommandHandlers.js +4 -1
- package/src/server/helperMap.js +463 -0
- package/src/server/helperMapPaths.js +7 -0
- package/src/server/sessionRuntime/appReadiness.js +55 -0
- package/src/server/sessionRuntime/constants.js +298 -135
- package/src/server/sessionRuntime/paths.js +2 -4
- package/src/server/sessionRuntime/preconditions.js +393 -26
- package/src/server/sessionRuntime/promptRenderer.js +15 -2
- package/src/server/sessionRuntime/prompts/app_blueprint.md +26 -2
- package/src/server/sessionRuntime/prompts/automated_checks.md +42 -0
- package/src/server/sessionRuntime/prompts/deep_ui_check.md +53 -0
- package/src/server/sessionRuntime/prompts/doctor_failure.md +21 -1
- package/src/server/sessionRuntime/prompts/execute_plan.md +61 -0
- package/src/server/sessionRuntime/prompts/final_comment.md +3 -1
- package/src/server/sessionRuntime/prompts/issue_details.md +52 -0
- package/src/server/sessionRuntime/prompts/new_issue.md +34 -3
- package/src/server/sessionRuntime/prompts/plan_issue.md +81 -0
- package/src/server/sessionRuntime/prompts/pr_failure.md +14 -1
- package/src/server/sessionRuntime/prompts/review_changes.md +77 -15
- package/src/server/sessionRuntime/prompts/update_blueprint.md +36 -0
- package/src/server/sessionRuntime/prompts/user_check.md +22 -4
- package/src/server/sessionRuntime/responses.js +877 -30
- package/src/server/sessionRuntime/worktrees.js +31 -0
- package/src/server/sessionRuntime.js +2070 -244
- package/src/server/sessionRuntime/prompts/implement_issue.md +0 -25
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import {
|
|
2
2
|
access,
|
|
3
3
|
appendFile,
|
|
4
|
-
mkdir
|
|
5
|
-
stat
|
|
4
|
+
mkdir
|
|
6
5
|
} from "node:fs/promises";
|
|
7
6
|
import { constants as fsConstants } from "node:fs";
|
|
8
7
|
import path from "node:path";
|
|
9
8
|
import {
|
|
9
|
+
JSKIT_CLI_SHELL_COMMAND,
|
|
10
10
|
SESSION_STATE_RELATIVE_PATH
|
|
11
11
|
} from "./constants.js";
|
|
12
12
|
import {
|
|
@@ -22,6 +22,16 @@ import {
|
|
|
22
22
|
import {
|
|
23
23
|
resolveExistingSessionRoot
|
|
24
24
|
} from "./paths.js";
|
|
25
|
+
import {
|
|
26
|
+
hasWorktree
|
|
27
|
+
} from "./worktrees.js";
|
|
28
|
+
import {
|
|
29
|
+
inspectReadyJskitAppRoot
|
|
30
|
+
} from "./appReadiness.js";
|
|
31
|
+
|
|
32
|
+
function jskitCommand(args = "") {
|
|
33
|
+
return `${JSKIT_CLI_SHELL_COMMAND}${args ? ` ${args}` : ""}`;
|
|
34
|
+
}
|
|
25
35
|
|
|
26
36
|
async function assertTargetRootWritable(targetRoot) {
|
|
27
37
|
try {
|
|
@@ -226,7 +236,7 @@ async function assertSessionExists(paths) {
|
|
|
226
236
|
error: createError({
|
|
227
237
|
code: "session_missing",
|
|
228
238
|
message: `Session does not exist: ${paths.sessionId}`,
|
|
229
|
-
repairCommand: "
|
|
239
|
+
repairCommand: jskitCommand("session create")
|
|
230
240
|
}),
|
|
231
241
|
precondition: createPrecondition({
|
|
232
242
|
id: "session_exists",
|
|
@@ -236,32 +246,251 @@ async function assertSessionExists(paths) {
|
|
|
236
246
|
};
|
|
237
247
|
}
|
|
238
248
|
|
|
239
|
-
async function
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
249
|
+
async function pathExists(filePath) {
|
|
250
|
+
try {
|
|
251
|
+
await access(filePath, fsConstants.F_OK);
|
|
252
|
+
return true;
|
|
253
|
+
} catch {
|
|
254
|
+
return false;
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
async function assertDependenciesInstalled(paths) {
|
|
259
|
+
if (await pathExists(path.join(paths.sessionRoot, "steps", "dependencies_installed"))) {
|
|
260
|
+
return {
|
|
261
|
+
ok: true,
|
|
262
|
+
precondition: createPrecondition({
|
|
263
|
+
id: "dependencies_installed",
|
|
264
|
+
ok: true,
|
|
265
|
+
message: "Session worktree dependencies have been installed."
|
|
266
|
+
})
|
|
267
|
+
};
|
|
268
|
+
}
|
|
269
|
+
return {
|
|
270
|
+
ok: false,
|
|
271
|
+
error: createError({
|
|
272
|
+
code: "dependencies_not_installed",
|
|
273
|
+
message: "Cannot start issue work until dependencies are installed in the session worktree.",
|
|
274
|
+
repairCommand: jskitCommand(`session ${paths.sessionId} step`)
|
|
275
|
+
}),
|
|
276
|
+
precondition: createPrecondition({
|
|
277
|
+
id: "dependencies_installed",
|
|
278
|
+
ok: false,
|
|
279
|
+
message: "Session worktree dependencies have been installed."
|
|
280
|
+
})
|
|
281
|
+
};
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
async function assertReadyJskitApp(paths) {
|
|
285
|
+
const root = paths.worktree || paths.targetRoot;
|
|
286
|
+
const readiness = await inspectReadyJskitAppRoot(root);
|
|
287
|
+
if (readiness.ok) {
|
|
288
|
+
return {
|
|
289
|
+
ok: true,
|
|
290
|
+
precondition: createPrecondition({
|
|
291
|
+
id: "ready_jskit_app",
|
|
292
|
+
ok: true,
|
|
293
|
+
message: "Session worktree has the required JSKIT app markers."
|
|
294
|
+
})
|
|
295
|
+
};
|
|
296
|
+
}
|
|
297
|
+
return {
|
|
298
|
+
ok: false,
|
|
299
|
+
error: createError({
|
|
300
|
+
code: "app_setup_required",
|
|
301
|
+
message: `Issue sessions require a ready JSKIT app. Missing: ${readiness.missing.join(", ")}.`,
|
|
302
|
+
repairCommand: "Run the app setup flow before starting issue work."
|
|
303
|
+
}),
|
|
304
|
+
precondition: createPrecondition({
|
|
305
|
+
id: "ready_jskit_app",
|
|
306
|
+
ok: false,
|
|
307
|
+
message: "Session worktree has the required JSKIT app markers."
|
|
308
|
+
})
|
|
309
|
+
};
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
async function assertActiveCycleExists(paths) {
|
|
313
|
+
const activeCycle = await readTrimmedFile(path.join(paths.sessionRoot, "active_cycle"));
|
|
314
|
+
if (/^\d+$/u.test(activeCycle)) {
|
|
315
|
+
return {
|
|
316
|
+
ok: true,
|
|
317
|
+
precondition: createPrecondition({
|
|
318
|
+
id: "active_cycle_exists",
|
|
319
|
+
ok: true,
|
|
320
|
+
message: "Active cycle marker exists."
|
|
321
|
+
})
|
|
322
|
+
};
|
|
323
|
+
}
|
|
324
|
+
return {
|
|
325
|
+
ok: false,
|
|
326
|
+
error: createError({
|
|
327
|
+
code: "active_cycle_missing",
|
|
328
|
+
message: "Session active_cycle is missing or invalid."
|
|
329
|
+
}),
|
|
330
|
+
precondition: createPrecondition({
|
|
331
|
+
id: "active_cycle_exists",
|
|
332
|
+
ok: false,
|
|
333
|
+
message: "Active cycle marker exists."
|
|
334
|
+
})
|
|
335
|
+
};
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
async function assertActiveCycleUserCheckPassed(paths) {
|
|
339
|
+
const activeCycle = await readTrimmedFile(path.join(paths.sessionRoot, "active_cycle"));
|
|
340
|
+
const receiptPath = path.join(paths.sessionRoot, "steps", `cycle_${activeCycle}`, "user_check_completed");
|
|
341
|
+
if (await pathExists(receiptPath)) {
|
|
342
|
+
return {
|
|
343
|
+
ok: true,
|
|
344
|
+
precondition: createPrecondition({
|
|
345
|
+
id: "active_cycle_user_check_passed",
|
|
346
|
+
ok: true,
|
|
347
|
+
message: "The active cycle user check has passed."
|
|
348
|
+
})
|
|
349
|
+
};
|
|
350
|
+
}
|
|
351
|
+
return {
|
|
352
|
+
ok: false,
|
|
353
|
+
error: createError({
|
|
354
|
+
code: "user_check_not_passed",
|
|
355
|
+
message: "Finalization cannot continue until the active cycle user check has passed.",
|
|
356
|
+
repairCommand: jskitCommand(`session ${paths.sessionId} step --user-check passed`)
|
|
357
|
+
}),
|
|
358
|
+
precondition: createPrecondition({
|
|
359
|
+
id: "active_cycle_user_check_passed",
|
|
360
|
+
ok: false,
|
|
361
|
+
message: "The active cycle user check has passed."
|
|
362
|
+
})
|
|
363
|
+
};
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
async function assertAcceptedChangesCommitted(paths) {
|
|
367
|
+
const receiptPath = path.join(paths.sessionRoot, "steps", "changes_committed");
|
|
368
|
+
if (await pathExists(receiptPath)) {
|
|
245
369
|
return {
|
|
246
370
|
ok: true,
|
|
247
371
|
precondition: createPrecondition({
|
|
248
|
-
id: "
|
|
372
|
+
id: "accepted_changes_committed",
|
|
249
373
|
ok: true,
|
|
250
|
-
message: "
|
|
374
|
+
message: "Accepted changes have been committed."
|
|
251
375
|
})
|
|
252
376
|
};
|
|
253
377
|
}
|
|
254
378
|
return {
|
|
255
379
|
ok: false,
|
|
256
380
|
error: createError({
|
|
257
|
-
code: "
|
|
258
|
-
message: "
|
|
259
|
-
repairCommand: `
|
|
381
|
+
code: "accepted_changes_not_committed",
|
|
382
|
+
message: "Accepted changes must be committed before app memory and finalization steps continue.",
|
|
383
|
+
repairCommand: jskitCommand(`session ${paths.sessionId} step`)
|
|
260
384
|
}),
|
|
261
385
|
precondition: createPrecondition({
|
|
262
|
-
id: "
|
|
386
|
+
id: "accepted_changes_committed",
|
|
263
387
|
ok: false,
|
|
264
|
-
message: "
|
|
388
|
+
message: "Accepted changes have been committed."
|
|
389
|
+
})
|
|
390
|
+
};
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
async function assertActiveCycleStepReceipt(paths, {
|
|
394
|
+
code,
|
|
395
|
+
id,
|
|
396
|
+
message,
|
|
397
|
+
stepId
|
|
398
|
+
}) {
|
|
399
|
+
const activeCycle = await readTrimmedFile(path.join(paths.sessionRoot, "active_cycle"));
|
|
400
|
+
const receiptPath = path.join(paths.sessionRoot, "steps", `cycle_${activeCycle}`, stepId);
|
|
401
|
+
if (await pathExists(receiptPath)) {
|
|
402
|
+
return {
|
|
403
|
+
ok: true,
|
|
404
|
+
precondition: createPrecondition({
|
|
405
|
+
id,
|
|
406
|
+
ok: true,
|
|
407
|
+
message
|
|
408
|
+
})
|
|
409
|
+
};
|
|
410
|
+
}
|
|
411
|
+
return {
|
|
412
|
+
ok: false,
|
|
413
|
+
error: createError({
|
|
414
|
+
code,
|
|
415
|
+
message,
|
|
416
|
+
repairCommand: jskitCommand(`session ${paths.sessionId} step`)
|
|
417
|
+
}),
|
|
418
|
+
precondition: createPrecondition({
|
|
419
|
+
id,
|
|
420
|
+
ok: false,
|
|
421
|
+
message
|
|
422
|
+
})
|
|
423
|
+
};
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
async function assertAutomatedChecksPassed(paths) {
|
|
427
|
+
return assertActiveCycleStepReceipt(paths, {
|
|
428
|
+
code: "automated_checks_not_passed",
|
|
429
|
+
id: "automated_checks_passed",
|
|
430
|
+
message: "Automated checks have passed.",
|
|
431
|
+
stepId: "automated_checks_run"
|
|
432
|
+
});
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
async function assertDeepUiCheckSatisfied(paths) {
|
|
436
|
+
return assertActiveCycleStepReceipt(paths, {
|
|
437
|
+
code: "deep_ui_check_not_satisfied",
|
|
438
|
+
id: "deep_ui_check_satisfied",
|
|
439
|
+
message: "Deep UI check is satisfied.",
|
|
440
|
+
stepId: "deep_ui_check_run"
|
|
441
|
+
});
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
async function assertIssueMetadataExists(paths) {
|
|
445
|
+
const source = await readTextIfExists(path.join(paths.sessionRoot, "issue_metadata.json"));
|
|
446
|
+
if (!source) {
|
|
447
|
+
return {
|
|
448
|
+
ok: false,
|
|
449
|
+
error: createError({
|
|
450
|
+
code: "issue_metadata_missing",
|
|
451
|
+
message: "Cannot continue before issue_metadata.json records the issue category and UI impact.",
|
|
452
|
+
repairCommand: jskitCommand(`session ${paths.sessionId} step --issue-details -`)
|
|
453
|
+
}),
|
|
454
|
+
precondition: createPrecondition({
|
|
455
|
+
id: "issue_metadata_exists",
|
|
456
|
+
ok: false,
|
|
457
|
+
message: "Issue metadata records issue category and UI impact."
|
|
458
|
+
})
|
|
459
|
+
};
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
let metadata = null;
|
|
463
|
+
try {
|
|
464
|
+
metadata = JSON.parse(source);
|
|
465
|
+
} catch {
|
|
466
|
+
metadata = null;
|
|
467
|
+
}
|
|
468
|
+
const issueCategory = String(metadata?.issueCategory || "").trim().toLowerCase();
|
|
469
|
+
const uiImpact = String(metadata?.uiImpact || "").trim().toLowerCase();
|
|
470
|
+
const validIssueCategories = new Set(["client", "server", "client_server", "tooling", "unknown"]);
|
|
471
|
+
const validUiImpacts = new Set(["none", "possible", "definite", "unknown"]);
|
|
472
|
+
if (validIssueCategories.has(issueCategory) && validUiImpacts.has(uiImpact)) {
|
|
473
|
+
return {
|
|
474
|
+
ok: true,
|
|
475
|
+
precondition: createPrecondition({
|
|
476
|
+
id: "issue_metadata_exists",
|
|
477
|
+
ok: true,
|
|
478
|
+
message: "Issue metadata records issue category and UI impact."
|
|
479
|
+
})
|
|
480
|
+
};
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
return {
|
|
484
|
+
ok: false,
|
|
485
|
+
error: createError({
|
|
486
|
+
code: "issue_metadata_invalid",
|
|
487
|
+
message: "issue_metadata.json must include a valid issueCategory and uiImpact.",
|
|
488
|
+
repairCommand: jskitCommand(`session ${paths.sessionId} step --issue-details -`)
|
|
489
|
+
}),
|
|
490
|
+
precondition: createPrecondition({
|
|
491
|
+
id: "issue_metadata_exists",
|
|
492
|
+
ok: false,
|
|
493
|
+
message: "Issue metadata records issue category and UI impact."
|
|
265
494
|
})
|
|
266
495
|
};
|
|
267
496
|
}
|
|
@@ -283,7 +512,7 @@ async function assertIssueTextExists(paths) {
|
|
|
283
512
|
error: createError({
|
|
284
513
|
code: "issue_text_missing",
|
|
285
514
|
message: "Cannot create a GitHub issue before issue.md exists.",
|
|
286
|
-
repairCommand: `
|
|
515
|
+
repairCommand: jskitCommand(`session ${paths.sessionId} step --issue -`)
|
|
287
516
|
}),
|
|
288
517
|
precondition: createPrecondition({
|
|
289
518
|
id: "issue_text_exists",
|
|
@@ -293,13 +522,139 @@ async function assertIssueTextExists(paths) {
|
|
|
293
522
|
};
|
|
294
523
|
}
|
|
295
524
|
|
|
296
|
-
async function
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
return
|
|
300
|
-
|
|
301
|
-
|
|
525
|
+
async function assertIssueUrlExists(paths) {
|
|
526
|
+
const issueUrl = await readTrimmedFile(path.join(paths.sessionRoot, "issue_url"));
|
|
527
|
+
if (issueUrl) {
|
|
528
|
+
return {
|
|
529
|
+
ok: true,
|
|
530
|
+
precondition: createPrecondition({
|
|
531
|
+
id: "issue_url_exists",
|
|
532
|
+
ok: true,
|
|
533
|
+
message: "GitHub issue URL exists."
|
|
534
|
+
})
|
|
535
|
+
};
|
|
302
536
|
}
|
|
537
|
+
return {
|
|
538
|
+
ok: false,
|
|
539
|
+
error: createError({
|
|
540
|
+
code: "issue_url_missing",
|
|
541
|
+
message: "Cannot create a plan before the GitHub issue exists.",
|
|
542
|
+
repairCommand: jskitCommand(`session ${paths.sessionId} step`)
|
|
543
|
+
}),
|
|
544
|
+
precondition: createPrecondition({
|
|
545
|
+
id: "issue_url_exists",
|
|
546
|
+
ok: false,
|
|
547
|
+
message: "GitHub issue URL exists."
|
|
548
|
+
})
|
|
549
|
+
};
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
async function assertPlanTextExists(paths) {
|
|
553
|
+
const activeCycle = await readTrimmedFile(path.join(paths.sessionRoot, "active_cycle"));
|
|
554
|
+
const planPath = path.join(paths.sessionRoot, "cycles", `cycle_${activeCycle || "001"}`, "plan.md");
|
|
555
|
+
const planText = await readTrimmedFile(planPath);
|
|
556
|
+
if (planText) {
|
|
557
|
+
return {
|
|
558
|
+
ok: true,
|
|
559
|
+
precondition: createPrecondition({
|
|
560
|
+
id: "plan_text_exists",
|
|
561
|
+
ok: true,
|
|
562
|
+
message: "Plan text exists."
|
|
563
|
+
})
|
|
564
|
+
};
|
|
565
|
+
}
|
|
566
|
+
return {
|
|
567
|
+
ok: false,
|
|
568
|
+
error: createError({
|
|
569
|
+
code: "plan_text_missing",
|
|
570
|
+
message: "Cannot execute a plan before the active cycle plan exists.",
|
|
571
|
+
repairCommand: jskitCommand(`session ${paths.sessionId} step --plan -`)
|
|
572
|
+
}),
|
|
573
|
+
precondition: createPrecondition({
|
|
574
|
+
id: "plan_text_exists",
|
|
575
|
+
ok: false,
|
|
576
|
+
message: "Plan text exists."
|
|
577
|
+
})
|
|
578
|
+
};
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
async function assertIssueDetailsExists(paths) {
|
|
582
|
+
const issueDetails = await readTrimmedFile(path.join(paths.sessionRoot, "issue_details.md"));
|
|
583
|
+
if (issueDetails) {
|
|
584
|
+
return {
|
|
585
|
+
ok: true,
|
|
586
|
+
precondition: createPrecondition({
|
|
587
|
+
id: "issue_details_exists",
|
|
588
|
+
ok: true,
|
|
589
|
+
message: "Issue details exist."
|
|
590
|
+
})
|
|
591
|
+
};
|
|
592
|
+
}
|
|
593
|
+
return {
|
|
594
|
+
ok: false,
|
|
595
|
+
error: createError({
|
|
596
|
+
code: "issue_details_missing",
|
|
597
|
+
message: "Cannot create a plan before issue_details.md exists.",
|
|
598
|
+
repairCommand: jskitCommand(`session ${paths.sessionId} step --issue-details -`)
|
|
599
|
+
}),
|
|
600
|
+
precondition: createPrecondition({
|
|
601
|
+
id: "issue_details_exists",
|
|
602
|
+
ok: false,
|
|
603
|
+
message: "Issue details exist."
|
|
604
|
+
})
|
|
605
|
+
};
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
async function assertBlueprintUpdateSatisfied(paths) {
|
|
609
|
+
if (await pathExists(path.join(paths.sessionRoot, "steps", "blueprint_updated"))) {
|
|
610
|
+
return {
|
|
611
|
+
ok: true,
|
|
612
|
+
precondition: createPrecondition({
|
|
613
|
+
id: "blueprint_update_satisfied",
|
|
614
|
+
ok: true,
|
|
615
|
+
message: "Blueprint update step is complete."
|
|
616
|
+
})
|
|
617
|
+
};
|
|
618
|
+
}
|
|
619
|
+
return {
|
|
620
|
+
ok: false,
|
|
621
|
+
error: createError({
|
|
622
|
+
code: "blueprint_update_missing",
|
|
623
|
+
message: "Cannot continue before the blueprint update step is complete.",
|
|
624
|
+
repairCommand: jskitCommand(`session ${paths.sessionId} step`)
|
|
625
|
+
}),
|
|
626
|
+
precondition: createPrecondition({
|
|
627
|
+
id: "blueprint_update_satisfied",
|
|
628
|
+
ok: false,
|
|
629
|
+
message: "Blueprint update step is complete."
|
|
630
|
+
})
|
|
631
|
+
};
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
async function assertFinalReportExists(paths) {
|
|
635
|
+
if (await pathExists(path.join(paths.sessionRoot, "final_report.md"))) {
|
|
636
|
+
return {
|
|
637
|
+
ok: true,
|
|
638
|
+
precondition: createPrecondition({
|
|
639
|
+
id: "final_report_exists",
|
|
640
|
+
ok: true,
|
|
641
|
+
message: "Final report exists."
|
|
642
|
+
})
|
|
643
|
+
};
|
|
644
|
+
}
|
|
645
|
+
return {
|
|
646
|
+
ok: false,
|
|
647
|
+
error: createError({
|
|
648
|
+
code: "final_report_missing",
|
|
649
|
+
message: "Cannot publish the PR before final_report.md exists.",
|
|
650
|
+
repairCommand: jskitCommand(`session ${paths.sessionId} step`)
|
|
651
|
+
}),
|
|
652
|
+
precondition: createPrecondition({
|
|
653
|
+
id: "final_report_exists",
|
|
654
|
+
ok: false,
|
|
655
|
+
message: "Final report exists."
|
|
656
|
+
})
|
|
657
|
+
};
|
|
303
658
|
}
|
|
304
659
|
|
|
305
660
|
async function assertWorktreeExists(paths) {
|
|
@@ -318,7 +673,7 @@ async function assertWorktreeExists(paths) {
|
|
|
318
673
|
error: createError({
|
|
319
674
|
code: "worktree_missing",
|
|
320
675
|
message: "Session worktree does not exist.",
|
|
321
|
-
repairCommand: `
|
|
676
|
+
repairCommand: jskitCommand(`session ${paths.sessionId} step`)
|
|
322
677
|
}),
|
|
323
678
|
precondition: createPrecondition({
|
|
324
679
|
id: "worktree_exists",
|
|
@@ -345,7 +700,7 @@ async function assertPrUrlExists(paths) {
|
|
|
345
700
|
error: createError({
|
|
346
701
|
code: "pr_url_missing",
|
|
347
702
|
message: "Cannot merge before pr_url exists.",
|
|
348
|
-
repairCommand: `
|
|
703
|
+
repairCommand: jskitCommand(`session ${paths.sessionId} step`)
|
|
349
704
|
}),
|
|
350
705
|
precondition: createPrecondition({
|
|
351
706
|
id: "pr_url_exists",
|
|
@@ -357,13 +712,25 @@ async function assertPrUrlExists(paths) {
|
|
|
357
712
|
|
|
358
713
|
export {
|
|
359
714
|
applyPreconditions,
|
|
715
|
+
assertAcceptedChangesCommitted,
|
|
716
|
+
assertActiveCycleExists,
|
|
717
|
+
assertActiveCycleUserCheckPassed,
|
|
718
|
+
assertBlueprintUpdateSatisfied,
|
|
719
|
+
assertDeepUiCheckSatisfied,
|
|
720
|
+
assertDependenciesInstalled,
|
|
721
|
+
assertFinalReportExists,
|
|
360
722
|
assertGhAuth,
|
|
361
723
|
assertGitCurrentBranch,
|
|
362
724
|
assertGitRepository,
|
|
363
725
|
assertGithubOrigin,
|
|
364
|
-
|
|
726
|
+
assertIssueMetadataExists,
|
|
365
727
|
assertIssueTextExists,
|
|
728
|
+
assertIssueUrlExists,
|
|
729
|
+
assertAutomatedChecksPassed,
|
|
730
|
+
assertIssueDetailsExists,
|
|
731
|
+
assertPlanTextExists,
|
|
366
732
|
assertPrUrlExists,
|
|
733
|
+
assertReadyJskitApp,
|
|
367
734
|
assertSessionExists,
|
|
368
735
|
assertTargetRootWritable,
|
|
369
736
|
assertWorktreeExists,
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import path from "node:path";
|
|
2
2
|
import {
|
|
3
|
+
JSKIT_CLI_SHELL_RULE,
|
|
3
4
|
PROMPT_DIRECTORY,
|
|
4
5
|
SESSION_STATE_RELATIVE_PATH
|
|
5
6
|
} from "./constants.js";
|
|
@@ -24,9 +25,20 @@ function renderTemplate(source, values = {}) {
|
|
|
24
25
|
});
|
|
25
26
|
}
|
|
26
27
|
|
|
28
|
+
function withShellCommandRule(template) {
|
|
29
|
+
const body = String(template || "").trim();
|
|
30
|
+
if (!body) {
|
|
31
|
+
return JSKIT_CLI_SHELL_RULE;
|
|
32
|
+
}
|
|
33
|
+
if (body.includes("When running JSKIT CLI commands from the shell")) {
|
|
34
|
+
return body;
|
|
35
|
+
}
|
|
36
|
+
return `${JSKIT_CLI_SHELL_RULE}\n\n---\n\n${body}`;
|
|
37
|
+
}
|
|
38
|
+
|
|
27
39
|
async function renderPrompt(paths, templateName, values = {}) {
|
|
28
40
|
const template = await readPromptTemplate(paths.targetRoot, templateName);
|
|
29
|
-
return renderTemplate(template, {
|
|
41
|
+
return renderTemplate(withShellCommandRule(template), {
|
|
30
42
|
branch: paths.branch,
|
|
31
43
|
session_id: paths.sessionId,
|
|
32
44
|
worktree: paths.worktree,
|
|
@@ -37,5 +49,6 @@ async function renderPrompt(paths, templateName, values = {}) {
|
|
|
37
49
|
export {
|
|
38
50
|
readPromptTemplate,
|
|
39
51
|
renderPrompt,
|
|
40
|
-
renderTemplate
|
|
52
|
+
renderTemplate,
|
|
53
|
+
withShellCommandRule
|
|
41
54
|
};
|
|
@@ -4,21 +4,45 @@ App brief:
|
|
|
4
4
|
|
|
5
5
|
{{app_brief}}
|
|
6
6
|
|
|
7
|
-
Produce a concise but useful app-level blueprint. It must describe product intent, not implementation
|
|
7
|
+
Produce a concise but useful app-level blueprint. It must describe product intent, platform choices, and architectural boundaries. It must not become an implementation workboard for one issue.
|
|
8
|
+
|
|
9
|
+
Before writing the blueprint, classify the app state if local files are available:
|
|
10
|
+
|
|
11
|
+
- empty
|
|
12
|
+
- non_jskit_repo
|
|
13
|
+
- partial_jskit_app
|
|
14
|
+
- jskit_app
|
|
15
|
+
|
|
16
|
+
Use these markers for a real JSKIT app when they exist:
|
|
17
|
+
|
|
18
|
+
- package.json
|
|
19
|
+
- config/public.js
|
|
20
|
+
- src/main.js
|
|
21
|
+
- packages/main/package.descriptor.mjs
|
|
22
|
+
- .jskit/lock.json
|
|
23
|
+
|
|
24
|
+
If the app is empty or only a fresh minimal scaffold, keep platform choices explicit and provisional until decided. Do not treat a missing `config.tenancyMode` line or untouched minimal scaffold as a final tenancy decision.
|
|
8
25
|
|
|
9
26
|
Cover:
|
|
10
27
|
|
|
11
28
|
- App purpose and what the app will do in general.
|
|
12
29
|
- Primary users and actors.
|
|
13
30
|
- Type of multihoming or tenancy: none, personal, workspaces, or another explicit model from the brief.
|
|
31
|
+
- Database engine, if the app needs persistence.
|
|
32
|
+
- Auth provider, if the app needs auth.
|
|
14
33
|
- The role of each surface: app, admin, console, settings, public, workspace, or any app-specific surface from the brief.
|
|
15
34
|
- Global view of the main product areas and navigation destinations.
|
|
16
35
|
- Key domain concepts and data objects, without inventing database schemas unless the brief clearly requires them.
|
|
36
|
+
- Ownership model per persistent entity when it is already clear: public, user, workspace, or workspace_user.
|
|
37
|
+
- Baseline JSKIT package workflows to accept as defaults and any intended overrides.
|
|
38
|
+
- Package install, generator, and custom-code areas at a high level.
|
|
39
|
+
- CRUDs likely to need server ownership, and any narrow exceptions that should be called out later.
|
|
17
40
|
- Important non-goals and constraints.
|
|
18
41
|
- First useful screen and what it should show.
|
|
19
42
|
- Settings, admin, and operator expectations when relevant.
|
|
43
|
+
- Verification expectations, including UI checks for user-facing screens.
|
|
20
44
|
|
|
21
|
-
If the brief is ambiguous, state the assumption in the blueprint instead of asking questions.
|
|
45
|
+
If the brief is ambiguous, state the assumption in the blueprint instead of asking questions. Do not invent detailed feature behavior that the brief does not support.
|
|
22
46
|
|
|
23
47
|
When the blueprint is ready, output only the final markdown surrounded by these exact markers:
|
|
24
48
|
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
Run automated checks for JSKIT session {{session_id}}.
|
|
2
|
+
|
|
3
|
+
Worktree:
|
|
4
|
+
|
|
5
|
+
{{worktree}}
|
|
6
|
+
|
|
7
|
+
Command:
|
|
8
|
+
|
|
9
|
+
{{check_command}}
|
|
10
|
+
|
|
11
|
+
Run the command in the session worktree. If it fails, diagnose the root cause, fix the worktree, and rerun the command. Keep repeating until the command passes or until a real blocker prevents progress.
|
|
12
|
+
|
|
13
|
+
Rules:
|
|
14
|
+
|
|
15
|
+
- Fix the underlying cause. Do not remove checks, weaken verification, or hide failures to make the command pass.
|
|
16
|
+
- Prefer JSKIT-owned helpers, generators, package seams, and documented commands over local hacks.
|
|
17
|
+
- Use `npx --no-install jskit ...` for JSKIT CLI commands you run directly from the shell.
|
|
18
|
+
- Keep fixes scoped to the current issue, issue details, and active plan.
|
|
19
|
+
- Do not create commits, branches, issues, pull requests, merges, or worktree cleanup. JSKIT session owns those steps.
|
|
20
|
+
|
|
21
|
+
When finished, report:
|
|
22
|
+
|
|
23
|
+
- root cause
|
|
24
|
+
- files changed
|
|
25
|
+
- final check command and result
|
|
26
|
+
- anything still unverified
|
|
27
|
+
|
|
28
|
+
If the repair records a meaningful verification decision, tradeoff, missing coverage, or root-cause explanation future steps should know, include concise entries with reasons in this exact marker block:
|
|
29
|
+
|
|
30
|
+
```text
|
|
31
|
+
[agent_decisions]
|
|
32
|
+
<verification or repair decisions, or "No new decisions.">
|
|
33
|
+
[/agent_decisions]
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
At the very end, include this completion block so Studio knows the step is complete:
|
|
37
|
+
|
|
38
|
+
[jskit_step_result]
|
|
39
|
+
status: complete
|
|
40
|
+
step: automated_checks_run
|
|
41
|
+
summary: Short summary of the final check command, result, and any repairs.
|
|
42
|
+
[/jskit_step_result]
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
Deep UI quality check for JSKIT session {{session_id}}.
|
|
2
|
+
|
|
3
|
+
Phase: {{phase}}
|
|
4
|
+
GitHub issue: {{issue_url}}
|
|
5
|
+
Issue number: {{issue_number}}
|
|
6
|
+
Issue title: {{issue_title}}
|
|
7
|
+
Issue body file: {{issue_file}}
|
|
8
|
+
Issue details file (`issue_details.md`): {{issue_details_file}}
|
|
9
|
+
Plan file: {{plan_file}}
|
|
10
|
+
UI impact: {{ui_impact}}
|
|
11
|
+
Worktree: {{worktree}}
|
|
12
|
+
|
|
13
|
+
Changed files since the session base:
|
|
14
|
+
|
|
15
|
+
{{changed_files}}
|
|
16
|
+
|
|
17
|
+
Run a focused UI quality pass for the current worktree. If this is not UI-impacting after inspection, say exactly why and do not edit files. If the issue touches UI, inspect the changed routes, views, components, placements, layouts, and styles.
|
|
18
|
+
|
|
19
|
+
Check:
|
|
20
|
+
|
|
21
|
+
- Material Design quality.
|
|
22
|
+
- Vuetify best practices.
|
|
23
|
+
- visual hierarchy and density.
|
|
24
|
+
- spacing and alignment.
|
|
25
|
+
- responsive behavior on mobile, tablet, and desktop.
|
|
26
|
+
- loading, empty, error, disabled, and success states where relevant.
|
|
27
|
+
- accessibility basics.
|
|
28
|
+
- route and navigation coherence.
|
|
29
|
+
- consistency with the existing app style.
|
|
30
|
+
|
|
31
|
+
When clear scoped UI issues exist, fix them in the worktree. Keep fixes limited to the issue, confirmed issue details, and approved plan.
|
|
32
|
+
|
|
33
|
+
Use Playwright for a meaningful route check when possible. If login is required, use a development-only auth bootstrap path. When possible, record UI verification with:
|
|
34
|
+
|
|
35
|
+
`npx --no-install jskit app verify-ui --command "<playwright command>" --feature "<label>" --auth-mode <mode>`
|
|
36
|
+
|
|
37
|
+
Do not create commits, branches, issues, pull requests, merges, or worktree cleanup. JSKIT session owns those steps.
|
|
38
|
+
|
|
39
|
+
If this pass makes UI fixes, intentionally skips UI work after inspection, changes a design direction, or leaves a meaningful UI verification gap, include concise decision entries with reasons in this exact marker block:
|
|
40
|
+
|
|
41
|
+
```text
|
|
42
|
+
[agent_decisions]
|
|
43
|
+
<Deep UI decisions or "No new decisions.">
|
|
44
|
+
[/agent_decisions]
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
At the very end, include this completion block so Studio knows the step is complete:
|
|
48
|
+
|
|
49
|
+
[jskit_step_result]
|
|
50
|
+
status: complete
|
|
51
|
+
step: deep_ui_check_run
|
|
52
|
+
summary: Short summary of UI findings, fixes, verification, or why no UI work applied.
|
|
53
|
+
[/jskit_step_result]
|